checks.py 38 KB

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