Admin System Checks
The Django Admin system relies heavily on declarative configuration. Developers define lists and tuples of field names (e.g., list_display, fields, search_fields) to control the interface. Because these are strings, they are prone to typos or referencing fields that no longer exist on the model. The Admin System Checks provide a robust validation layer that runs at startup to catch these configuration errors before they cause runtime crashes.
The Check Class Hierarchy
The validation logic is encapsulated in a hierarchy of "check classes" located in django/contrib/admin/checks.py. This design allows for code reuse between standard model admins and inline admins.
BaseModelAdminChecks
The BaseModelAdminChecks class serves as the foundation. It contains validation logic for attributes shared by both ModelAdmin and InlineModelAdmin. Its check() method orchestrates a series of internal checks:
class BaseModelAdminChecks:
def check(self, admin_obj, **kwargs):
return [
*self._check_autocomplete_fields(admin_obj),
*self._check_raw_id_fields(admin_obj),
*self._check_fields(admin_obj),
*self._check_fieldsets(admin_obj),
*self._check_exclude(admin_obj),
# ... other checks ...
*self._check_readonly_fields(admin_obj),
]
This class focuses on core form-related options. For example, _check_fields ensures that the fields attribute is a list or tuple and doesn't contain duplicates or overlap with fieldsets.
ModelAdminChecks
ModelAdminChecks inherits from the base class and adds validation for options specific to the main change list and change form views, such as list_display, list_filter, and search_fields.
Crucially, ModelAdminChecks is responsible for triggering checks on any inlines registered with the admin via _check_inlines. It iterates through admin_obj.inlines, verifies they inherit from InlineModelAdmin, and then calls the check() method on the inline instance itself.
InlineModelAdminChecks
InlineModelAdminChecks handles validation specific to inline relationships. It ensures that the inline correctly references a parent model and that the foreign key relationship is valid.
One specific check in this class is _check_exclude_of_parent_model, which prevents developers from excluding the foreign key field that links the inline model to the parent model, as this would make the relationship impossible to maintain in the admin interface.
Integration and Execution
The admin system integrates these checks through the checks_class attribute on the admin options classes in django/contrib/admin/options.py.
BaseModelAdminsetschecks_class = BaseModelAdminChecks.ModelAdminoverrides this withchecks_class = ModelAdminChecks.
When the system check framework runs, it eventually calls the check() method on the AdminSite instance (found in django/contrib/admin/sites.py). The AdminSite then iterates over all registered ModelAdmin instances and triggers their validation:
# django/contrib/admin/sites.py
def check(self, app_configs):
# ...
errors = []
modeladmins = (
o for o in self._registry.values() if o.__class__ is not ModelAdmin
)
for modeladmin in modeladmins:
if modeladmin.model._meta.app_config in app_configs:
errors.extend(modeladmin.check())
return errors
Note that AdminSite skips instances of the raw ModelAdmin class, performing checks only on customized subclasses.
Validation Logic and Patterns
The checks rely heavily on the Django model _meta API to verify that the strings provided in the configuration correspond to real model fields.
Field Verification
Most checks follow a pattern of attempting to retrieve a field via obj.model._meta.get_field(field_name). If the field does not exist, a FieldDoesNotExist exception is caught, and a specific error (like admin.E002 for raw_id_fields) is returned.
Inter-field Dependencies
The system also validates that different options don't conflict. A notable example is the relationship between list_editable and list_display_links. In ModelAdminChecks._check_list_editable_item, the system ensures that a field is not present in both lists, as a field cannot be both a link to the change page and an editable input simultaneously.
Permissive Validation for Read-only Fields
Unlike fields or list_display, the readonly_fields attribute is more permissive. In BaseModelAdminChecks._check_readonly_fields_item, the system allows a value if it is:
- A callable.
- An attribute of the
ModelAdminclass itself. - An attribute of the
Modelclass. - A valid field on the model.
This flexibility supports the common pattern of defining "calculated fields" as methods on the admin class.
Design Tradeoffs and Constraints
The implementation acknowledges that some validations are too complex or "fiddly" to perform reliably at startup.
For instance, in BaseModelAdminChecks._check_ordering_item, the system explicitly skips validation for field names containing the lookup separator (LOOKUP_SEP, usually __). While it can verify that a local field like name exists, verifying a deep relationship like author__profile__bio is considered too complex for the startup check phase.
Similarly, _check_field_spec_item allows fields that aren't on the model if they might be extra fields defined on the admin's form. Because the form might be generated dynamically in get_form(), the system check framework cannot always know the full set of available form fields at the time the checks are run.