Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/contrib/admin/checks.py: 59%
456 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import collections
2from itertools import chain
4from django.apps import apps
5from django.conf import settings
6from django.contrib.admin.utils import NotRelationField, flatten, get_fields_from_path
7from django.core import checks
8from django.core.exceptions import FieldDoesNotExist
9from django.db import models
10from django.db.models.constants import LOOKUP_SEP
11from django.db.models.expressions import Combinable
12from django.forms.models import BaseModelForm, BaseModelFormSet, _get_foreign_key
13from django.template import engines
14from django.template.backends.django import DjangoTemplates
15from django.utils.module_loading import import_string
18def _issubclass(cls, classinfo):
19 """
20 issubclass() variant that doesn't raise an exception if cls isn't a
21 class.
22 """
23 try:
24 return issubclass(cls, classinfo)
25 except TypeError:
26 return False
29def _contains_subclass(class_path, candidate_paths):
30 """
31 Return whether or not a dotted class path (or a subclass of that class) is
32 found in a list of candidate paths.
33 """
34 cls = import_string(class_path)
35 for path in candidate_paths: 35 ↛ 43line 35 didn't jump to line 43, because the loop on line 35 didn't complete
36 try:
37 candidate_cls = import_string(path)
38 except ImportError:
39 # ImportErrors are raised elsewhere.
40 continue
41 if _issubclass(candidate_cls, cls):
42 return True
43 return False
46def check_admin_app(app_configs, **kwargs):
47 from django.contrib.admin.sites import all_sites
49 errors = []
50 for site in all_sites:
51 errors.extend(site.check(app_configs))
52 return errors
55def check_dependencies(**kwargs):
56 """
57 Check that the admin's dependencies are correctly installed.
58 """
59 from django.contrib.admin.sites import all_sites
61 if not apps.is_installed("django.contrib.admin"): 61 ↛ 62line 61 didn't jump to line 62, because the condition on line 61 was never true
62 return []
63 errors = []
64 app_dependencies = (
65 ("django.contrib.contenttypes", 401),
66 ("django.contrib.auth", 405),
67 ("django.contrib.messages", 406),
68 )
69 for app_name, error_code in app_dependencies:
70 if not apps.is_installed(app_name): 70 ↛ 71line 70 didn't jump to line 71, because the condition on line 70 was never true
71 errors.append(
72 checks.Error(
73 "'%s' must be in INSTALLED_APPS in order to use the admin "
74 "application." % app_name,
75 id="admin.E%d" % error_code,
76 )
77 )
78 for engine in engines.all(): 78 ↛ 83line 78 didn't jump to line 83, because the loop on line 78 didn't complete
79 if isinstance(engine, DjangoTemplates): 79 ↛ 78line 79 didn't jump to line 78, because the condition on line 79 was never false
80 django_templates_instance = engine.engine
81 break
82 else:
83 django_templates_instance = None
84 if not django_templates_instance: 84 ↛ 85line 84 didn't jump to line 85, because the condition on line 84 was never true
85 errors.append(
86 checks.Error(
87 "A 'django.template.backends.django.DjangoTemplates' instance "
88 "must be configured in TEMPLATES in order to use the admin "
89 "application.",
90 id="admin.E403",
91 )
92 )
93 else:
94 if ( 94 ↛ 102line 94 didn't jump to line 102
95 "django.contrib.auth.context_processors.auth"
96 not in django_templates_instance.context_processors
97 and _contains_subclass(
98 "django.contrib.auth.backends.ModelBackend",
99 settings.AUTHENTICATION_BACKENDS,
100 )
101 ):
102 errors.append(
103 checks.Error(
104 "'django.contrib.auth.context_processors.auth' must be "
105 "enabled in DjangoTemplates (TEMPLATES) if using the default "
106 "auth backend in order to use the admin application.",
107 id="admin.E402",
108 )
109 )
110 if ( 110 ↛ 114line 110 didn't jump to line 114
111 "django.contrib.messages.context_processors.messages"
112 not in django_templates_instance.context_processors
113 ):
114 errors.append(
115 checks.Error(
116 "'django.contrib.messages.context_processors.messages' must "
117 "be enabled in DjangoTemplates (TEMPLATES) in order to use "
118 "the admin application.",
119 id="admin.E404",
120 )
121 )
122 sidebar_enabled = any(site.enable_nav_sidebar for site in all_sites) 122 ↛ exitline 122 didn't finish the generator expression on line 122
123 if ( 123 ↛ 128line 123 didn't jump to line 128
124 sidebar_enabled
125 and "django.template.context_processors.request"
126 not in django_templates_instance.context_processors
127 ):
128 errors.append(
129 checks.Warning(
130 "'django.template.context_processors.request' must be enabled "
131 "in DjangoTemplates (TEMPLATES) in order to use the admin "
132 "navigation sidebar.",
133 id="admin.W411",
134 )
135 )
137 if not _contains_subclass( 137 ↛ 140line 137 didn't jump to line 140, because the condition on line 137 was never true
138 "django.contrib.auth.middleware.AuthenticationMiddleware", settings.MIDDLEWARE
139 ):
140 errors.append(
141 checks.Error(
142 "'django.contrib.auth.middleware.AuthenticationMiddleware' must "
143 "be in MIDDLEWARE in order to use the admin application.",
144 id="admin.E408",
145 )
146 )
147 if not _contains_subclass( 147 ↛ 150line 147 didn't jump to line 150, because the condition on line 147 was never true
148 "django.contrib.messages.middleware.MessageMiddleware", settings.MIDDLEWARE
149 ):
150 errors.append(
151 checks.Error(
152 "'django.contrib.messages.middleware.MessageMiddleware' must "
153 "be in MIDDLEWARE in order to use the admin application.",
154 id="admin.E409",
155 )
156 )
157 if not _contains_subclass( 157 ↛ 160line 157 didn't jump to line 160, because the condition on line 157 was never true
158 "django.contrib.sessions.middleware.SessionMiddleware", settings.MIDDLEWARE
159 ):
160 errors.append(
161 checks.Error(
162 "'django.contrib.sessions.middleware.SessionMiddleware' must "
163 "be in MIDDLEWARE in order to use the admin application.",
164 hint=(
165 "Insert "
166 "'django.contrib.sessions.middleware.SessionMiddleware' "
167 "before "
168 "'django.contrib.auth.middleware.AuthenticationMiddleware'."
169 ),
170 id="admin.E410",
171 )
172 )
173 return errors
176class BaseModelAdminChecks:
177 def check(self, admin_obj, **kwargs):
178 return [
179 *self._check_autocomplete_fields(admin_obj),
180 *self._check_raw_id_fields(admin_obj),
181 *self._check_fields(admin_obj),
182 *self._check_fieldsets(admin_obj),
183 *self._check_exclude(admin_obj),
184 *self._check_form(admin_obj),
185 *self._check_filter_vertical(admin_obj),
186 *self._check_filter_horizontal(admin_obj),
187 *self._check_radio_fields(admin_obj),
188 *self._check_prepopulated_fields(admin_obj),
189 *self._check_view_on_site_url(admin_obj),
190 *self._check_ordering(admin_obj),
191 *self._check_readonly_fields(admin_obj),
192 ]
194 def _check_autocomplete_fields(self, obj):
195 """
196 Check that `autocomplete_fields` is a list or tuple of model fields.
197 """
198 if not isinstance(obj.autocomplete_fields, (list, tuple)): 198 ↛ 199line 198 didn't jump to line 199, because the condition on line 198 was never true
199 return must_be(
200 "a list or tuple",
201 option="autocomplete_fields",
202 obj=obj,
203 id="admin.E036",
204 )
205 else:
206 return list(
207 chain.from_iterable(
208 [
209 self._check_autocomplete_fields_item(
210 obj, field_name, "autocomplete_fields[%d]" % index
211 )
212 for index, field_name in enumerate(obj.autocomplete_fields)
213 ]
214 )
215 )
217 def _check_autocomplete_fields_item(self, obj, field_name, label):
218 """
219 Check that an item in `autocomplete_fields` is a ForeignKey or a
220 ManyToManyField and that the item has a related ModelAdmin with
221 search_fields defined.
222 """
223 try:
224 field = obj.model._meta.get_field(field_name)
225 except FieldDoesNotExist:
226 return refer_to_missing_field(
227 field=field_name, option=label, obj=obj, id="admin.E037"
228 )
229 else:
230 if not field.many_to_many and not isinstance(field, models.ForeignKey): 230 ↛ 231line 230 didn't jump to line 231, because the condition on line 230 was never true
231 return must_be(
232 "a foreign key or a many-to-many field",
233 option=label,
234 obj=obj,
235 id="admin.E038",
236 )
237 related_admin = obj.admin_site._registry.get(field.remote_field.model)
238 if related_admin is None: 238 ↛ 239line 238 didn't jump to line 239, because the condition on line 238 was never true
239 return [
240 checks.Error(
241 'An admin for model "%s" has to be registered '
242 "to be referenced by %s.autocomplete_fields."
243 % (
244 field.remote_field.model.__name__,
245 type(obj).__name__,
246 ),
247 obj=obj.__class__,
248 id="admin.E039",
249 )
250 ]
251 elif not related_admin.search_fields: 251 ↛ 252line 251 didn't jump to line 252, because the condition on line 251 was never true
252 return [
253 checks.Error(
254 '%s must define "search_fields", because it\'s '
255 "referenced by %s.autocomplete_fields."
256 % (
257 related_admin.__class__.__name__,
258 type(obj).__name__,
259 ),
260 obj=obj.__class__,
261 id="admin.E040",
262 )
263 ]
264 return []
266 def _check_raw_id_fields(self, obj):
267 """Check that `raw_id_fields` only contains field names that are listed
268 on the model."""
270 if not isinstance(obj.raw_id_fields, (list, tuple)): 270 ↛ 271line 270 didn't jump to line 271, because the condition on line 270 was never true
271 return must_be(
272 "a list or tuple", option="raw_id_fields", obj=obj, id="admin.E001"
273 )
274 else:
275 return list(
276 chain.from_iterable(
277 self._check_raw_id_fields_item(
278 obj, field_name, "raw_id_fields[%d]" % index
279 )
280 for index, field_name in enumerate(obj.raw_id_fields)
281 )
282 )
284 def _check_raw_id_fields_item(self, obj, field_name, label):
285 """Check an item of `raw_id_fields`, i.e. check that field named
286 `field_name` exists in model `model` and is a ForeignKey or a
287 ManyToManyField."""
289 try:
290 field = obj.model._meta.get_field(field_name)
291 except FieldDoesNotExist:
292 return refer_to_missing_field(
293 field=field_name, option=label, obj=obj, id="admin.E002"
294 )
295 else:
296 # Using attname is not supported.
297 if field.name != field_name: 297 ↛ 298line 297 didn't jump to line 298, because the condition on line 297 was never true
298 return refer_to_missing_field(
299 field=field_name,
300 option=label,
301 obj=obj,
302 id="admin.E002",
303 )
304 if not field.many_to_many and not isinstance(field, models.ForeignKey): 304 ↛ 305line 304 didn't jump to line 305, because the condition on line 304 was never true
305 return must_be(
306 "a foreign key or a many-to-many field",
307 option=label,
308 obj=obj,
309 id="admin.E003",
310 )
311 else:
312 return []
314 def _check_fields(self, obj):
315 """Check that `fields` only refer to existing fields, doesn't contain
316 duplicates. Check if at most one of `fields` and `fieldsets` is defined.
317 """
319 if obj.fields is None: 319 ↛ 321line 319 didn't jump to line 321, because the condition on line 319 was never false
320 return []
321 elif not isinstance(obj.fields, (list, tuple)):
322 return must_be("a list or tuple", option="fields", obj=obj, id="admin.E004")
323 elif obj.fieldsets:
324 return [
325 checks.Error(
326 "Both 'fieldsets' and 'fields' are specified.",
327 obj=obj.__class__,
328 id="admin.E005",
329 )
330 ]
331 fields = flatten(obj.fields)
332 if len(fields) != len(set(fields)):
333 return [
334 checks.Error(
335 "The value of 'fields' contains duplicate field(s).",
336 obj=obj.__class__,
337 id="admin.E006",
338 )
339 ]
341 return list(
342 chain.from_iterable(
343 self._check_field_spec(obj, field_name, "fields")
344 for field_name in obj.fields
345 )
346 )
348 def _check_fieldsets(self, obj):
349 """Check that fieldsets is properly formatted and doesn't contain
350 duplicates."""
352 if obj.fieldsets is None:
353 return []
354 elif not isinstance(obj.fieldsets, (list, tuple)): 354 ↛ 355line 354 didn't jump to line 355, because the condition on line 354 was never true
355 return must_be(
356 "a list or tuple", option="fieldsets", obj=obj, id="admin.E007"
357 )
358 else:
359 seen_fields = []
360 return list(
361 chain.from_iterable(
362 self._check_fieldsets_item(
363 obj, fieldset, "fieldsets[%d]" % index, seen_fields
364 )
365 for index, fieldset in enumerate(obj.fieldsets)
366 )
367 )
369 def _check_fieldsets_item(self, obj, fieldset, label, seen_fields):
370 """Check an item of `fieldsets`, i.e. check that this is a pair of a
371 set name and a dictionary containing "fields" key."""
373 if not isinstance(fieldset, (list, tuple)): 373 ↛ 374line 373 didn't jump to line 374, because the condition on line 373 was never true
374 return must_be("a list or tuple", option=label, obj=obj, id="admin.E008")
375 elif len(fieldset) != 2: 375 ↛ 376line 375 didn't jump to line 376, because the condition on line 375 was never true
376 return must_be("of length 2", option=label, obj=obj, id="admin.E009")
377 elif not isinstance(fieldset[1], dict): 377 ↛ 378line 377 didn't jump to line 378, because the condition on line 377 was never true
378 return must_be(
379 "a dictionary", option="%s[1]" % label, obj=obj, id="admin.E010"
380 )
381 elif "fields" not in fieldset[1]: 381 ↛ 382line 381 didn't jump to line 382, because the condition on line 381 was never true
382 return [
383 checks.Error(
384 "The value of '%s[1]' must contain the key 'fields'." % label,
385 obj=obj.__class__,
386 id="admin.E011",
387 )
388 ]
389 elif not isinstance(fieldset[1]["fields"], (list, tuple)): 389 ↛ 390line 389 didn't jump to line 390, because the condition on line 389 was never true
390 return must_be(
391 "a list or tuple",
392 option="%s[1]['fields']" % label,
393 obj=obj,
394 id="admin.E008",
395 )
397 seen_fields.extend(flatten(fieldset[1]["fields"]))
398 if len(seen_fields) != len(set(seen_fields)): 398 ↛ 399line 398 didn't jump to line 399, because the condition on line 398 was never true
399 return [
400 checks.Error(
401 "There are duplicate field(s) in '%s[1]'." % label,
402 obj=obj.__class__,
403 id="admin.E012",
404 )
405 ]
406 return list(
407 chain.from_iterable(
408 self._check_field_spec(obj, fieldset_fields, '%s[1]["fields"]' % label)
409 for fieldset_fields in fieldset[1]["fields"]
410 )
411 )
413 def _check_field_spec(self, obj, fields, label):
414 """`fields` should be an item of `fields` or an item of
415 fieldset[1]['fields'] for any `fieldset` in `fieldsets`. It should be a
416 field name or a tuple of field names."""
418 if isinstance(fields, tuple): 418 ↛ 419line 418 didn't jump to line 419, because the condition on line 418 was never true
419 return list(
420 chain.from_iterable(
421 self._check_field_spec_item(
422 obj, field_name, "%s[%d]" % (label, index)
423 )
424 for index, field_name in enumerate(fields)
425 )
426 )
427 else:
428 return self._check_field_spec_item(obj, fields, label)
430 def _check_field_spec_item(self, obj, field_name, label):
431 if field_name in obj.readonly_fields:
432 # Stuff can be put in fields that isn't actually a model field if
433 # it's in readonly_fields, readonly_fields will handle the
434 # validation of such things.
435 return []
436 else:
437 try:
438 field = obj.model._meta.get_field(field_name)
439 except FieldDoesNotExist:
440 # If we can't find a field on the model that matches, it could
441 # be an extra field on the form.
442 return []
443 else:
444 if ( 444 ↛ 448line 444 didn't jump to line 448
445 isinstance(field, models.ManyToManyField)
446 and not field.remote_field.through._meta.auto_created
447 ):
448 return [
449 checks.Error(
450 "The value of '%s' cannot include the ManyToManyField "
451 "'%s', because that field manually specifies a "
452 "relationship model." % (label, field_name),
453 obj=obj.__class__,
454 id="admin.E013",
455 )
456 ]
457 else:
458 return []
460 def _check_exclude(self, obj):
461 """Check that exclude is a sequence without duplicates."""
463 if obj.exclude is None: # default value is None 463 ↛ 465line 463 didn't jump to line 465, because the condition on line 463 was never false
464 return []
465 elif not isinstance(obj.exclude, (list, tuple)):
466 return must_be(
467 "a list or tuple", option="exclude", obj=obj, id="admin.E014"
468 )
469 elif len(obj.exclude) > len(set(obj.exclude)):
470 return [
471 checks.Error(
472 "The value of 'exclude' contains duplicate field(s).",
473 obj=obj.__class__,
474 id="admin.E015",
475 )
476 ]
477 else:
478 return []
480 def _check_form(self, obj):
481 """Check that form subclasses BaseModelForm."""
482 if not _issubclass(obj.form, BaseModelForm): 482 ↛ 483line 482 didn't jump to line 483, because the condition on line 482 was never true
483 return must_inherit_from(
484 parent="BaseModelForm", option="form", obj=obj, id="admin.E016"
485 )
486 else:
487 return []
489 def _check_filter_vertical(self, obj):
490 """Check that filter_vertical is a sequence of field names."""
491 if not isinstance(obj.filter_vertical, (list, tuple)): 491 ↛ 492line 491 didn't jump to line 492, because the condition on line 491 was never true
492 return must_be(
493 "a list or tuple", option="filter_vertical", obj=obj, id="admin.E017"
494 )
495 else:
496 return list(
497 chain.from_iterable(
498 self._check_filter_item(
499 obj, field_name, "filter_vertical[%d]" % index
500 )
501 for index, field_name in enumerate(obj.filter_vertical)
502 )
503 )
505 def _check_filter_horizontal(self, obj):
506 """Check that filter_horizontal is a sequence of field names."""
507 if not isinstance(obj.filter_horizontal, (list, tuple)): 507 ↛ 508line 507 didn't jump to line 508, because the condition on line 507 was never true
508 return must_be(
509 "a list or tuple", option="filter_horizontal", obj=obj, id="admin.E018"
510 )
511 else:
512 return list(
513 chain.from_iterable(
514 self._check_filter_item(
515 obj, field_name, "filter_horizontal[%d]" % index
516 )
517 for index, field_name in enumerate(obj.filter_horizontal)
518 )
519 )
521 def _check_filter_item(self, obj, field_name, label):
522 """Check one item of `filter_vertical` or `filter_horizontal`, i.e.
523 check that given field exists and is a ManyToManyField."""
525 try:
526 field = obj.model._meta.get_field(field_name)
527 except FieldDoesNotExist:
528 return refer_to_missing_field(
529 field=field_name, option=label, obj=obj, id="admin.E019"
530 )
531 else:
532 if not field.many_to_many: 532 ↛ 533line 532 didn't jump to line 533, because the condition on line 532 was never true
533 return must_be(
534 "a many-to-many field", option=label, obj=obj, id="admin.E020"
535 )
536 else:
537 return []
539 def _check_radio_fields(self, obj):
540 """Check that `radio_fields` is a dictionary."""
541 if not isinstance(obj.radio_fields, dict): 541 ↛ 542line 541 didn't jump to line 542, because the condition on line 541 was never true
542 return must_be(
543 "a dictionary", option="radio_fields", obj=obj, id="admin.E021"
544 )
545 else:
546 return list(
547 chain.from_iterable(
548 self._check_radio_fields_key(obj, field_name, "radio_fields")
549 + self._check_radio_fields_value(
550 obj, val, 'radio_fields["%s"]' % field_name
551 )
552 for field_name, val in obj.radio_fields.items()
553 )
554 )
556 def _check_radio_fields_key(self, obj, field_name, label):
557 """Check that a key of `radio_fields` dictionary is name of existing
558 field and that the field is a ForeignKey or has `choices` defined."""
560 try:
561 field = obj.model._meta.get_field(field_name)
562 except FieldDoesNotExist:
563 return refer_to_missing_field(
564 field=field_name, option=label, obj=obj, id="admin.E022"
565 )
566 else:
567 if not (isinstance(field, models.ForeignKey) or field.choices):
568 return [
569 checks.Error(
570 "The value of '%s' refers to '%s', which is not an "
571 "instance of ForeignKey, and does not have a 'choices' "
572 "definition." % (label, field_name),
573 obj=obj.__class__,
574 id="admin.E023",
575 )
576 ]
577 else:
578 return []
580 def _check_radio_fields_value(self, obj, val, label):
581 """Check type of a value of `radio_fields` dictionary."""
583 from django.contrib.admin.options import HORIZONTAL, VERTICAL
585 if val not in (HORIZONTAL, VERTICAL):
586 return [
587 checks.Error(
588 "The value of '%s' must be either admin.HORIZONTAL or "
589 "admin.VERTICAL." % label,
590 obj=obj.__class__,
591 id="admin.E024",
592 )
593 ]
594 else:
595 return []
597 def _check_view_on_site_url(self, obj):
598 if not callable(obj.view_on_site) and not isinstance(obj.view_on_site, bool): 598 ↛ 599line 598 didn't jump to line 599, because the condition on line 598 was never true
599 return [
600 checks.Error(
601 "The value of 'view_on_site' must be a callable or a boolean "
602 "value.",
603 obj=obj.__class__,
604 id="admin.E025",
605 )
606 ]
607 else:
608 return []
610 def _check_prepopulated_fields(self, obj):
611 """Check that `prepopulated_fields` is a dictionary containing allowed
612 field types."""
613 if not isinstance(obj.prepopulated_fields, dict): 613 ↛ 614line 613 didn't jump to line 614, because the condition on line 613 was never true
614 return must_be(
615 "a dictionary", option="prepopulated_fields", obj=obj, id="admin.E026"
616 )
617 else:
618 return list(
619 chain.from_iterable(
620 self._check_prepopulated_fields_key(
621 obj, field_name, "prepopulated_fields"
622 )
623 + self._check_prepopulated_fields_value(
624 obj, val, 'prepopulated_fields["%s"]' % field_name
625 )
626 for field_name, val in obj.prepopulated_fields.items()
627 )
628 )
630 def _check_prepopulated_fields_key(self, obj, field_name, label):
631 """Check a key of `prepopulated_fields` dictionary, i.e. check that it
632 is a name of existing field and the field is one of the allowed types.
633 """
635 try:
636 field = obj.model._meta.get_field(field_name)
637 except FieldDoesNotExist:
638 return refer_to_missing_field(
639 field=field_name, option=label, obj=obj, id="admin.E027"
640 )
641 else:
642 if isinstance(
643 field, (models.DateTimeField, models.ForeignKey, models.ManyToManyField)
644 ):
645 return [
646 checks.Error(
647 "The value of '%s' refers to '%s', which must not be a "
648 "DateTimeField, a ForeignKey, a OneToOneField, or a "
649 "ManyToManyField." % (label, field_name),
650 obj=obj.__class__,
651 id="admin.E028",
652 )
653 ]
654 else:
655 return []
657 def _check_prepopulated_fields_value(self, obj, val, label):
658 """Check a value of `prepopulated_fields` dictionary, i.e. it's an
659 iterable of existing fields."""
661 if not isinstance(val, (list, tuple)):
662 return must_be("a list or tuple", option=label, obj=obj, id="admin.E029")
663 else:
664 return list(
665 chain.from_iterable(
666 self._check_prepopulated_fields_value_item(
667 obj, subfield_name, "%s[%r]" % (label, index)
668 )
669 for index, subfield_name in enumerate(val)
670 )
671 )
673 def _check_prepopulated_fields_value_item(self, obj, field_name, label):
674 """For `prepopulated_fields` equal to {"slug": ("title",)},
675 `field_name` is "title"."""
677 try:
678 obj.model._meta.get_field(field_name)
679 except FieldDoesNotExist:
680 return refer_to_missing_field(
681 field=field_name, option=label, obj=obj, id="admin.E030"
682 )
683 else:
684 return []
686 def _check_ordering(self, obj):
687 """Check that ordering refers to existing fields or is random."""
689 # ordering = None
690 if obj.ordering is None: # The default value is None
691 return []
692 elif not isinstance(obj.ordering, (list, tuple)): 692 ↛ 693line 692 didn't jump to line 693, because the condition on line 692 was never true
693 return must_be(
694 "a list or tuple", option="ordering", obj=obj, id="admin.E031"
695 )
696 else:
697 return list(
698 chain.from_iterable(
699 self._check_ordering_item(obj, field_name, "ordering[%d]" % index)
700 for index, field_name in enumerate(obj.ordering)
701 )
702 )
704 def _check_ordering_item(self, obj, field_name, label):
705 """Check that `ordering` refers to existing fields."""
706 if isinstance(field_name, (Combinable, models.OrderBy)): 706 ↛ 707line 706 didn't jump to line 707, because the condition on line 706 was never true
707 if not isinstance(field_name, models.OrderBy):
708 field_name = field_name.asc()
709 if isinstance(field_name.expression, models.F):
710 field_name = field_name.expression.name
711 else:
712 return []
713 if field_name == "?" and len(obj.ordering) != 1: 713 ↛ 714line 713 didn't jump to line 714, because the condition on line 713 was never true
714 return [
715 checks.Error(
716 "The value of 'ordering' has the random ordering marker '?', "
717 "but contains other fields as well.",
718 hint='Either remove the "?", or remove the other fields.',
719 obj=obj.__class__,
720 id="admin.E032",
721 )
722 ]
723 elif field_name == "?": 723 ↛ 724line 723 didn't jump to line 724, because the condition on line 723 was never true
724 return []
725 elif LOOKUP_SEP in field_name: 725 ↛ 728line 725 didn't jump to line 728, because the condition on line 725 was never true
726 # Skip ordering in the format field1__field2 (FIXME: checking
727 # this format would be nice, but it's a little fiddly).
728 return []
729 else:
730 if field_name.startswith("-"): 730 ↛ 731line 730 didn't jump to line 731, because the condition on line 730 was never true
731 field_name = field_name[1:]
732 if field_name == "pk": 732 ↛ 733line 732 didn't jump to line 733, because the condition on line 732 was never true
733 return []
734 try:
735 obj.model._meta.get_field(field_name)
736 except FieldDoesNotExist:
737 return refer_to_missing_field(
738 field=field_name, option=label, obj=obj, id="admin.E033"
739 )
740 else:
741 return []
743 def _check_readonly_fields(self, obj):
744 """Check that readonly_fields refers to proper attribute or field."""
746 if obj.readonly_fields == ():
747 return []
748 elif not isinstance(obj.readonly_fields, (list, tuple)): 748 ↛ 749line 748 didn't jump to line 749, because the condition on line 748 was never true
749 return must_be(
750 "a list or tuple", option="readonly_fields", obj=obj, id="admin.E034"
751 )
752 else:
753 return list(
754 chain.from_iterable(
755 self._check_readonly_fields_item(
756 obj, field_name, "readonly_fields[%d]" % index
757 )
758 for index, field_name in enumerate(obj.readonly_fields)
759 )
760 )
762 def _check_readonly_fields_item(self, obj, field_name, label):
763 if callable(field_name): 763 ↛ 764line 763 didn't jump to line 764, because the condition on line 763 was never true
764 return []
765 elif hasattr(obj, field_name): 765 ↛ 766line 765 didn't jump to line 766, because the condition on line 765 was never true
766 return []
767 elif hasattr(obj.model, field_name): 767 ↛ 770line 767 didn't jump to line 770, because the condition on line 767 was never false
768 return []
769 else:
770 try:
771 obj.model._meta.get_field(field_name)
772 except FieldDoesNotExist:
773 return [
774 checks.Error(
775 "The value of '%s' is not a callable, an attribute of "
776 "'%s', or an attribute of '%s'."
777 % (
778 label,
779 obj.__class__.__name__,
780 obj.model._meta.label,
781 ),
782 obj=obj.__class__,
783 id="admin.E035",
784 )
785 ]
786 else:
787 return []
790class ModelAdminChecks(BaseModelAdminChecks):
791 def check(self, admin_obj, **kwargs):
792 return [
793 *super().check(admin_obj),
794 *self._check_save_as(admin_obj),
795 *self._check_save_on_top(admin_obj),
796 *self._check_inlines(admin_obj),
797 *self._check_list_display(admin_obj),
798 *self._check_list_display_links(admin_obj),
799 *self._check_list_filter(admin_obj),
800 *self._check_list_select_related(admin_obj),
801 *self._check_list_per_page(admin_obj),
802 *self._check_list_max_show_all(admin_obj),
803 *self._check_list_editable(admin_obj),
804 *self._check_search_fields(admin_obj),
805 *self._check_date_hierarchy(admin_obj),
806 *self._check_action_permission_methods(admin_obj),
807 *self._check_actions_uniqueness(admin_obj),
808 ]
810 def _check_save_as(self, obj):
811 """Check save_as is a boolean."""
813 if not isinstance(obj.save_as, bool): 813 ↛ 814line 813 didn't jump to line 814, because the condition on line 813 was never true
814 return must_be("a boolean", option="save_as", obj=obj, id="admin.E101")
815 else:
816 return []
818 def _check_save_on_top(self, obj):
819 """Check save_on_top is a boolean."""
821 if not isinstance(obj.save_on_top, bool): 821 ↛ 822line 821 didn't jump to line 822, because the condition on line 821 was never true
822 return must_be("a boolean", option="save_on_top", obj=obj, id="admin.E102")
823 else:
824 return []
826 def _check_inlines(self, obj):
827 """Check all inline model admin classes."""
829 if not isinstance(obj.inlines, (list, tuple)): 829 ↛ 830line 829 didn't jump to line 830, because the condition on line 829 was never true
830 return must_be(
831 "a list or tuple", option="inlines", obj=obj, id="admin.E103"
832 )
833 else:
834 return list(
835 chain.from_iterable(
836 self._check_inlines_item(obj, item, "inlines[%d]" % index)
837 for index, item in enumerate(obj.inlines)
838 )
839 )
841 def _check_inlines_item(self, obj, inline, label):
842 """Check one inline model admin."""
843 try:
844 inline_label = inline.__module__ + "." + inline.__name__
845 except AttributeError:
846 return [
847 checks.Error(
848 "'%s' must inherit from 'InlineModelAdmin'." % obj,
849 obj=obj.__class__,
850 id="admin.E104",
851 )
852 ]
854 from django.contrib.admin.options import InlineModelAdmin
856 if not _issubclass(inline, InlineModelAdmin): 856 ↛ 857line 856 didn't jump to line 857, because the condition on line 856 was never true
857 return [
858 checks.Error(
859 "'%s' must inherit from 'InlineModelAdmin'." % inline_label,
860 obj=obj.__class__,
861 id="admin.E104",
862 )
863 ]
864 elif not inline.model: 864 ↛ 865line 864 didn't jump to line 865, because the condition on line 864 was never true
865 return [
866 checks.Error(
867 "'%s' must have a 'model' attribute." % inline_label,
868 obj=obj.__class__,
869 id="admin.E105",
870 )
871 ]
872 elif not _issubclass(inline.model, models.Model): 872 ↛ 873line 872 didn't jump to line 873, because the condition on line 872 was never true
873 return must_be(
874 "a Model", option="%s.model" % inline_label, obj=obj, id="admin.E106"
875 )
876 else:
877 return inline(obj.model, obj.admin_site).check()
879 def _check_list_display(self, obj):
880 """Check that list_display only contains fields or usable attributes."""
882 if not isinstance(obj.list_display, (list, tuple)): 882 ↛ 883line 882 didn't jump to line 883, because the condition on line 882 was never true
883 return must_be(
884 "a list or tuple", option="list_display", obj=obj, id="admin.E107"
885 )
886 else:
887 return list(
888 chain.from_iterable(
889 self._check_list_display_item(obj, item, "list_display[%d]" % index)
890 for index, item in enumerate(obj.list_display)
891 )
892 )
894 def _check_list_display_item(self, obj, item, label):
895 if callable(item): 895 ↛ 896line 895 didn't jump to line 896, because the condition on line 895 was never true
896 return []
897 elif hasattr(obj, item):
898 return []
899 try:
900 field = obj.model._meta.get_field(item)
901 except FieldDoesNotExist:
902 try:
903 field = getattr(obj.model, item)
904 except AttributeError:
905 return [
906 checks.Error(
907 "The value of '%s' refers to '%s', which is not a "
908 "callable, an attribute of '%s', or an attribute or "
909 "method on '%s'."
910 % (
911 label,
912 item,
913 obj.__class__.__name__,
914 obj.model._meta.label,
915 ),
916 obj=obj.__class__,
917 id="admin.E108",
918 )
919 ]
920 if isinstance(field, models.ManyToManyField): 920 ↛ 921line 920 didn't jump to line 921, because the condition on line 920 was never true
921 return [
922 checks.Error(
923 "The value of '%s' must not be a ManyToManyField." % label,
924 obj=obj.__class__,
925 id="admin.E109",
926 )
927 ]
928 return []
930 def _check_list_display_links(self, obj):
931 """Check that list_display_links is a unique subset of list_display."""
932 from django.contrib.admin.options import ModelAdmin
934 if obj.list_display_links is None: 934 ↛ 935line 934 didn't jump to line 935, because the condition on line 934 was never true
935 return []
936 elif not isinstance(obj.list_display_links, (list, tuple)): 936 ↛ 937line 936 didn't jump to line 937, because the condition on line 936 was never true
937 return must_be(
938 "a list, a tuple, or None",
939 option="list_display_links",
940 obj=obj,
941 id="admin.E110",
942 )
943 # Check only if ModelAdmin.get_list_display() isn't overridden.
944 elif obj.get_list_display.__func__ is ModelAdmin.get_list_display: 944 ↛ 953line 944 didn't jump to line 953, because the condition on line 944 was never false
945 return list(
946 chain.from_iterable(
947 self._check_list_display_links_item(
948 obj, field_name, "list_display_links[%d]" % index
949 )
950 for index, field_name in enumerate(obj.list_display_links)
951 )
952 )
953 return []
955 def _check_list_display_links_item(self, obj, field_name, label):
956 if field_name not in obj.list_display: 956 ↛ 957line 956 didn't jump to line 957, because the condition on line 956 was never true
957 return [
958 checks.Error(
959 "The value of '%s' refers to '%s', which is not defined in "
960 "'list_display'." % (label, field_name),
961 obj=obj.__class__,
962 id="admin.E111",
963 )
964 ]
965 else:
966 return []
968 def _check_list_filter(self, obj):
969 if not isinstance(obj.list_filter, (list, tuple)): 969 ↛ 970line 969 didn't jump to line 970, because the condition on line 969 was never true
970 return must_be(
971 "a list or tuple", option="list_filter", obj=obj, id="admin.E112"
972 )
973 else:
974 return list(
975 chain.from_iterable(
976 self._check_list_filter_item(obj, item, "list_filter[%d]" % index)
977 for index, item in enumerate(obj.list_filter)
978 )
979 )
981 def _check_list_filter_item(self, obj, item, label):
982 """
983 Check one item of `list_filter`, i.e. check if it is one of three options:
984 1. 'field' -- a basic field filter, possibly w/ relationships (e.g.
985 'field__rel')
986 2. ('field', SomeFieldListFilter) - a field-based list filter class
987 3. SomeListFilter - a non-field list filter class
988 """
989 from django.contrib.admin import FieldListFilter, ListFilter
991 if callable(item) and not isinstance(item, models.Field):
992 # If item is option 3, it should be a ListFilter...
993 if not _issubclass(item, ListFilter): 993 ↛ 994line 993 didn't jump to line 994, because the condition on line 993 was never true
994 return must_inherit_from(
995 parent="ListFilter", option=label, obj=obj, id="admin.E113"
996 )
997 # ... but not a FieldListFilter.
998 elif issubclass(item, FieldListFilter): 998 ↛ 999line 998 didn't jump to line 999, because the condition on line 998 was never true
999 return [
1000 checks.Error(
1001 "The value of '%s' must not inherit from 'FieldListFilter'."
1002 % label,
1003 obj=obj.__class__,
1004 id="admin.E114",
1005 )
1006 ]
1007 else:
1008 return []
1009 elif isinstance(item, (tuple, list)): 1009 ↛ 1011line 1009 didn't jump to line 1011, because the condition on line 1009 was never true
1010 # item is option #2
1011 field, list_filter_class = item
1012 if not _issubclass(list_filter_class, FieldListFilter):
1013 return must_inherit_from(
1014 parent="FieldListFilter",
1015 option="%s[1]" % label,
1016 obj=obj,
1017 id="admin.E115",
1018 )
1019 else:
1020 return []
1021 else:
1022 # item is option #1
1023 field = item
1025 # Validate the field string
1026 try:
1027 get_fields_from_path(obj.model, field)
1028 except (NotRelationField, FieldDoesNotExist):
1029 return [
1030 checks.Error(
1031 "The value of '%s' refers to '%s', which does not refer to a "
1032 "Field." % (label, field),
1033 obj=obj.__class__,
1034 id="admin.E116",
1035 )
1036 ]
1037 else:
1038 return []
1040 def _check_list_select_related(self, obj):
1041 """Check that list_select_related is a boolean, a list or a tuple."""
1043 if not isinstance(obj.list_select_related, (bool, list, tuple)): 1043 ↛ 1044line 1043 didn't jump to line 1044, because the condition on line 1043 was never true
1044 return must_be(
1045 "a boolean, tuple or list",
1046 option="list_select_related",
1047 obj=obj,
1048 id="admin.E117",
1049 )
1050 else:
1051 return []
1053 def _check_list_per_page(self, obj):
1054 """Check that list_per_page is an integer."""
1056 if not isinstance(obj.list_per_page, int): 1056 ↛ 1057line 1056 didn't jump to line 1057, because the condition on line 1056 was never true
1057 return must_be(
1058 "an integer", option="list_per_page", obj=obj, id="admin.E118"
1059 )
1060 else:
1061 return []
1063 def _check_list_max_show_all(self, obj):
1064 """Check that list_max_show_all is an integer."""
1066 if not isinstance(obj.list_max_show_all, int): 1066 ↛ 1067line 1066 didn't jump to line 1067, because the condition on line 1066 was never true
1067 return must_be(
1068 "an integer", option="list_max_show_all", obj=obj, id="admin.E119"
1069 )
1070 else:
1071 return []
1073 def _check_list_editable(self, obj):
1074 """Check that list_editable is a sequence of editable fields from
1075 list_display without first element."""
1077 if not isinstance(obj.list_editable, (list, tuple)): 1077 ↛ 1078line 1077 didn't jump to line 1078, because the condition on line 1077 was never true
1078 return must_be(
1079 "a list or tuple", option="list_editable", obj=obj, id="admin.E120"
1080 )
1081 else:
1082 return list(
1083 chain.from_iterable(
1084 self._check_list_editable_item(
1085 obj, item, "list_editable[%d]" % index
1086 )
1087 for index, item in enumerate(obj.list_editable)
1088 )
1089 )
1091 def _check_list_editable_item(self, obj, field_name, label):
1092 try:
1093 field = obj.model._meta.get_field(field_name)
1094 except FieldDoesNotExist:
1095 return refer_to_missing_field(
1096 field=field_name, option=label, obj=obj, id="admin.E121"
1097 )
1098 else:
1099 if field_name not in obj.list_display:
1100 return [
1101 checks.Error(
1102 "The value of '%s' refers to '%s', which is not "
1103 "contained in 'list_display'." % (label, field_name),
1104 obj=obj.__class__,
1105 id="admin.E122",
1106 )
1107 ]
1108 elif obj.list_display_links and field_name in obj.list_display_links:
1109 return [
1110 checks.Error(
1111 "The value of '%s' cannot be in both 'list_editable' and "
1112 "'list_display_links'." % field_name,
1113 obj=obj.__class__,
1114 id="admin.E123",
1115 )
1116 ]
1117 # If list_display[0] is in list_editable, check that
1118 # list_display_links is set. See #22792 and #26229 for use cases.
1119 elif (
1120 obj.list_display[0] == field_name
1121 and not obj.list_display_links
1122 and obj.list_display_links is not None
1123 ):
1124 return [
1125 checks.Error(
1126 "The value of '%s' refers to the first field in 'list_display' "
1127 "('%s'), which cannot be used unless 'list_display_links' is "
1128 "set." % (label, obj.list_display[0]),
1129 obj=obj.__class__,
1130 id="admin.E124",
1131 )
1132 ]
1133 elif not field.editable:
1134 return [
1135 checks.Error(
1136 "The value of '%s' refers to '%s', which is not editable "
1137 "through the admin." % (label, field_name),
1138 obj=obj.__class__,
1139 id="admin.E125",
1140 )
1141 ]
1142 else:
1143 return []
1145 def _check_search_fields(self, obj):
1146 """Check search_fields is a sequence."""
1148 if not isinstance(obj.search_fields, (list, tuple)): 1148 ↛ 1149line 1148 didn't jump to line 1149, because the condition on line 1148 was never true
1149 return must_be(
1150 "a list or tuple", option="search_fields", obj=obj, id="admin.E126"
1151 )
1152 else:
1153 return []
1155 def _check_date_hierarchy(self, obj):
1156 """Check that date_hierarchy refers to DateField or DateTimeField."""
1158 if obj.date_hierarchy is None:
1159 return []
1160 else:
1161 try:
1162 field = get_fields_from_path(obj.model, obj.date_hierarchy)[-1]
1163 except (NotRelationField, FieldDoesNotExist):
1164 return [
1165 checks.Error(
1166 "The value of 'date_hierarchy' refers to '%s', which "
1167 "does not refer to a Field." % obj.date_hierarchy,
1168 obj=obj.__class__,
1169 id="admin.E127",
1170 )
1171 ]
1172 else:
1173 if not isinstance(field, (models.DateField, models.DateTimeField)): 1173 ↛ 1174line 1173 didn't jump to line 1174, because the condition on line 1173 was never true
1174 return must_be(
1175 "a DateField or DateTimeField",
1176 option="date_hierarchy",
1177 obj=obj,
1178 id="admin.E128",
1179 )
1180 else:
1181 return []
1183 def _check_action_permission_methods(self, obj):
1184 """
1185 Actions with an allowed_permission attribute require the ModelAdmin to
1186 implement a has_<perm>_permission() method for each permission.
1187 """
1188 actions = obj._get_base_actions()
1189 errors = []
1190 for func, name, _ in actions:
1191 if not hasattr(func, "allowed_permissions"):
1192 continue
1193 for permission in func.allowed_permissions:
1194 method_name = "has_%s_permission" % permission
1195 if not hasattr(obj, method_name): 1195 ↛ 1196line 1195 didn't jump to line 1196, because the condition on line 1195 was never true
1196 errors.append(
1197 checks.Error(
1198 "%s must define a %s() method for the %s action."
1199 % (
1200 obj.__class__.__name__,
1201 method_name,
1202 func.__name__,
1203 ),
1204 obj=obj.__class__,
1205 id="admin.E129",
1206 )
1207 )
1208 return errors
1210 def _check_actions_uniqueness(self, obj):
1211 """Check that every action has a unique __name__."""
1212 errors = []
1213 names = collections.Counter(name for _, name, _ in obj._get_base_actions())
1214 for name, count in names.items():
1215 if count > 1: 1215 ↛ 1216line 1215 didn't jump to line 1216, because the condition on line 1215 was never true
1216 errors.append(
1217 checks.Error(
1218 "__name__ attributes of actions defined in %s must be "
1219 "unique. Name %r is not unique."
1220 % (
1221 obj.__class__.__name__,
1222 name,
1223 ),
1224 obj=obj.__class__,
1225 id="admin.E130",
1226 )
1227 )
1228 return errors
1231class InlineModelAdminChecks(BaseModelAdminChecks):
1232 def check(self, inline_obj, **kwargs):
1233 parent_model = inline_obj.parent_model
1234 return [
1235 *super().check(inline_obj),
1236 *self._check_relation(inline_obj, parent_model),
1237 *self._check_exclude_of_parent_model(inline_obj, parent_model),
1238 *self._check_extra(inline_obj),
1239 *self._check_max_num(inline_obj),
1240 *self._check_min_num(inline_obj),
1241 *self._check_formset(inline_obj),
1242 ]
1244 def _check_exclude_of_parent_model(self, obj, parent_model):
1245 # Do not perform more specific checks if the base checks result in an
1246 # error.
1247 errors = super()._check_exclude(obj)
1248 if errors: 1248 ↛ 1249line 1248 didn't jump to line 1249, because the condition on line 1248 was never true
1249 return []
1251 # Skip if `fk_name` is invalid.
1252 if self._check_relation(obj, parent_model): 1252 ↛ 1253line 1252 didn't jump to line 1253, because the condition on line 1252 was never true
1253 return []
1255 if obj.exclude is None: 1255 ↛ 1258line 1255 didn't jump to line 1258, because the condition on line 1255 was never false
1256 return []
1258 fk = _get_foreign_key(parent_model, obj.model, fk_name=obj.fk_name)
1259 if fk.name in obj.exclude:
1260 return [
1261 checks.Error(
1262 "Cannot exclude the field '%s', because it is the foreign key "
1263 "to the parent model '%s'."
1264 % (
1265 fk.name,
1266 parent_model._meta.label,
1267 ),
1268 obj=obj.__class__,
1269 id="admin.E201",
1270 )
1271 ]
1272 else:
1273 return []
1275 def _check_relation(self, obj, parent_model):
1276 try:
1277 _get_foreign_key(parent_model, obj.model, fk_name=obj.fk_name)
1278 except ValueError as e:
1279 return [checks.Error(e.args[0], obj=obj.__class__, id="admin.E202")]
1280 else:
1281 return []
1283 def _check_extra(self, obj):
1284 """Check that extra is an integer."""
1286 if not isinstance(obj.extra, int): 1286 ↛ 1287line 1286 didn't jump to line 1287, because the condition on line 1286 was never true
1287 return must_be("an integer", option="extra", obj=obj, id="admin.E203")
1288 else:
1289 return []
1291 def _check_max_num(self, obj):
1292 """Check that max_num is an integer."""
1294 if obj.max_num is None: 1294 ↛ 1296line 1294 didn't jump to line 1296, because the condition on line 1294 was never false
1295 return []
1296 elif not isinstance(obj.max_num, int):
1297 return must_be("an integer", option="max_num", obj=obj, id="admin.E204")
1298 else:
1299 return []
1301 def _check_min_num(self, obj):
1302 """Check that min_num is an integer."""
1304 if obj.min_num is None: 1304 ↛ 1306line 1304 didn't jump to line 1306, because the condition on line 1304 was never false
1305 return []
1306 elif not isinstance(obj.min_num, int):
1307 return must_be("an integer", option="min_num", obj=obj, id="admin.E205")
1308 else:
1309 return []
1311 def _check_formset(self, obj):
1312 """Check formset is a subclass of BaseModelFormSet."""
1314 if not _issubclass(obj.formset, BaseModelFormSet): 1314 ↛ 1315line 1314 didn't jump to line 1315, because the condition on line 1314 was never true
1315 return must_inherit_from(
1316 parent="BaseModelFormSet", option="formset", obj=obj, id="admin.E206"
1317 )
1318 else:
1319 return []
1322def must_be(type, option, obj, id):
1323 return [
1324 checks.Error(
1325 "The value of '%s' must be %s." % (option, type),
1326 obj=obj.__class__,
1327 id=id,
1328 ),
1329 ]
1332def must_inherit_from(parent, option, obj, id):
1333 return [
1334 checks.Error(
1335 "The value of '%s' must inherit from '%s'." % (option, parent),
1336 obj=obj.__class__,
1337 id=id,
1338 ),
1339 ]
1342def refer_to_missing_field(field, option, obj, id):
1343 return [
1344 checks.Error(
1345 "The value of '%s' refers to '%s', which is not a field of '%s'."
1346 % (option, field, obj.model._meta.label),
1347 obj=obj.__class__,
1348 id=id,
1349 ),
1350 ]