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 44KB

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