checks.py 44 KB

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