How to Use Forms, Widgets, and the Admin¶
Registry model fields produce appropriate form fields by default, but you can also use them directly or swap in context-aware and hierarchical variants.
Form Fields¶
RegistryFormField¶
A ChoiceField that presents registry implementations as choices and returns the selected class on clean.
from django_stratagem import RegistryFormField
class MyForm(forms.Form):
strategy = RegistryFormField(registry=NotificationRegistry)
RegistryMultipleChoiceFormField¶
A TypedMultipleChoiceField for selecting multiple implementations.
from django_stratagem import RegistryMultipleChoiceFormField
class MyForm(forms.Form):
strategies = RegistryMultipleChoiceFormField(registry=NotificationRegistry)
ContextAwareRegistryFormField¶
Limits the choices shown based on the current user’s permissions, feature flags, or other runtime state (used with ConditionalInterface).
from django_stratagem import ContextAwareRegistryFormField
class MyForm(forms.Form):
strategy = ContextAwareRegistryFormField(
registry=NotificationRegistry,
context={"user": request.user, "request": request},
)
Call field.set_context(new_context) to update the context and refresh choices.
HierarchicalRegistryFormField¶
Shows only child options that are valid for the selected parent.
from django_stratagem import HierarchicalRegistryFormField
class MyForm(forms.Form):
category = RegistryFormField(registry=CategoryRegistry)
subcategory = HierarchicalRegistryFormField(
registry=SubcategoryRegistry,
parent_field="category",
)
Call field.set_parent_value(value) to update the parent and refresh choices.
Form Mixins¶
RegistryContextMixin¶
Mixin for forms that need to pass context to ContextAwareRegistryFormField fields.
from django_stratagem import RegistryContextMixin
class MyForm(RegistryContextMixin, forms.Form):
strategy = ContextAwareRegistryFormField(registry=NotificationRegistry)
# Usage:
form = MyForm(registry_context={"user": request.user})
HierarchicalFormMixin¶
Mixin for forms with hierarchical registry fields. Automatically sets up parent-child relationships and validates them on clean.
from django_stratagem import HierarchicalFormMixin
class MyForm(HierarchicalFormMixin, forms.ModelForm):
class Meta:
model = MyModel
fields = ["category", "subcategory"]
Widgets¶
RegistryWidget¶
Enhanced Select widget that adds title (description), data-description, data-icon, and data-priority attributes to each <option> element.
from django_stratagem import RegistryWidget
class MyForm(forms.Form):
strategy = RegistryFormField(
registry=NotificationRegistry,
widget=RegistryWidget(registry=NotificationRegistry),
)
RegistryDescriptionWidget¶
Extends RegistryWidget to render a companion <div> below the <select> that shows the description of whichever option is currently selected. The description updates on change via a small bundled JS file - no AJAX calls, no extra views or URL patterns needed.
Each <option> already carries a data-description attribute (set by RegistryWidget.create_option). The JS reads that attribute and writes its text into the container. When no description is available the container is hidden entirely.
from django_stratagem import RegistryDescriptionWidget
class MyForm(forms.Form):
strategy = RegistryFormField(
registry=NotificationRegistry,
widget=RegistryDescriptionWidget(registry=NotificationRegistry),
)
Pass description_attrs to control the container’s HTML attributes:
RegistryDescriptionWidget(
registry=NotificationRegistry,
description_attrs={"class": "alert alert-info", "style": "font-size: 0.9rem;"},
)
The container element looks like this in the rendered HTML:
<div id="id_strategy-registry-description"
class="registry-description-container alert alert-info"
data-registry-description-for="id_strategy"
style="font-size: 0.9rem;"
aria-live="polite"
aria-atomic="true">
Selected option's description text here.
</div>
If the form is loaded inside an HTMX swap, the JS reinitialises automatically on htmx:afterSettle.
Using show_description on model fields¶
For model forms you can skip the manual widget assignment. Pass show_description=True when overriding formfield() and the field will pick RegistryDescriptionWidget on its own:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ["strategy"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Override the field to get the description widget
self.fields["strategy"] = MyModel._meta.get_field("strategy").formfield(
show_description=True,
)
If you pass an explicit widget kwarg alongside show_description=True, the explicit widget wins.
HierarchicalRegistryWidget¶
A Select widget that emits data-parent-field and data-hierarchical attributes for JavaScript-driven dynamic updates.
from django_stratagem import HierarchicalRegistryWidget
class MyForm(forms.Form):
subcategory = HierarchicalRegistryFormField(
registry=SubcategoryRegistry,
parent_field="category",
widget=HierarchicalRegistryWidget(parent_field="category"),
)
Customizing Form Fields¶
Subclass the built-in form fields to add custom filtering or behavior:
from django_stratagem.forms import RegistryFormField
class FilteredRegistryFormField(RegistryFormField):
"""Only show implementations with priority < 100."""
def __init__(self, *args, max_priority=100, **kwargs):
super().__init__(*args, **kwargs)
self.max_priority = max_priority
self._filter_choices()
def _filter_choices(self):
registry = self.registry
filtered = [
(slug, label)
for slug, label in registry.get_choices()
if registry.implementations.get(slug, {}).get("priority", 0) < self.max_priority
]
self.choices = filtered
Extending Form Mixins¶
from django_stratagem.forms import RegistryContextMixin
class TenantRegistryForm(RegistryContextMixin, forms.ModelForm):
"""Automatically inject tenant from request."""
def __init__(self, *args, request=None, **kwargs):
context = {"user": request.user, "tenant": request.tenant} if request else {}
super().__init__(*args, registry_context=context, **kwargs)
Django Admin¶
ContextAwareRegistryAdmin¶
A ModelAdmin that injects request context into registry form fields so that conditional implementations are filtered per-user.
from django.contrib import admin
from django_stratagem.admin import ContextAwareRegistryAdmin
@admin.register(MyModel)
class MyModelAdmin(ContextAwareRegistryAdmin):
pass
HierarchicalRegistryAdmin¶
Extends ContextAwareRegistryAdmin with support for hierarchical registry fields. Adds data-hierarchical, data-registry, and data-parent-field widget attributes for JavaScript integration.
from django_stratagem.admin import HierarchicalRegistryAdmin
@admin.register(MyModel)
class MyModelAdmin(HierarchicalRegistryAdmin):
pass
Includes Media that references admin/js/hierarchical_registry.js.
RegistryFieldListFilter¶
Admin list filter for registry fields. Automatically registered for all AbstractRegistryField instances, but can also be used explicitly:
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
list_filter = (("strategy", RegistryFieldListFilter),)
The filter is context-aware and only shows implementations available to the current user.
RegistryListMixin¶
Mixin that automatically adds registry fields to list_display and list_filter.
Customizing Admin Behavior¶
Extending ContextAwareRegistryAdmin¶
from django_stratagem.admin import ContextAwareRegistryAdmin
class MyModelAdmin(ContextAwareRegistryAdmin):
def get_registry_context(self, request):
"""Build the context dict passed to conditional form fields."""
return {
"user": request.user,
"request": request,
"tenant": getattr(request, "tenant", None),
}
Adding Dashboard Actions¶
from django_stratagem.admin import DjangoStratagemAdminSite
class CustomAdminSite(DjangoStratagemAdminSite):
def get_urls(self):
urls = super().get_urls()
# Add custom registry management URLs
return urls
Dashboard Views¶
DjangoStratagemAdminSite¶
An AdminSite subclass that adds a registry dashboard at /admin/registry-dashboard/. Shows all registries, their implementations, availability status, and conditions.
EnhancedDjangoStratagemAdminSite¶
Extended dashboard at /admin/enhanced-registry-dashboard/ with hierarchy visualization, parent requirements, and relationship information.
Registry Inspector¶
django-stratagem ships a read-only inspector page that lists every registry,
its implementations, and whether each is currently available (and why). To
enable it, include the package URLs in your project’s root urls.py:
from django.urls import include, path
urlpatterns = [
# ...
path("stratagem/", include("django_stratagem.urls")),
]
The inspector is then reachable at /stratagem/inspector/ and is restricted
to staff users.
Linking it from the admin¶
Do not add a admin/index.html template inside an installed app whose
templates shadow django.contrib.admin (such as django-stratagem itself):
{% extends "admin/index.html" %} would resolve to itself and recurse. Add
the override in a project-level template directory configured in
TEMPLATES["DIRS"] instead:
{% extends "admin/index.html" %}
{% block content %}
{{ block.super }}
<div class="module">
<h2>Django Stratagem</h2>
<ul><li><a href="{% url 'django_stratagem:registry-inspector' %}">Registry Inspector</a></li></ul>
</div>
{% endblock %}
The URL name is also available programmatically via the constant
django_stratagem.inspector.INSPECTOR_URL_NAME.