Architecture¶
High-level components¶
ReverseRelationAdminMixin layers reverse-relation behaviour onto Django’s
ModelAdmin lifecycle. Configuration happens declaratively on the admin
class; the mixin then wires dynamic form fields, validation, permissions, and
persistence logic around Django’s normal flow.
reverse_relations— declarative mapping of virtual field name toReverseRelationConfig. Each configuration knows which reverse model to touch, which ForeignKey points back to the admin object, whether the selection is multi-valued, and how to scope the queryset.ReverseRelationConfig— per-field knobs for labels, widgets, queryset limiters, validation hooks, and permission policies.reverse_relations_atomic— governs whether all reverse updates execute in a singledjango.db.transaction.atomic()block.Permission hooks — optional policies that gate rendering and persistence.
Form lifecycle¶
Field declaration — the admin declares virtual field names in
reverse_relationsand lists them infieldsetsso the Django admin template renders them.Form construction —
get_form()strips the virtual names out of the basefieldsargument (to avoid Django’s “unknown field” errors) and delegates tosuper(). After the base form class is produced, the mixin injectsModelChoiceFieldorModelMultipleChoiceFieldinstances for each configured relation. Querysets come fromReverseRelationConfig.limit_choices_to(callable ordict) plus optionalordering.Initial data — the derived form’s
__init__resolves the queryset and current selections for the object under edit. Virtual fields point at the reverse model’s objects whose ForeignKey already references the parent instance.Render gate — if
reverse_permissions_enabledis true, the form checks permissions before rendering. By default this uses a base permission check, but can be configured to use the full permission policy to allow per-field visibility. Fields become hidden or disabled based onreverse_permission_mode.
See also
The rendering flow and configuration options are detailed in Rendering & Visibility.
Validation and permissions¶
ReverseRelationConfig.cleanhooks run during formclean()with(instance, selection, request). Use this for business rules such as capacity limits or forbidding unbinds.Permission evaluation happens twice:
During
clean()— when a custom policy (per-field or global) denies a specific selection, the field receives a validation error. Error messages resolve using the precedence described in Permissions.During
save()— unauthorized fields are excluded from the persistence payload to guard against crafted POSTs.
Persistence¶
ModelForm.save delegates to the base implementation and then synchronizes
reverse relations via
_apply_reverse_relations().
For each configured field:
Multi-select fields compute the exact set of rows that should point at the parent instance. Items removed from the selection are unbound (ForeignKey set to
None) before new bindings are applied.Single-select fields unbind all rows except the chosen object, then bind the target if it is not already pointing at the instance.
When reverse_relations_atomic is True (the default) all configured
fields are synchronized inside a single transaction so either all bindings are
updated or none are. Unbinds happen before binds within each field to minimise
transient uniqueness conflicts on OneToOneField or unique ForeignKeys.
Note
commit=False
If a form is saved with commit=False, the mixin defers reverse updates
until save_model(). The
payload of authorized reverse fields is stored on the form instance and
applied during the admin save hook.
Extensibility checklist¶
Provide custom widgets by supplying
ReverseRelationConfig.widgetwith a widget instance or class (e.g., DAL/Unfold).Scope querysets dynamically with a callable
limit_choices_to. Callables receive the current request and instance, allowing per-user filtering.Implement per-field
permissionpolicies or assignreverse_permission_policyon the admin for global rules. Policies may be callables or objects implementinghas_perm.Override
has_reverse_change_permission()if you need to enforce different permission codenames (add/delete) or object-level checks.