Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
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 49KB

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