Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

checks.py 42KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054
  1. import warnings
  2. from itertools import chain
  3. from django.apps import apps
  4. from django.conf import settings
  5. from django.contrib.admin.utils import (
  6. NotRelationField, flatten, get_fields_from_path,
  7. )
  8. from django.core import checks
  9. from django.core.exceptions import FieldDoesNotExist
  10. from django.db import models
  11. from django.db.models.constants import LOOKUP_SEP
  12. from django.db.models.expressions import Combinable, F, OrderBy
  13. from django.forms.models import (
  14. BaseModelForm, BaseModelFormSet, _get_foreign_key,
  15. )
  16. from django.template.engine import Engine
  17. from django.utils.deprecation import RemovedInDjango30Warning
  18. from django.utils.inspect import get_func_args
  19. def check_admin_app(app_configs, **kwargs):
  20. from django.contrib.admin.sites import all_sites
  21. errors = []
  22. for site in all_sites:
  23. errors.extend(site.check(app_configs))
  24. return errors
  25. def check_dependencies(**kwargs):
  26. """
  27. Check that the admin's dependencies are correctly installed.
  28. """
  29. errors = []
  30. # contrib.contenttypes must be installed.
  31. if not apps.is_installed('django.contrib.contenttypes'):
  32. missing_app = checks.Error(
  33. "'django.contrib.contenttypes' must be in INSTALLED_APPS in order "
  34. "to use the admin application.",
  35. id="admin.E401",
  36. )
  37. errors.append(missing_app)
  38. # The auth context processor must be installed if using the default
  39. # authentication backend.
  40. try:
  41. default_template_engine = Engine.get_default()
  42. except Exception:
  43. # Skip this non-critical check:
  44. # 1. if the user has a non-trivial TEMPLATES setting and Django
  45. # can't find a default template engine
  46. # 2. if anything goes wrong while loading template engines, in
  47. # order to avoid raising an exception from a confusing location
  48. # Catching ImproperlyConfigured suffices for 1. but 2. requires
  49. # catching all exceptions.
  50. pass
  51. else:
  52. if ('django.contrib.auth.context_processors.auth'
  53. not in default_template_engine.context_processors and
  54. 'django.contrib.auth.backends.ModelBackend' in settings.AUTHENTICATION_BACKENDS):
  55. missing_template = checks.Error(
  56. "'django.contrib.auth.context_processors.auth' must be in "
  57. "TEMPLATES in order to use the admin application.",
  58. id="admin.E402"
  59. )
  60. errors.append(missing_template)
  61. return errors
  62. class BaseModelAdminChecks:
  63. def check(self, admin_obj, **kwargs):
  64. return [
  65. *self._check_autocomplete_fields(admin_obj),
  66. *self._check_raw_id_fields(admin_obj),
  67. *self._check_fields(admin_obj),
  68. *self._check_fieldsets(admin_obj),
  69. *self._check_exclude(admin_obj),
  70. *self._check_form(admin_obj),
  71. *self._check_filter_vertical(admin_obj),
  72. *self._check_filter_horizontal(admin_obj),
  73. *self._check_radio_fields(admin_obj),
  74. *self._check_prepopulated_fields(admin_obj),
  75. *self._check_view_on_site_url(admin_obj),
  76. *self._check_ordering(admin_obj),
  77. *self._check_readonly_fields(admin_obj),
  78. ]
  79. def _check_autocomplete_fields(self, obj):
  80. """
  81. Check that `autocomplete_fields` is a list or tuple of model fields.
  82. """
  83. if not isinstance(obj.autocomplete_fields, (list, tuple)):
  84. return must_be('a list or tuple', option='autocomplete_fields', obj=obj, id='admin.E036')
  85. else:
  86. return list(chain.from_iterable([
  87. self._check_autocomplete_fields_item(obj, obj.model, field_name, 'autocomplete_fields[%d]' % index)
  88. for index, field_name in enumerate(obj.autocomplete_fields)
  89. ]))
  90. def _check_autocomplete_fields_item(self, obj, model, field_name, label):
  91. """
  92. Check that an item in `autocomplete_fields` is a ForeignKey or a
  93. ManyToManyField and that the item has a related ModelAdmin with
  94. search_fields defined.
  95. """
  96. try:
  97. field = model._meta.get_field(field_name)
  98. except FieldDoesNotExist:
  99. return refer_to_missing_field(field=field_name, option=label, model=model, obj=obj, id='admin.E037')
  100. else:
  101. if not field.many_to_many and not isinstance(field, models.ForeignKey):
  102. return must_be(
  103. 'a foreign key or a many-to-many field',
  104. option=label, obj=obj, id='admin.E038'
  105. )
  106. related_admin = obj.admin_site._registry.get(field.remote_field.model)
  107. if related_admin is None:
  108. return [
  109. checks.Error(
  110. 'An admin for model "%s" has to be registered '
  111. 'to be referenced by %s.autocomplete_fields.' % (
  112. field.remote_field.model.__name__,
  113. type(obj).__name__,
  114. ),
  115. obj=obj.__class__,
  116. id='admin.E039',
  117. )
  118. ]
  119. elif not related_admin.search_fields:
  120. return [
  121. checks.Error(
  122. '%s must define "search_fields", because it\'s '
  123. 'referenced by %s.autocomplete_fields.' % (
  124. related_admin.__class__.__name__,
  125. type(obj).__name__,
  126. ),
  127. obj=obj.__class__,
  128. id='admin.E040',
  129. )
  130. ]
  131. return []
  132. def _check_raw_id_fields(self, obj):
  133. """ Check that `raw_id_fields` only contains field names that are listed
  134. on the model. """
  135. if not isinstance(obj.raw_id_fields, (list, tuple)):
  136. return must_be('a list or tuple', option='raw_id_fields', obj=obj, id='admin.E001')
  137. else:
  138. return list(chain.from_iterable(
  139. self._check_raw_id_fields_item(obj, obj.model, field_name, 'raw_id_fields[%d]' % index)
  140. for index, field_name in enumerate(obj.raw_id_fields)
  141. ))
  142. def _check_raw_id_fields_item(self, obj, model, field_name, label):
  143. """ Check an item of `raw_id_fields`, i.e. check that field named
  144. `field_name` exists in model `model` and is a ForeignKey or a
  145. ManyToManyField. """
  146. try:
  147. field = model._meta.get_field(field_name)
  148. except FieldDoesNotExist:
  149. return refer_to_missing_field(field=field_name, option=label,
  150. model=model, obj=obj, id='admin.E002')
  151. else:
  152. if not field.many_to_many and not isinstance(field, models.ForeignKey):
  153. return must_be('a foreign key or a many-to-many field',
  154. option=label, obj=obj, id='admin.E003')
  155. else:
  156. return []
  157. def _check_fields(self, obj):
  158. """ Check that `fields` only refer to existing fields, doesn't contain
  159. duplicates. Check if at most one of `fields` and `fieldsets` is defined.
  160. """
  161. if obj.fields is None:
  162. return []
  163. elif not isinstance(obj.fields, (list, tuple)):
  164. return must_be('a list or tuple', option='fields', obj=obj, id='admin.E004')
  165. elif obj.fieldsets:
  166. return [
  167. checks.Error(
  168. "Both 'fieldsets' and 'fields' are specified.",
  169. obj=obj.__class__,
  170. id='admin.E005',
  171. )
  172. ]
  173. fields = flatten(obj.fields)
  174. if len(fields) != len(set(fields)):
  175. return [
  176. checks.Error(
  177. "The value of 'fields' contains duplicate field(s).",
  178. obj=obj.__class__,
  179. id='admin.E006',
  180. )
  181. ]
  182. return list(chain.from_iterable(
  183. self._check_field_spec(obj, obj.model, field_name, 'fields')
  184. for field_name in obj.fields
  185. ))
  186. def _check_fieldsets(self, obj):
  187. """ Check that fieldsets is properly formatted and doesn't contain
  188. duplicates. """
  189. if obj.fieldsets is None:
  190. return []
  191. elif not isinstance(obj.fieldsets, (list, tuple)):
  192. return must_be('a list or tuple', option='fieldsets', obj=obj, id='admin.E007')
  193. else:
  194. seen_fields = []
  195. return list(chain.from_iterable(
  196. self._check_fieldsets_item(obj, obj.model, fieldset, 'fieldsets[%d]' % index, seen_fields)
  197. for index, fieldset in enumerate(obj.fieldsets)
  198. ))
  199. def _check_fieldsets_item(self, obj, model, fieldset, label, seen_fields):
  200. """ Check an item of `fieldsets`, i.e. check that this is a pair of a
  201. set name and a dictionary containing "fields" key. """
  202. if not isinstance(fieldset, (list, tuple)):
  203. return must_be('a list or tuple', option=label, obj=obj, id='admin.E008')
  204. elif len(fieldset) != 2:
  205. return must_be('of length 2', option=label, obj=obj, id='admin.E009')
  206. elif not isinstance(fieldset[1], dict):
  207. return must_be('a dictionary', option='%s[1]' % label, obj=obj, id='admin.E010')
  208. elif 'fields' not in fieldset[1]:
  209. return [
  210. checks.Error(
  211. "The value of '%s[1]' must contain the key 'fields'." % label,
  212. obj=obj.__class__,
  213. id='admin.E011',
  214. )
  215. ]
  216. elif not isinstance(fieldset[1]['fields'], (list, tuple)):
  217. return must_be('a list or tuple', option="%s[1]['fields']" % label, obj=obj, id='admin.E008')
  218. seen_fields.extend(flatten(fieldset[1]['fields']))
  219. if len(seen_fields) != len(set(seen_fields)):
  220. return [
  221. checks.Error(
  222. "There are duplicate field(s) in '%s[1]'." % label,
  223. obj=obj.__class__,
  224. id='admin.E012',
  225. )
  226. ]
  227. return list(chain.from_iterable(
  228. self._check_field_spec(obj, model, fieldset_fields, '%s[1]["fields"]' % label)
  229. for fieldset_fields in fieldset[1]['fields']
  230. ))
  231. def _check_field_spec(self, obj, model, fields, label):
  232. """ `fields` should be an item of `fields` or an item of
  233. fieldset[1]['fields'] for any `fieldset` in `fieldsets`. It should be a
  234. field name or a tuple of field names. """
  235. if isinstance(fields, tuple):
  236. return list(chain.from_iterable(
  237. self._check_field_spec_item(obj, model, field_name, "%s[%d]" % (label, index))
  238. for index, field_name in enumerate(fields)
  239. ))
  240. else:
  241. return self._check_field_spec_item(obj, model, fields, label)
  242. def _check_field_spec_item(self, obj, model, field_name, label):
  243. if field_name in obj.readonly_fields:
  244. # Stuff can be put in fields that isn't actually a model field if
  245. # it's in readonly_fields, readonly_fields will handle the
  246. # validation of such things.
  247. return []
  248. else:
  249. try:
  250. field = model._meta.get_field(field_name)
  251. except FieldDoesNotExist:
  252. # If we can't find a field on the model that matches, it could
  253. # be an extra field on the form.
  254. return []
  255. else:
  256. if (isinstance(field, models.ManyToManyField) and
  257. not field.remote_field.through._meta.auto_created):
  258. return [
  259. checks.Error(
  260. "The value of '%s' cannot include the ManyToManyField '%s', "
  261. "because that field manually specifies a relationship model."
  262. % (label, field_name),
  263. obj=obj.__class__,
  264. id='admin.E013',
  265. )
  266. ]
  267. else:
  268. return []
  269. def _check_exclude(self, obj):
  270. """ Check that exclude is a sequence without duplicates. """
  271. if obj.exclude is None: # default value is None
  272. return []
  273. elif not isinstance(obj.exclude, (list, tuple)):
  274. return must_be('a list or tuple', option='exclude', obj=obj, id='admin.E014')
  275. elif len(obj.exclude) > len(set(obj.exclude)):
  276. return [
  277. checks.Error(
  278. "The value of 'exclude' contains duplicate field(s).",
  279. obj=obj.__class__,
  280. id='admin.E015',
  281. )
  282. ]
  283. else:
  284. return []
  285. def _check_form(self, obj):
  286. """ Check that form subclasses BaseModelForm. """
  287. if not issubclass(obj.form, BaseModelForm):
  288. return must_inherit_from(parent='BaseModelForm', option='form',
  289. obj=obj, id='admin.E016')
  290. else:
  291. return []
  292. def _check_filter_vertical(self, obj):
  293. """ Check that filter_vertical is a sequence of field names. """
  294. if not isinstance(obj.filter_vertical, (list, tuple)):
  295. return must_be('a list or tuple', option='filter_vertical', obj=obj, id='admin.E017')
  296. else:
  297. return list(chain.from_iterable(
  298. self._check_filter_item(obj, obj.model, field_name, "filter_vertical[%d]" % index)
  299. for index, field_name in enumerate(obj.filter_vertical)
  300. ))
  301. def _check_filter_horizontal(self, obj):
  302. """ Check that filter_horizontal is a sequence of field names. """
  303. if not isinstance(obj.filter_horizontal, (list, tuple)):
  304. return must_be('a list or tuple', option='filter_horizontal', obj=obj, id='admin.E018')
  305. else:
  306. return list(chain.from_iterable(
  307. self._check_filter_item(obj, obj.model, field_name, "filter_horizontal[%d]" % index)
  308. for index, field_name in enumerate(obj.filter_horizontal)
  309. ))
  310. def _check_filter_item(self, obj, model, field_name, label):
  311. """ Check one item of `filter_vertical` or `filter_horizontal`, i.e.
  312. check that given field exists and is a ManyToManyField. """
  313. try:
  314. field = model._meta.get_field(field_name)
  315. except FieldDoesNotExist:
  316. return refer_to_missing_field(field=field_name, option=label,
  317. model=model, obj=obj, id='admin.E019')
  318. else:
  319. if not field.many_to_many:
  320. return must_be('a many-to-many field', option=label, obj=obj, id='admin.E020')
  321. else:
  322. return []
  323. def _check_radio_fields(self, obj):
  324. """ Check that `radio_fields` is a dictionary. """
  325. if not isinstance(obj.radio_fields, dict):
  326. return must_be('a dictionary', option='radio_fields', obj=obj, id='admin.E021')
  327. else:
  328. return list(chain.from_iterable(
  329. self._check_radio_fields_key(obj, obj.model, field_name, 'radio_fields') +
  330. self._check_radio_fields_value(obj, val, 'radio_fields["%s"]' % field_name)
  331. for field_name, val in obj.radio_fields.items()
  332. ))
  333. def _check_radio_fields_key(self, obj, model, field_name, label):
  334. """ Check that a key of `radio_fields` dictionary is name of existing
  335. field and that the field is a ForeignKey or has `choices` defined. """
  336. try:
  337. field = model._meta.get_field(field_name)
  338. except FieldDoesNotExist:
  339. return refer_to_missing_field(field=field_name, option=label,
  340. model=model, obj=obj, id='admin.E022')
  341. else:
  342. if not (isinstance(field, models.ForeignKey) or field.choices):
  343. return [
  344. checks.Error(
  345. "The value of '%s' refers to '%s', which is not an "
  346. "instance of ForeignKey, and does not have a 'choices' definition." % (
  347. label, field_name
  348. ),
  349. obj=obj.__class__,
  350. id='admin.E023',
  351. )
  352. ]
  353. else:
  354. return []
  355. def _check_radio_fields_value(self, obj, val, label):
  356. """ Check type of a value of `radio_fields` dictionary. """
  357. from django.contrib.admin.options import HORIZONTAL, VERTICAL
  358. if val not in (HORIZONTAL, VERTICAL):
  359. return [
  360. checks.Error(
  361. "The value of '%s' must be either admin.HORIZONTAL or admin.VERTICAL." % label,
  362. obj=obj.__class__,
  363. id='admin.E024',
  364. )
  365. ]
  366. else:
  367. return []
  368. def _check_view_on_site_url(self, obj):
  369. if not callable(obj.view_on_site) and not isinstance(obj.view_on_site, bool):
  370. return [
  371. checks.Error(
  372. "The value of 'view_on_site' must be a callable or a boolean value.",
  373. obj=obj.__class__,
  374. id='admin.E025',
  375. )
  376. ]
  377. else:
  378. return []
  379. def _check_prepopulated_fields(self, obj):
  380. """ Check that `prepopulated_fields` is a dictionary containing allowed
  381. field types. """
  382. if not isinstance(obj.prepopulated_fields, dict):
  383. return must_be('a dictionary', option='prepopulated_fields', obj=obj, id='admin.E026')
  384. else:
  385. return list(chain.from_iterable(
  386. self._check_prepopulated_fields_key(obj, obj.model, field_name, 'prepopulated_fields') +
  387. self._check_prepopulated_fields_value(obj, obj.model, val, 'prepopulated_fields["%s"]' % field_name)
  388. for field_name, val in obj.prepopulated_fields.items()
  389. ))
  390. def _check_prepopulated_fields_key(self, obj, model, field_name, label):
  391. """ Check a key of `prepopulated_fields` dictionary, i.e. check that it
  392. is a name of existing field and the field is one of the allowed types.
  393. """
  394. try:
  395. field = model._meta.get_field(field_name)
  396. except FieldDoesNotExist:
  397. return refer_to_missing_field(field=field_name, option=label,
  398. model=model, obj=obj, id='admin.E027')
  399. else:
  400. if isinstance(field, (models.DateTimeField, models.ForeignKey, models.ManyToManyField)):
  401. return [
  402. checks.Error(
  403. "The value of '%s' refers to '%s', which must not be a DateTimeField, "
  404. "a ForeignKey, a OneToOneField, or a ManyToManyField." % (label, field_name),
  405. obj=obj.__class__,
  406. id='admin.E028',
  407. )
  408. ]
  409. else:
  410. return []
  411. def _check_prepopulated_fields_value(self, obj, model, val, label):
  412. """ Check a value of `prepopulated_fields` dictionary, i.e. it's an
  413. iterable of existing fields. """
  414. if not isinstance(val, (list, tuple)):
  415. return must_be('a list or tuple', option=label, obj=obj, id='admin.E029')
  416. else:
  417. return list(chain.from_iterable(
  418. self._check_prepopulated_fields_value_item(obj, model, subfield_name, "%s[%r]" % (label, index))
  419. for index, subfield_name in enumerate(val)
  420. ))
  421. def _check_prepopulated_fields_value_item(self, obj, model, field_name, label):
  422. """ For `prepopulated_fields` equal to {"slug": ("title",)},
  423. `field_name` is "title". """
  424. try:
  425. model._meta.get_field(field_name)
  426. except FieldDoesNotExist:
  427. return refer_to_missing_field(field=field_name, option=label, model=model, obj=obj, id='admin.E030')
  428. else:
  429. return []
  430. def _check_ordering(self, obj):
  431. """ Check that ordering refers to existing fields or is random. """
  432. # ordering = None
  433. if obj.ordering is None: # The default value is None
  434. return []
  435. elif not isinstance(obj.ordering, (list, tuple)):
  436. return must_be('a list or tuple', option='ordering', obj=obj, id='admin.E031')
  437. else:
  438. return list(chain.from_iterable(
  439. self._check_ordering_item(obj, obj.model, field_name, 'ordering[%d]' % index)
  440. for index, field_name in enumerate(obj.ordering)
  441. ))
  442. def _check_ordering_item(self, obj, model, field_name, label):
  443. """ Check that `ordering` refers to existing fields. """
  444. if isinstance(field_name, (Combinable, OrderBy)):
  445. if not isinstance(field_name, OrderBy):
  446. field_name = field_name.asc()
  447. if isinstance(field_name.expression, F):
  448. field_name = field_name.expression.name
  449. else:
  450. return []
  451. if field_name == '?' and len(obj.ordering) != 1:
  452. return [
  453. checks.Error(
  454. "The value of 'ordering' has the random ordering marker '?', "
  455. "but contains other fields as well.",
  456. hint='Either remove the "?", or remove the other fields.',
  457. obj=obj.__class__,
  458. id='admin.E032',
  459. )
  460. ]
  461. elif field_name == '?':
  462. return []
  463. elif LOOKUP_SEP in field_name:
  464. # Skip ordering in the format field1__field2 (FIXME: checking
  465. # this format would be nice, but it's a little fiddly).
  466. return []
  467. else:
  468. if field_name.startswith('-'):
  469. field_name = field_name[1:]
  470. if field_name == 'pk':
  471. return []
  472. try:
  473. model._meta.get_field(field_name)
  474. except FieldDoesNotExist:
  475. return refer_to_missing_field(field=field_name, option=label, model=model, obj=obj, id='admin.E033')
  476. else:
  477. return []
  478. def _check_readonly_fields(self, obj):
  479. """ Check that readonly_fields refers to proper attribute or field. """
  480. if obj.readonly_fields == ():
  481. return []
  482. elif not isinstance(obj.readonly_fields, (list, tuple)):
  483. return must_be('a list or tuple', option='readonly_fields', obj=obj, id='admin.E034')
  484. else:
  485. return list(chain.from_iterable(
  486. self._check_readonly_fields_item(obj, obj.model, field_name, "readonly_fields[%d]" % index)
  487. for index, field_name in enumerate(obj.readonly_fields)
  488. ))
  489. def _check_readonly_fields_item(self, obj, model, field_name, label):
  490. if callable(field_name):
  491. return []
  492. elif hasattr(obj, field_name):
  493. return []
  494. elif hasattr(model, field_name):
  495. return []
  496. else:
  497. try:
  498. model._meta.get_field(field_name)
  499. except FieldDoesNotExist:
  500. return [
  501. checks.Error(
  502. "The value of '%s' is not a callable, an attribute of '%s', or an attribute of '%s.%s'." % (
  503. label, obj.__class__.__name__, model._meta.app_label, model._meta.object_name
  504. ),
  505. obj=obj.__class__,
  506. id='admin.E035',
  507. )
  508. ]
  509. else:
  510. return []
  511. class ModelAdminChecks(BaseModelAdminChecks):
  512. def check(self, admin_obj, **kwargs):
  513. return [
  514. *super().check(admin_obj),
  515. *self._check_save_as(admin_obj),
  516. *self._check_save_on_top(admin_obj),
  517. *self._check_inlines(admin_obj),
  518. *self._check_list_display(admin_obj),
  519. *self._check_list_display_links(admin_obj),
  520. *self._check_list_filter(admin_obj),
  521. *self._check_list_select_related(admin_obj),
  522. *self._check_list_per_page(admin_obj),
  523. *self._check_list_max_show_all(admin_obj),
  524. *self._check_list_editable(admin_obj),
  525. *self._check_search_fields(admin_obj),
  526. *self._check_date_hierarchy(admin_obj),
  527. *self._check_action_permission_methods(admin_obj),
  528. ]
  529. def _check_save_as(self, obj):
  530. """ Check save_as is a boolean. """
  531. if not isinstance(obj.save_as, bool):
  532. return must_be('a boolean', option='save_as',
  533. obj=obj, id='admin.E101')
  534. else:
  535. return []
  536. def _check_save_on_top(self, obj):
  537. """ Check save_on_top is a boolean. """
  538. if not isinstance(obj.save_on_top, bool):
  539. return must_be('a boolean', option='save_on_top',
  540. obj=obj, id='admin.E102')
  541. else:
  542. return []
  543. def _check_inlines(self, obj):
  544. """ Check all inline model admin classes. """
  545. if not isinstance(obj.inlines, (list, tuple)):
  546. return must_be('a list or tuple', option='inlines', obj=obj, id='admin.E103')
  547. else:
  548. return list(chain.from_iterable(
  549. self._check_inlines_item(obj, obj.model, item, "inlines[%d]" % index)
  550. for index, item in enumerate(obj.inlines)
  551. ))
  552. def _check_inlines_item(self, obj, model, inline, label):
  553. """ Check one inline model admin. """
  554. inline_label = inline.__module__ + '.' + inline.__name__
  555. from django.contrib.admin.options import InlineModelAdmin
  556. if not issubclass(inline, InlineModelAdmin):
  557. return [
  558. checks.Error(
  559. "'%s' must inherit from 'InlineModelAdmin'." % inline_label,
  560. obj=obj.__class__,
  561. id='admin.E104',
  562. )
  563. ]
  564. elif not inline.model:
  565. return [
  566. checks.Error(
  567. "'%s' must have a 'model' attribute." % inline_label,
  568. obj=obj.__class__,
  569. id='admin.E105',
  570. )
  571. ]
  572. elif not issubclass(inline.model, models.Model):
  573. return must_be('a Model', option='%s.model' % inline_label, obj=obj, id='admin.E106')
  574. else:
  575. return inline(model, obj.admin_site).check()
  576. def _check_list_display(self, obj):
  577. """ Check that list_display only contains fields or usable attributes.
  578. """
  579. if not isinstance(obj.list_display, (list, tuple)):
  580. return must_be('a list or tuple', option='list_display', obj=obj, id='admin.E107')
  581. else:
  582. return list(chain.from_iterable(
  583. self._check_list_display_item(obj, obj.model, item, "list_display[%d]" % index)
  584. for index, item in enumerate(obj.list_display)
  585. ))
  586. def _check_list_display_item(self, obj, model, item, label):
  587. if callable(item):
  588. return []
  589. elif hasattr(obj, item):
  590. return []
  591. elif hasattr(model, item):
  592. try:
  593. field = model._meta.get_field(item)
  594. except FieldDoesNotExist:
  595. return []
  596. else:
  597. if isinstance(field, models.ManyToManyField):
  598. return [
  599. checks.Error(
  600. "The value of '%s' must not be a ManyToManyField." % label,
  601. obj=obj.__class__,
  602. id='admin.E109',
  603. )
  604. ]
  605. return []
  606. else:
  607. return [
  608. checks.Error(
  609. "The value of '%s' refers to '%s', which is not a callable, "
  610. "an attribute of '%s', or an attribute or method on '%s.%s'." % (
  611. label, item, obj.__class__.__name__,
  612. model._meta.app_label, model._meta.object_name,
  613. ),
  614. obj=obj.__class__,
  615. id='admin.E108',
  616. )
  617. ]
  618. def _check_list_display_links(self, obj):
  619. """ Check that list_display_links is a unique subset of list_display.
  620. """
  621. from django.contrib.admin.options import ModelAdmin
  622. if obj.list_display_links is None:
  623. return []
  624. elif not isinstance(obj.list_display_links, (list, tuple)):
  625. return must_be('a list, a tuple, or None', option='list_display_links', obj=obj, id='admin.E110')
  626. # Check only if ModelAdmin.get_list_display() isn't overridden.
  627. elif obj.get_list_display.__func__ is ModelAdmin.get_list_display:
  628. return list(chain.from_iterable(
  629. self._check_list_display_links_item(obj, field_name, "list_display_links[%d]" % index)
  630. for index, field_name in enumerate(obj.list_display_links)
  631. ))
  632. return []
  633. def _check_list_display_links_item(self, obj, field_name, label):
  634. if field_name not in obj.list_display:
  635. return [
  636. checks.Error(
  637. "The value of '%s' refers to '%s', which is not defined in 'list_display'." % (
  638. label, field_name
  639. ),
  640. obj=obj.__class__,
  641. id='admin.E111',
  642. )
  643. ]
  644. else:
  645. return []
  646. def _check_list_filter(self, obj):
  647. if not isinstance(obj.list_filter, (list, tuple)):
  648. return must_be('a list or tuple', option='list_filter', obj=obj, id='admin.E112')
  649. else:
  650. return list(chain.from_iterable(
  651. self._check_list_filter_item(obj, obj.model, item, "list_filter[%d]" % index)
  652. for index, item in enumerate(obj.list_filter)
  653. ))
  654. def _check_list_filter_item(self, obj, model, item, label):
  655. """
  656. Check one item of `list_filter`, i.e. check if it is one of three options:
  657. 1. 'field' -- a basic field filter, possibly w/ relationships (e.g.
  658. 'field__rel')
  659. 2. ('field', SomeFieldListFilter) - a field-based list filter class
  660. 3. SomeListFilter - a non-field list filter class
  661. """
  662. from django.contrib.admin import ListFilter, FieldListFilter
  663. if callable(item) and not isinstance(item, models.Field):
  664. # If item is option 3, it should be a ListFilter...
  665. if not issubclass(item, ListFilter):
  666. return must_inherit_from(parent='ListFilter', option=label,
  667. obj=obj, id='admin.E113')
  668. # ... but not a FieldListFilter.
  669. elif issubclass(item, FieldListFilter):
  670. return [
  671. checks.Error(
  672. "The value of '%s' must not inherit from 'FieldListFilter'." % label,
  673. obj=obj.__class__,
  674. id='admin.E114',
  675. )
  676. ]
  677. else:
  678. return []
  679. elif isinstance(item, (tuple, list)):
  680. # item is option #2
  681. field, list_filter_class = item
  682. if not issubclass(list_filter_class, FieldListFilter):
  683. return must_inherit_from(parent='FieldListFilter', option='%s[1]' % label, obj=obj, id='admin.E115')
  684. else:
  685. return []
  686. else:
  687. # item is option #1
  688. field = item
  689. # Validate the field string
  690. try:
  691. get_fields_from_path(model, field)
  692. except (NotRelationField, FieldDoesNotExist):
  693. return [
  694. checks.Error(
  695. "The value of '%s' refers to '%s', which does not refer to a Field." % (label, field),
  696. obj=obj.__class__,
  697. id='admin.E116',
  698. )
  699. ]
  700. else:
  701. return []
  702. def _check_list_select_related(self, obj):
  703. """ Check that list_select_related is a boolean, a list or a tuple. """
  704. if not isinstance(obj.list_select_related, (bool, list, tuple)):
  705. return must_be('a boolean, tuple or list', option='list_select_related', obj=obj, id='admin.E117')
  706. else:
  707. return []
  708. def _check_list_per_page(self, obj):
  709. """ Check that list_per_page is an integer. """
  710. if not isinstance(obj.list_per_page, int):
  711. return must_be('an integer', option='list_per_page', obj=obj, id='admin.E118')
  712. else:
  713. return []
  714. def _check_list_max_show_all(self, obj):
  715. """ Check that list_max_show_all is an integer. """
  716. if not isinstance(obj.list_max_show_all, int):
  717. return must_be('an integer', option='list_max_show_all', obj=obj, id='admin.E119')
  718. else:
  719. return []
  720. def _check_list_editable(self, obj):
  721. """ Check that list_editable is a sequence of editable fields from
  722. list_display without first element. """
  723. if not isinstance(obj.list_editable, (list, tuple)):
  724. return must_be('a list or tuple', option='list_editable', obj=obj, id='admin.E120')
  725. else:
  726. return list(chain.from_iterable(
  727. self._check_list_editable_item(obj, obj.model, item, "list_editable[%d]" % index)
  728. for index, item in enumerate(obj.list_editable)
  729. ))
  730. def _check_list_editable_item(self, obj, model, field_name, label):
  731. try:
  732. field = model._meta.get_field(field_name)
  733. except FieldDoesNotExist:
  734. return refer_to_missing_field(field=field_name, option=label, model=model, obj=obj, id='admin.E121')
  735. else:
  736. if field_name not in obj.list_display:
  737. return [
  738. checks.Error(
  739. "The value of '%s' refers to '%s', which is not "
  740. "contained in 'list_display'." % (label, field_name),
  741. obj=obj.__class__,
  742. id='admin.E122',
  743. )
  744. ]
  745. elif obj.list_display_links and field_name in obj.list_display_links:
  746. return [
  747. checks.Error(
  748. "The value of '%s' cannot be in both 'list_editable' and 'list_display_links'." % field_name,
  749. obj=obj.__class__,
  750. id='admin.E123',
  751. )
  752. ]
  753. # If list_display[0] is in list_editable, check that
  754. # list_display_links is set. See #22792 and #26229 for use cases.
  755. elif (obj.list_display[0] == field_name and not obj.list_display_links and
  756. obj.list_display_links is not None):
  757. return [
  758. checks.Error(
  759. "The value of '%s' refers to the first field in 'list_display' ('%s'), "
  760. "which cannot be used unless 'list_display_links' is set." % (
  761. label, obj.list_display[0]
  762. ),
  763. obj=obj.__class__,
  764. id='admin.E124',
  765. )
  766. ]
  767. elif not field.editable:
  768. return [
  769. checks.Error(
  770. "The value of '%s' refers to '%s', which is not editable through the admin." % (
  771. label, field_name
  772. ),
  773. obj=obj.__class__,
  774. id='admin.E125',
  775. )
  776. ]
  777. else:
  778. return []
  779. def _check_search_fields(self, obj):
  780. """ Check search_fields is a sequence. """
  781. if not isinstance(obj.search_fields, (list, tuple)):
  782. return must_be('a list or tuple', option='search_fields', obj=obj, id='admin.E126')
  783. else:
  784. return []
  785. def _check_date_hierarchy(self, obj):
  786. """ Check that date_hierarchy refers to DateField or DateTimeField. """
  787. if obj.date_hierarchy is None:
  788. return []
  789. else:
  790. try:
  791. field = get_fields_from_path(obj.model, obj.date_hierarchy)[-1]
  792. except (NotRelationField, FieldDoesNotExist):
  793. return [
  794. checks.Error(
  795. "The value of 'date_hierarchy' refers to '%s', which "
  796. "does not refer to a Field." % obj.date_hierarchy,
  797. obj=obj.__class__,
  798. id='admin.E127',
  799. )
  800. ]
  801. else:
  802. if not isinstance(field, (models.DateField, models.DateTimeField)):
  803. return must_be('a DateField or DateTimeField', option='date_hierarchy', obj=obj, id='admin.E128')
  804. else:
  805. return []
  806. def _check_action_permission_methods(self, obj):
  807. """
  808. Actions with an allowed_permission attribute require the ModelAdmin to
  809. implement a has_<perm>_permission() method for each permission.
  810. """
  811. actions = obj._get_base_actions()
  812. errors = []
  813. for func, name, _ in actions:
  814. if not hasattr(func, 'allowed_permissions'):
  815. continue
  816. for permission in func.allowed_permissions:
  817. method_name = 'has_%s_permission' % permission
  818. if not hasattr(obj, method_name):
  819. errors.append(
  820. checks.Error(
  821. '%s must define a %s() method for the %s action.' % (
  822. obj.__class__.__name__,
  823. method_name,
  824. func.__name__,
  825. ),
  826. obj=obj.__class__,
  827. id='admin.E129',
  828. )
  829. )
  830. return errors
  831. class InlineModelAdminChecks(BaseModelAdminChecks):
  832. def check(self, inline_obj, **kwargs):
  833. self._check_has_add_permission(inline_obj)
  834. parent_model = inline_obj.parent_model
  835. return [
  836. *super().check(inline_obj),
  837. *self._check_relation(inline_obj, parent_model),
  838. *self._check_exclude_of_parent_model(inline_obj, parent_model),
  839. *self._check_extra(inline_obj),
  840. *self._check_max_num(inline_obj),
  841. *self._check_min_num(inline_obj),
  842. *self._check_formset(inline_obj),
  843. ]
  844. def _check_exclude_of_parent_model(self, obj, parent_model):
  845. # Do not perform more specific checks if the base checks result in an
  846. # error.
  847. errors = super()._check_exclude(obj)
  848. if errors:
  849. return []
  850. # Skip if `fk_name` is invalid.
  851. if self._check_relation(obj, parent_model):
  852. return []
  853. if obj.exclude is None:
  854. return []
  855. fk = _get_foreign_key(parent_model, obj.model, fk_name=obj.fk_name)
  856. if fk.name in obj.exclude:
  857. return [
  858. checks.Error(
  859. "Cannot exclude the field '%s', because it is the foreign key "
  860. "to the parent model '%s.%s'." % (
  861. fk.name, parent_model._meta.app_label, parent_model._meta.object_name
  862. ),
  863. obj=obj.__class__,
  864. id='admin.E201',
  865. )
  866. ]
  867. else:
  868. return []
  869. def _check_relation(self, obj, parent_model):
  870. try:
  871. _get_foreign_key(parent_model, obj.model, fk_name=obj.fk_name)
  872. except ValueError as e:
  873. return [checks.Error(e.args[0], obj=obj.__class__, id='admin.E202')]
  874. else:
  875. return []
  876. def _check_extra(self, obj):
  877. """ Check that extra is an integer. """
  878. if not isinstance(obj.extra, int):
  879. return must_be('an integer', option='extra', obj=obj, id='admin.E203')
  880. else:
  881. return []
  882. def _check_max_num(self, obj):
  883. """ Check that max_num is an integer. """
  884. if obj.max_num is None:
  885. return []
  886. elif not isinstance(obj.max_num, int):
  887. return must_be('an integer', option='max_num', obj=obj, id='admin.E204')
  888. else:
  889. return []
  890. def _check_min_num(self, obj):
  891. """ Check that min_num is an integer. """
  892. if obj.min_num is None:
  893. return []
  894. elif not isinstance(obj.min_num, int):
  895. return must_be('an integer', option='min_num', obj=obj, id='admin.E205')
  896. else:
  897. return []
  898. def _check_formset(self, obj):
  899. """ Check formset is a subclass of BaseModelFormSet. """
  900. if not issubclass(obj.formset, BaseModelFormSet):
  901. return must_inherit_from(parent='BaseModelFormSet', option='formset', obj=obj, id='admin.E206')
  902. else:
  903. return []
  904. def _check_has_add_permission(self, obj):
  905. cls = obj.__class__
  906. try:
  907. func = cls.has_add_permission
  908. except AttributeError:
  909. pass
  910. else:
  911. args = get_func_args(func)
  912. if 'obj' not in args:
  913. warnings.warn(
  914. "Update %s.has_add_permission() to accept a positional "
  915. "`obj` argument." % cls.__name__, RemovedInDjango30Warning
  916. )
  917. def must_be(type, option, obj, id):
  918. return [
  919. checks.Error(
  920. "The value of '%s' must be %s." % (option, type),
  921. obj=obj.__class__,
  922. id=id,
  923. ),
  924. ]
  925. def must_inherit_from(parent, option, obj, id):
  926. return [
  927. checks.Error(
  928. "The value of '%s' must inherit from '%s'." % (option, parent),
  929. obj=obj.__class__,
  930. id=id,
  931. ),
  932. ]
  933. def refer_to_missing_field(field, option, model, obj, id):
  934. return [
  935. checks.Error(
  936. "The value of '%s' refers to '%s', which is not an attribute of '%s.%s'." % (
  937. option, field, model._meta.app_label, model._meta.object_name
  938. ),
  939. obj=obj.__class__,
  940. id=id,
  941. ),
  942. ]