checks.py 38 KB

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