checks.py 45 KB

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