19.16 The admin’s checks: check_dependencies and ModelAdminChecks
The admin’s system checks are two functions registered under the tag "admin": check_dependencies, which looks for the applications, template settings and middleware the admin needs, and check_admin_app, which has every admin site check its model admins, each through a checks class, ModelAdminChecks for a model admin and InlineModelAdminChecks for an inline.
Most of a model admin’s options are read only when a page uses them, so a misspelt name would otherwise surface only on that page, often as an exception. The checks read them, and the settings, when a command runs the system checks (The system checks).
When they run
SimpleAdminConfig registers both functions, and AdminConfig calls it before autodiscover. They run after every application is ready: under AdminConfig, after every admin module has registered its model admins.
The admin’s dependencies: check_dependencies
check_dependencies looks at the settings. It returns nothing when the admin is not installed, and takes no notice of the applications it is given.
check_dependencies asks, and the id of each error or warning.| what is required | id | asked |
|---|---|---|
the contenttypes, auth and messages applications in INSTALLED_APPS |
id "admin.E401", "admin.E405", "admin.E406" |
asked always |
a DjangoTemplates engine in TEMPLATES |
id "admin.E403" |
asked always |
"django |
id "admin.E402" |
asked when there is such an engine and AUTHENTICATION_ holds ModelBackend or a subclass |
"django |
id "admin.E404" |
asked when there is such an engine |
"django |
id "admin.W411", a warning |
asked when there is such an engine and a site shows the navigation sidebar |
Authentication, Message, Session in MIDDLEWARE |
id "admin.E408", "admin.E409", "admin.E410" |
asked always |
The context processors are looked for in the first DjangoTemplates engine only, by the dotted paths the setting spells. The admin’s base template reads "user" and "messages", which the auth and messages processors set, and the sidebar includes "admin/, which reads the request’s path. The warning needs a site in all_sites with AdminSite true, its default.
The middleware are looked for by class: _contains_subclass imports each path in MIDDLEWARE and is satisfied by the class required or a subclass of it. It asks whether each is there, not in what order: the hint of "admin.E410" says to put SessionMiddleware before AuthenticationMiddleware, yet harbour’s settings with AuthenticationMiddleware moved first passed every check. With MessageMiddleware taken out:
# the check command, harbour's MIDDLEWARE without MessageMiddleware
SystemCheckError: System check identified some issues:
ERRORS:
?: (admin.E409) 'django.contrib.messages.middleware.MessageMiddleware' must be in MIDDLEWARE in order to use the admin application.
System check identified 1 issue (0 silenced).
Every site and its model admins: check_admin_app
check_admin_app calls AdminSite.check on each site in all_sites, a weak set that AdminSite adds every site to, so a site is checked whether or not its URLs are in the URLconf, as long as something holds it. AdminSite.check passes over a model admin whose class is ModelAdmin itself, one its docstring calls not customized at all, and checks each other one whose model belongs to an application the run was given, or to any when it was given none. In the harbour office that is GroupAdmin, UserAdmin, PortAdmin and ShipAdmin, and not Voyage’s plain ModelAdmin.
BaseModelAdmin makes an instance of the class in BaseModelAdmin and hands it the model admin. ModelAdmin names ModelAdminChecks there and InlineModelAdmin names InlineModelAdminChecks, both subclasses of BaseModelAdminChecks, which checks the options the two share; a project adds checks of its own by naming a subclass in checks_class. An inline is checked through its parent: for each class in inlines that inherits from InlineModelAdmin and names a model, ModelAdminChecks makes an instance with the parent’s model and site and calls its check.
What the options are checked against
The checks read the options as attributes and call none of the hooks, which take a request; the check of list_display_links against list_display is skipped when the class overrides ModelAdmin.
- The type. Many options must be a list or a tuple, others a dictionary, a boolean or an integer;
formmust inherit fromBaseModelForm, an inline’sformsetfromBaseModelFormSet. - Fields of the model. Each name in
raw_id_fields,autocomplete_fields,filter_horizontal,filter_vertical,radio_fields,prepopulated_fields,list_editableandorderingmust be a field, thoughorderinglets"?"alone,"pk", a path with"__"and an expression other than anFthrough.list_displayandreadonly_fieldstake more: a callable, or an attribute of the model admin or of the model, andlist_displaya path across relations, though no item of it may be a many-to-many field or a reverse foreign key.date_hierarchymust lead to a date or date-and-time field, andlist_filtermust take one of its three forms (Filters and search:list_filter,search_fieldsanddate_hierarchy). - Relations.
raw_id_fieldsandautocomplete_fieldsmust name foreign keys or many-to-many fields, and the model an autocomplete field points to must be registered on the same site, withsearch_fieldson its model admin.filter_horizontalandfilter_verticalmust name many-to-many fields with no through model of their own. list_editable. Each field must be inlist_display, editable, not the primary key, and not inlist_display_links; nor the first column whilelist_display_linksis empty, as it is by default, which links the first column.Nonethere, or another column, frees it.- Actions. Only the actions whose locations include
ActionLocationare looked at: each permission in an action’s.CHANGE_LIST "allowed_permissions"needs a method named after it, asBaseModelAdminis after.has_change_permission "change", and no two of them may share a name. - An inline’s parent.
InlineModelAdminChecksasks_get_foreign_keyfor the inline’s foreign key to the parent, byfk_nameif set, reports its error as"admin.E202", and refuses anexcludenaming that key.
A name in fields or fieldsets that is not a field passes, since, a comment says, it could be an extra field of the form, and search_fields is checked only for its type. A ShipAdmin with “draught” in fields and “beam” in search_fields passed the checks, and then its change form and its search each raised FieldError.
The messages
Most messages take one of three shapes, made by must_be, must_inherit_from and refer_to_missing_field: the option, or the item at fault with its index, and what it must be, must inherit from, or names that the model lacks. Each gives the class checked as its object. Here ShipAdmin has three mistakes, and harbour/crew/admin.py registers Sailor under the plain ModelAdmin:
# harbour/fleet/admin.py
class ShipAdmin(admin.ModelAdmin):
list_display = ["name", "tonnage", "home", "is_large", "draught"]
list_editable = ["name"]
autocomplete_fields = ["captain"]
# the rest as before
# the check command, with ShipAdmin as above
SystemCheckError: System check identified some issues:
ERRORS:
<class 'harbour.fleet.admin.ShipAdmin'>: (admin.E040) ModelAdmin must define "search_fields", because it's referenced by ShipAdmin.autocomplete_fields.
<class 'harbour.fleet.admin.ShipAdmin'>: (admin.E108) The value of 'list_display[4]' refers to 'draught', which is not a callable or attribute of 'ShipAdmin', or an attribute, method, or field on 'fleet.Ship'.
<class 'harbour.fleet.admin.ShipAdmin'>: (admin.E124) The value of 'list_editable[0]' refers to the first field in 'list_display' ('name'), which cannot be used unless 'list_display_links' is set.
System check identified 3 issues (0 silenced).