Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/contrib/admin/checks.py: 59%

456 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1import collections 

2from itertools import chain 

3 

4from django.apps import apps 

5from django.conf import settings 

6from django.contrib.admin.utils import NotRelationField, flatten, get_fields_from_path 

7from django.core import checks 

8from django.core.exceptions import FieldDoesNotExist 

9from django.db import models 

10from django.db.models.constants import LOOKUP_SEP 

11from django.db.models.expressions import Combinable 

12from django.forms.models import BaseModelForm, BaseModelFormSet, _get_foreign_key 

13from django.template import engines 

14from django.template.backends.django import DjangoTemplates 

15from django.utils.module_loading import import_string 

16 

17 

18def _issubclass(cls, classinfo): 

19 """ 

20 issubclass() variant that doesn't raise an exception if cls isn't a 

21 class. 

22 """ 

23 try: 

24 return issubclass(cls, classinfo) 

25 except TypeError: 

26 return False 

27 

28 

29def _contains_subclass(class_path, candidate_paths): 

30 """ 

31 Return whether or not a dotted class path (or a subclass of that class) is 

32 found in a list of candidate paths. 

33 """ 

34 cls = import_string(class_path) 

35 for path in candidate_paths: 35 ↛ 43line 35 didn't jump to line 43, because the loop on line 35 didn't complete

36 try: 

37 candidate_cls = import_string(path) 

38 except ImportError: 

39 # ImportErrors are raised elsewhere. 

40 continue 

41 if _issubclass(candidate_cls, cls): 

42 return True 

43 return False 

44 

45 

46def check_admin_app(app_configs, **kwargs): 

47 from django.contrib.admin.sites import all_sites 

48 

49 errors = [] 

50 for site in all_sites: 

51 errors.extend(site.check(app_configs)) 

52 return errors 

53 

54 

55def check_dependencies(**kwargs): 

56 """ 

57 Check that the admin's dependencies are correctly installed. 

58 """ 

59 from django.contrib.admin.sites import all_sites 

60 

61 if not apps.is_installed("django.contrib.admin"): 61 ↛ 62line 61 didn't jump to line 62, because the condition on line 61 was never true

62 return [] 

63 errors = [] 

64 app_dependencies = ( 

65 ("django.contrib.contenttypes", 401), 

66 ("django.contrib.auth", 405), 

67 ("django.contrib.messages", 406), 

68 ) 

69 for app_name, error_code in app_dependencies: 

70 if not apps.is_installed(app_name): 70 ↛ 71line 70 didn't jump to line 71, because the condition on line 70 was never true

71 errors.append( 

72 checks.Error( 

73 "'%s' must be in INSTALLED_APPS in order to use the admin " 

74 "application." % app_name, 

75 id="admin.E%d" % error_code, 

76 ) 

77 ) 

78 for engine in engines.all(): 78 ↛ 83line 78 didn't jump to line 83, because the loop on line 78 didn't complete

79 if isinstance(engine, DjangoTemplates): 79 ↛ 78line 79 didn't jump to line 78, because the condition on line 79 was never false

80 django_templates_instance = engine.engine 

81 break 

82 else: 

83 django_templates_instance = None 

84 if not django_templates_instance: 84 ↛ 85line 84 didn't jump to line 85, because the condition on line 84 was never true

85 errors.append( 

86 checks.Error( 

87 "A 'django.template.backends.django.DjangoTemplates' instance " 

88 "must be configured in TEMPLATES in order to use the admin " 

89 "application.", 

90 id="admin.E403", 

91 ) 

92 ) 

93 else: 

94 if ( 94 ↛ 102line 94 didn't jump to line 102

95 "django.contrib.auth.context_processors.auth" 

96 not in django_templates_instance.context_processors 

97 and _contains_subclass( 

98 "django.contrib.auth.backends.ModelBackend", 

99 settings.AUTHENTICATION_BACKENDS, 

100 ) 

101 ): 

102 errors.append( 

103 checks.Error( 

104 "'django.contrib.auth.context_processors.auth' must be " 

105 "enabled in DjangoTemplates (TEMPLATES) if using the default " 

106 "auth backend in order to use the admin application.", 

107 id="admin.E402", 

108 ) 

109 ) 

110 if ( 110 ↛ 114line 110 didn't jump to line 114

111 "django.contrib.messages.context_processors.messages" 

112 not in django_templates_instance.context_processors 

113 ): 

114 errors.append( 

115 checks.Error( 

116 "'django.contrib.messages.context_processors.messages' must " 

117 "be enabled in DjangoTemplates (TEMPLATES) in order to use " 

118 "the admin application.", 

119 id="admin.E404", 

120 ) 

121 ) 

122 sidebar_enabled = any(site.enable_nav_sidebar for site in all_sites) 122 ↛ exitline 122 didn't finish the generator expression on line 122

123 if ( 123 ↛ 128line 123 didn't jump to line 128

124 sidebar_enabled 

125 and "django.template.context_processors.request" 

126 not in django_templates_instance.context_processors 

127 ): 

128 errors.append( 

129 checks.Warning( 

130 "'django.template.context_processors.request' must be enabled " 

131 "in DjangoTemplates (TEMPLATES) in order to use the admin " 

132 "navigation sidebar.", 

133 id="admin.W411", 

134 ) 

135 ) 

136 

137 if not _contains_subclass( 137 ↛ 140line 137 didn't jump to line 140, because the condition on line 137 was never true

138 "django.contrib.auth.middleware.AuthenticationMiddleware", settings.MIDDLEWARE 

139 ): 

140 errors.append( 

141 checks.Error( 

142 "'django.contrib.auth.middleware.AuthenticationMiddleware' must " 

143 "be in MIDDLEWARE in order to use the admin application.", 

144 id="admin.E408", 

145 ) 

146 ) 

147 if not _contains_subclass( 147 ↛ 150line 147 didn't jump to line 150, because the condition on line 147 was never true

148 "django.contrib.messages.middleware.MessageMiddleware", settings.MIDDLEWARE 

149 ): 

150 errors.append( 

151 checks.Error( 

152 "'django.contrib.messages.middleware.MessageMiddleware' must " 

153 "be in MIDDLEWARE in order to use the admin application.", 

154 id="admin.E409", 

155 ) 

156 ) 

157 if not _contains_subclass( 157 ↛ 160line 157 didn't jump to line 160, because the condition on line 157 was never true

158 "django.contrib.sessions.middleware.SessionMiddleware", settings.MIDDLEWARE 

159 ): 

160 errors.append( 

161 checks.Error( 

162 "'django.contrib.sessions.middleware.SessionMiddleware' must " 

163 "be in MIDDLEWARE in order to use the admin application.", 

164 hint=( 

165 "Insert " 

166 "'django.contrib.sessions.middleware.SessionMiddleware' " 

167 "before " 

168 "'django.contrib.auth.middleware.AuthenticationMiddleware'." 

169 ), 

170 id="admin.E410", 

171 ) 

172 ) 

173 return errors 

174 

175 

176class BaseModelAdminChecks: 

177 def check(self, admin_obj, **kwargs): 

178 return [ 

179 *self._check_autocomplete_fields(admin_obj), 

180 *self._check_raw_id_fields(admin_obj), 

181 *self._check_fields(admin_obj), 

182 *self._check_fieldsets(admin_obj), 

183 *self._check_exclude(admin_obj), 

184 *self._check_form(admin_obj), 

185 *self._check_filter_vertical(admin_obj), 

186 *self._check_filter_horizontal(admin_obj), 

187 *self._check_radio_fields(admin_obj), 

188 *self._check_prepopulated_fields(admin_obj), 

189 *self._check_view_on_site_url(admin_obj), 

190 *self._check_ordering(admin_obj), 

191 *self._check_readonly_fields(admin_obj), 

192 ] 

193 

194 def _check_autocomplete_fields(self, obj): 

195 """ 

196 Check that `autocomplete_fields` is a list or tuple of model fields. 

197 """ 

198 if not isinstance(obj.autocomplete_fields, (list, tuple)): 198 ↛ 199line 198 didn't jump to line 199, because the condition on line 198 was never true

199 return must_be( 

200 "a list or tuple", 

201 option="autocomplete_fields", 

202 obj=obj, 

203 id="admin.E036", 

204 ) 

205 else: 

206 return list( 

207 chain.from_iterable( 

208 [ 

209 self._check_autocomplete_fields_item( 

210 obj, field_name, "autocomplete_fields[%d]" % index 

211 ) 

212 for index, field_name in enumerate(obj.autocomplete_fields) 

213 ] 

214 ) 

215 ) 

216 

217 def _check_autocomplete_fields_item(self, obj, field_name, label): 

218 """ 

219 Check that an item in `autocomplete_fields` is a ForeignKey or a 

220 ManyToManyField and that the item has a related ModelAdmin with 

221 search_fields defined. 

222 """ 

223 try: 

224 field = obj.model._meta.get_field(field_name) 

225 except FieldDoesNotExist: 

226 return refer_to_missing_field( 

227 field=field_name, option=label, obj=obj, id="admin.E037" 

228 ) 

229 else: 

230 if not field.many_to_many and not isinstance(field, models.ForeignKey): 230 ↛ 231line 230 didn't jump to line 231, because the condition on line 230 was never true

231 return must_be( 

232 "a foreign key or a many-to-many field", 

233 option=label, 

234 obj=obj, 

235 id="admin.E038", 

236 ) 

237 related_admin = obj.admin_site._registry.get(field.remote_field.model) 

238 if related_admin is None: 238 ↛ 239line 238 didn't jump to line 239, because the condition on line 238 was never true

239 return [ 

240 checks.Error( 

241 'An admin for model "%s" has to be registered ' 

242 "to be referenced by %s.autocomplete_fields." 

243 % ( 

244 field.remote_field.model.__name__, 

245 type(obj).__name__, 

246 ), 

247 obj=obj.__class__, 

248 id="admin.E039", 

249 ) 

250 ] 

251 elif not related_admin.search_fields: 251 ↛ 252line 251 didn't jump to line 252, because the condition on line 251 was never true

252 return [ 

253 checks.Error( 

254 '%s must define "search_fields", because it\'s ' 

255 "referenced by %s.autocomplete_fields." 

256 % ( 

257 related_admin.__class__.__name__, 

258 type(obj).__name__, 

259 ), 

260 obj=obj.__class__, 

261 id="admin.E040", 

262 ) 

263 ] 

264 return [] 

265 

266 def _check_raw_id_fields(self, obj): 

267 """Check that `raw_id_fields` only contains field names that are listed 

268 on the model.""" 

269 

270 if not isinstance(obj.raw_id_fields, (list, tuple)): 270 ↛ 271line 270 didn't jump to line 271, because the condition on line 270 was never true

271 return must_be( 

272 "a list or tuple", option="raw_id_fields", obj=obj, id="admin.E001" 

273 ) 

274 else: 

275 return list( 

276 chain.from_iterable( 

277 self._check_raw_id_fields_item( 

278 obj, field_name, "raw_id_fields[%d]" % index 

279 ) 

280 for index, field_name in enumerate(obj.raw_id_fields) 

281 ) 

282 ) 

283 

284 def _check_raw_id_fields_item(self, obj, field_name, label): 

285 """Check an item of `raw_id_fields`, i.e. check that field named 

286 `field_name` exists in model `model` and is a ForeignKey or a 

287 ManyToManyField.""" 

288 

289 try: 

290 field = obj.model._meta.get_field(field_name) 

291 except FieldDoesNotExist: 

292 return refer_to_missing_field( 

293 field=field_name, option=label, obj=obj, id="admin.E002" 

294 ) 

295 else: 

296 # Using attname is not supported. 

297 if field.name != field_name: 297 ↛ 298line 297 didn't jump to line 298, because the condition on line 297 was never true

298 return refer_to_missing_field( 

299 field=field_name, 

300 option=label, 

301 obj=obj, 

302 id="admin.E002", 

303 ) 

304 if not field.many_to_many and not isinstance(field, models.ForeignKey): 304 ↛ 305line 304 didn't jump to line 305, because the condition on line 304 was never true

305 return must_be( 

306 "a foreign key or a many-to-many field", 

307 option=label, 

308 obj=obj, 

309 id="admin.E003", 

310 ) 

311 else: 

312 return [] 

313 

314 def _check_fields(self, obj): 

315 """Check that `fields` only refer to existing fields, doesn't contain 

316 duplicates. Check if at most one of `fields` and `fieldsets` is defined. 

317 """ 

318 

319 if obj.fields is None: 319 ↛ 321line 319 didn't jump to line 321, because the condition on line 319 was never false

320 return [] 

321 elif not isinstance(obj.fields, (list, tuple)): 

322 return must_be("a list or tuple", option="fields", obj=obj, id="admin.E004") 

323 elif obj.fieldsets: 

324 return [ 

325 checks.Error( 

326 "Both 'fieldsets' and 'fields' are specified.", 

327 obj=obj.__class__, 

328 id="admin.E005", 

329 ) 

330 ] 

331 fields = flatten(obj.fields) 

332 if len(fields) != len(set(fields)): 

333 return [ 

334 checks.Error( 

335 "The value of 'fields' contains duplicate field(s).", 

336 obj=obj.__class__, 

337 id="admin.E006", 

338 ) 

339 ] 

340 

341 return list( 

342 chain.from_iterable( 

343 self._check_field_spec(obj, field_name, "fields") 

344 for field_name in obj.fields 

345 ) 

346 ) 

347 

348 def _check_fieldsets(self, obj): 

349 """Check that fieldsets is properly formatted and doesn't contain 

350 duplicates.""" 

351 

352 if obj.fieldsets is None: 

353 return [] 

354 elif not isinstance(obj.fieldsets, (list, tuple)): 354 ↛ 355line 354 didn't jump to line 355, because the condition on line 354 was never true

355 return must_be( 

356 "a list or tuple", option="fieldsets", obj=obj, id="admin.E007" 

357 ) 

358 else: 

359 seen_fields = [] 

360 return list( 

361 chain.from_iterable( 

362 self._check_fieldsets_item( 

363 obj, fieldset, "fieldsets[%d]" % index, seen_fields 

364 ) 

365 for index, fieldset in enumerate(obj.fieldsets) 

366 ) 

367 ) 

368 

369 def _check_fieldsets_item(self, obj, fieldset, label, seen_fields): 

370 """Check an item of `fieldsets`, i.e. check that this is a pair of a 

371 set name and a dictionary containing "fields" key.""" 

372 

373 if not isinstance(fieldset, (list, tuple)): 373 ↛ 374line 373 didn't jump to line 374, because the condition on line 373 was never true

374 return must_be("a list or tuple", option=label, obj=obj, id="admin.E008") 

375 elif len(fieldset) != 2: 375 ↛ 376line 375 didn't jump to line 376, because the condition on line 375 was never true

376 return must_be("of length 2", option=label, obj=obj, id="admin.E009") 

377 elif not isinstance(fieldset[1], dict): 377 ↛ 378line 377 didn't jump to line 378, because the condition on line 377 was never true

378 return must_be( 

379 "a dictionary", option="%s[1]" % label, obj=obj, id="admin.E010" 

380 ) 

381 elif "fields" not in fieldset[1]: 381 ↛ 382line 381 didn't jump to line 382, because the condition on line 381 was never true

382 return [ 

383 checks.Error( 

384 "The value of '%s[1]' must contain the key 'fields'." % label, 

385 obj=obj.__class__, 

386 id="admin.E011", 

387 ) 

388 ] 

389 elif not isinstance(fieldset[1]["fields"], (list, tuple)): 389 ↛ 390line 389 didn't jump to line 390, because the condition on line 389 was never true

390 return must_be( 

391 "a list or tuple", 

392 option="%s[1]['fields']" % label, 

393 obj=obj, 

394 id="admin.E008", 

395 ) 

396 

397 seen_fields.extend(flatten(fieldset[1]["fields"])) 

398 if len(seen_fields) != len(set(seen_fields)): 398 ↛ 399line 398 didn't jump to line 399, because the condition on line 398 was never true

399 return [ 

400 checks.Error( 

401 "There are duplicate field(s) in '%s[1]'." % label, 

402 obj=obj.__class__, 

403 id="admin.E012", 

404 ) 

405 ] 

406 return list( 

407 chain.from_iterable( 

408 self._check_field_spec(obj, fieldset_fields, '%s[1]["fields"]' % label) 

409 for fieldset_fields in fieldset[1]["fields"] 

410 ) 

411 ) 

412 

413 def _check_field_spec(self, obj, fields, label): 

414 """`fields` should be an item of `fields` or an item of 

415 fieldset[1]['fields'] for any `fieldset` in `fieldsets`. It should be a 

416 field name or a tuple of field names.""" 

417 

418 if isinstance(fields, tuple): 418 ↛ 419line 418 didn't jump to line 419, because the condition on line 418 was never true

419 return list( 

420 chain.from_iterable( 

421 self._check_field_spec_item( 

422 obj, field_name, "%s[%d]" % (label, index) 

423 ) 

424 for index, field_name in enumerate(fields) 

425 ) 

426 ) 

427 else: 

428 return self._check_field_spec_item(obj, fields, label) 

429 

430 def _check_field_spec_item(self, obj, field_name, label): 

431 if field_name in obj.readonly_fields: 

432 # Stuff can be put in fields that isn't actually a model field if 

433 # it's in readonly_fields, readonly_fields will handle the 

434 # validation of such things. 

435 return [] 

436 else: 

437 try: 

438 field = obj.model._meta.get_field(field_name) 

439 except FieldDoesNotExist: 

440 # If we can't find a field on the model that matches, it could 

441 # be an extra field on the form. 

442 return [] 

443 else: 

444 if ( 444 ↛ 448line 444 didn't jump to line 448

445 isinstance(field, models.ManyToManyField) 

446 and not field.remote_field.through._meta.auto_created 

447 ): 

448 return [ 

449 checks.Error( 

450 "The value of '%s' cannot include the ManyToManyField " 

451 "'%s', because that field manually specifies a " 

452 "relationship model." % (label, field_name), 

453 obj=obj.__class__, 

454 id="admin.E013", 

455 ) 

456 ] 

457 else: 

458 return [] 

459 

460 def _check_exclude(self, obj): 

461 """Check that exclude is a sequence without duplicates.""" 

462 

463 if obj.exclude is None: # default value is None 463 ↛ 465line 463 didn't jump to line 465, because the condition on line 463 was never false

464 return [] 

465 elif not isinstance(obj.exclude, (list, tuple)): 

466 return must_be( 

467 "a list or tuple", option="exclude", obj=obj, id="admin.E014" 

468 ) 

469 elif len(obj.exclude) > len(set(obj.exclude)): 

470 return [ 

471 checks.Error( 

472 "The value of 'exclude' contains duplicate field(s).", 

473 obj=obj.__class__, 

474 id="admin.E015", 

475 ) 

476 ] 

477 else: 

478 return [] 

479 

480 def _check_form(self, obj): 

481 """Check that form subclasses BaseModelForm.""" 

482 if not _issubclass(obj.form, BaseModelForm): 482 ↛ 483line 482 didn't jump to line 483, because the condition on line 482 was never true

483 return must_inherit_from( 

484 parent="BaseModelForm", option="form", obj=obj, id="admin.E016" 

485 ) 

486 else: 

487 return [] 

488 

489 def _check_filter_vertical(self, obj): 

490 """Check that filter_vertical is a sequence of field names.""" 

491 if not isinstance(obj.filter_vertical, (list, tuple)): 491 ↛ 492line 491 didn't jump to line 492, because the condition on line 491 was never true

492 return must_be( 

493 "a list or tuple", option="filter_vertical", obj=obj, id="admin.E017" 

494 ) 

495 else: 

496 return list( 

497 chain.from_iterable( 

498 self._check_filter_item( 

499 obj, field_name, "filter_vertical[%d]" % index 

500 ) 

501 for index, field_name in enumerate(obj.filter_vertical) 

502 ) 

503 ) 

504 

505 def _check_filter_horizontal(self, obj): 

506 """Check that filter_horizontal is a sequence of field names.""" 

507 if not isinstance(obj.filter_horizontal, (list, tuple)): 507 ↛ 508line 507 didn't jump to line 508, because the condition on line 507 was never true

508 return must_be( 

509 "a list or tuple", option="filter_horizontal", obj=obj, id="admin.E018" 

510 ) 

511 else: 

512 return list( 

513 chain.from_iterable( 

514 self._check_filter_item( 

515 obj, field_name, "filter_horizontal[%d]" % index 

516 ) 

517 for index, field_name in enumerate(obj.filter_horizontal) 

518 ) 

519 ) 

520 

521 def _check_filter_item(self, obj, field_name, label): 

522 """Check one item of `filter_vertical` or `filter_horizontal`, i.e. 

523 check that given field exists and is a ManyToManyField.""" 

524 

525 try: 

526 field = obj.model._meta.get_field(field_name) 

527 except FieldDoesNotExist: 

528 return refer_to_missing_field( 

529 field=field_name, option=label, obj=obj, id="admin.E019" 

530 ) 

531 else: 

532 if not field.many_to_many: 532 ↛ 533line 532 didn't jump to line 533, because the condition on line 532 was never true

533 return must_be( 

534 "a many-to-many field", option=label, obj=obj, id="admin.E020" 

535 ) 

536 else: 

537 return [] 

538 

539 def _check_radio_fields(self, obj): 

540 """Check that `radio_fields` is a dictionary.""" 

541 if not isinstance(obj.radio_fields, dict): 541 ↛ 542line 541 didn't jump to line 542, because the condition on line 541 was never true

542 return must_be( 

543 "a dictionary", option="radio_fields", obj=obj, id="admin.E021" 

544 ) 

545 else: 

546 return list( 

547 chain.from_iterable( 

548 self._check_radio_fields_key(obj, field_name, "radio_fields") 

549 + self._check_radio_fields_value( 

550 obj, val, 'radio_fields["%s"]' % field_name 

551 ) 

552 for field_name, val in obj.radio_fields.items() 

553 ) 

554 ) 

555 

556 def _check_radio_fields_key(self, obj, field_name, label): 

557 """Check that a key of `radio_fields` dictionary is name of existing 

558 field and that the field is a ForeignKey or has `choices` defined.""" 

559 

560 try: 

561 field = obj.model._meta.get_field(field_name) 

562 except FieldDoesNotExist: 

563 return refer_to_missing_field( 

564 field=field_name, option=label, obj=obj, id="admin.E022" 

565 ) 

566 else: 

567 if not (isinstance(field, models.ForeignKey) or field.choices): 

568 return [ 

569 checks.Error( 

570 "The value of '%s' refers to '%s', which is not an " 

571 "instance of ForeignKey, and does not have a 'choices' " 

572 "definition." % (label, field_name), 

573 obj=obj.__class__, 

574 id="admin.E023", 

575 ) 

576 ] 

577 else: 

578 return [] 

579 

580 def _check_radio_fields_value(self, obj, val, label): 

581 """Check type of a value of `radio_fields` dictionary.""" 

582 

583 from django.contrib.admin.options import HORIZONTAL, VERTICAL 

584 

585 if val not in (HORIZONTAL, VERTICAL): 

586 return [ 

587 checks.Error( 

588 "The value of '%s' must be either admin.HORIZONTAL or " 

589 "admin.VERTICAL." % label, 

590 obj=obj.__class__, 

591 id="admin.E024", 

592 ) 

593 ] 

594 else: 

595 return [] 

596 

597 def _check_view_on_site_url(self, obj): 

598 if not callable(obj.view_on_site) and not isinstance(obj.view_on_site, bool): 598 ↛ 599line 598 didn't jump to line 599, because the condition on line 598 was never true

599 return [ 

600 checks.Error( 

601 "The value of 'view_on_site' must be a callable or a boolean " 

602 "value.", 

603 obj=obj.__class__, 

604 id="admin.E025", 

605 ) 

606 ] 

607 else: 

608 return [] 

609 

610 def _check_prepopulated_fields(self, obj): 

611 """Check that `prepopulated_fields` is a dictionary containing allowed 

612 field types.""" 

613 if not isinstance(obj.prepopulated_fields, dict): 613 ↛ 614line 613 didn't jump to line 614, because the condition on line 613 was never true

614 return must_be( 

615 "a dictionary", option="prepopulated_fields", obj=obj, id="admin.E026" 

616 ) 

617 else: 

618 return list( 

619 chain.from_iterable( 

620 self._check_prepopulated_fields_key( 

621 obj, field_name, "prepopulated_fields" 

622 ) 

623 + self._check_prepopulated_fields_value( 

624 obj, val, 'prepopulated_fields["%s"]' % field_name 

625 ) 

626 for field_name, val in obj.prepopulated_fields.items() 

627 ) 

628 ) 

629 

630 def _check_prepopulated_fields_key(self, obj, field_name, label): 

631 """Check a key of `prepopulated_fields` dictionary, i.e. check that it 

632 is a name of existing field and the field is one of the allowed types. 

633 """ 

634 

635 try: 

636 field = obj.model._meta.get_field(field_name) 

637 except FieldDoesNotExist: 

638 return refer_to_missing_field( 

639 field=field_name, option=label, obj=obj, id="admin.E027" 

640 ) 

641 else: 

642 if isinstance( 

643 field, (models.DateTimeField, models.ForeignKey, models.ManyToManyField) 

644 ): 

645 return [ 

646 checks.Error( 

647 "The value of '%s' refers to '%s', which must not be a " 

648 "DateTimeField, a ForeignKey, a OneToOneField, or a " 

649 "ManyToManyField." % (label, field_name), 

650 obj=obj.__class__, 

651 id="admin.E028", 

652 ) 

653 ] 

654 else: 

655 return [] 

656 

657 def _check_prepopulated_fields_value(self, obj, val, label): 

658 """Check a value of `prepopulated_fields` dictionary, i.e. it's an 

659 iterable of existing fields.""" 

660 

661 if not isinstance(val, (list, tuple)): 

662 return must_be("a list or tuple", option=label, obj=obj, id="admin.E029") 

663 else: 

664 return list( 

665 chain.from_iterable( 

666 self._check_prepopulated_fields_value_item( 

667 obj, subfield_name, "%s[%r]" % (label, index) 

668 ) 

669 for index, subfield_name in enumerate(val) 

670 ) 

671 ) 

672 

673 def _check_prepopulated_fields_value_item(self, obj, field_name, label): 

674 """For `prepopulated_fields` equal to {"slug": ("title",)}, 

675 `field_name` is "title".""" 

676 

677 try: 

678 obj.model._meta.get_field(field_name) 

679 except FieldDoesNotExist: 

680 return refer_to_missing_field( 

681 field=field_name, option=label, obj=obj, id="admin.E030" 

682 ) 

683 else: 

684 return [] 

685 

686 def _check_ordering(self, obj): 

687 """Check that ordering refers to existing fields or is random.""" 

688 

689 # ordering = None 

690 if obj.ordering is None: # The default value is None 

691 return [] 

692 elif not isinstance(obj.ordering, (list, tuple)): 692 ↛ 693line 692 didn't jump to line 693, because the condition on line 692 was never true

693 return must_be( 

694 "a list or tuple", option="ordering", obj=obj, id="admin.E031" 

695 ) 

696 else: 

697 return list( 

698 chain.from_iterable( 

699 self._check_ordering_item(obj, field_name, "ordering[%d]" % index) 

700 for index, field_name in enumerate(obj.ordering) 

701 ) 

702 ) 

703 

704 def _check_ordering_item(self, obj, field_name, label): 

705 """Check that `ordering` refers to existing fields.""" 

706 if isinstance(field_name, (Combinable, models.OrderBy)): 706 ↛ 707line 706 didn't jump to line 707, because the condition on line 706 was never true

707 if not isinstance(field_name, models.OrderBy): 

708 field_name = field_name.asc() 

709 if isinstance(field_name.expression, models.F): 

710 field_name = field_name.expression.name 

711 else: 

712 return [] 

713 if field_name == "?" and len(obj.ordering) != 1: 713 ↛ 714line 713 didn't jump to line 714, because the condition on line 713 was never true

714 return [ 

715 checks.Error( 

716 "The value of 'ordering' has the random ordering marker '?', " 

717 "but contains other fields as well.", 

718 hint='Either remove the "?", or remove the other fields.', 

719 obj=obj.__class__, 

720 id="admin.E032", 

721 ) 

722 ] 

723 elif field_name == "?": 723 ↛ 724line 723 didn't jump to line 724, because the condition on line 723 was never true

724 return [] 

725 elif LOOKUP_SEP in field_name: 725 ↛ 728line 725 didn't jump to line 728, because the condition on line 725 was never true

726 # Skip ordering in the format field1__field2 (FIXME: checking 

727 # this format would be nice, but it's a little fiddly). 

728 return [] 

729 else: 

730 if field_name.startswith("-"): 730 ↛ 731line 730 didn't jump to line 731, because the condition on line 730 was never true

731 field_name = field_name[1:] 

732 if field_name == "pk": 732 ↛ 733line 732 didn't jump to line 733, because the condition on line 732 was never true

733 return [] 

734 try: 

735 obj.model._meta.get_field(field_name) 

736 except FieldDoesNotExist: 

737 return refer_to_missing_field( 

738 field=field_name, option=label, obj=obj, id="admin.E033" 

739 ) 

740 else: 

741 return [] 

742 

743 def _check_readonly_fields(self, obj): 

744 """Check that readonly_fields refers to proper attribute or field.""" 

745 

746 if obj.readonly_fields == (): 

747 return [] 

748 elif not isinstance(obj.readonly_fields, (list, tuple)): 748 ↛ 749line 748 didn't jump to line 749, because the condition on line 748 was never true

749 return must_be( 

750 "a list or tuple", option="readonly_fields", obj=obj, id="admin.E034" 

751 ) 

752 else: 

753 return list( 

754 chain.from_iterable( 

755 self._check_readonly_fields_item( 

756 obj, field_name, "readonly_fields[%d]" % index 

757 ) 

758 for index, field_name in enumerate(obj.readonly_fields) 

759 ) 

760 ) 

761 

762 def _check_readonly_fields_item(self, obj, field_name, label): 

763 if callable(field_name): 763 ↛ 764line 763 didn't jump to line 764, because the condition on line 763 was never true

764 return [] 

765 elif hasattr(obj, field_name): 765 ↛ 766line 765 didn't jump to line 766, because the condition on line 765 was never true

766 return [] 

767 elif hasattr(obj.model, field_name): 767 ↛ 770line 767 didn't jump to line 770, because the condition on line 767 was never false

768 return [] 

769 else: 

770 try: 

771 obj.model._meta.get_field(field_name) 

772 except FieldDoesNotExist: 

773 return [ 

774 checks.Error( 

775 "The value of '%s' is not a callable, an attribute of " 

776 "'%s', or an attribute of '%s'." 

777 % ( 

778 label, 

779 obj.__class__.__name__, 

780 obj.model._meta.label, 

781 ), 

782 obj=obj.__class__, 

783 id="admin.E035", 

784 ) 

785 ] 

786 else: 

787 return [] 

788 

789 

790class ModelAdminChecks(BaseModelAdminChecks): 

791 def check(self, admin_obj, **kwargs): 

792 return [ 

793 *super().check(admin_obj), 

794 *self._check_save_as(admin_obj), 

795 *self._check_save_on_top(admin_obj), 

796 *self._check_inlines(admin_obj), 

797 *self._check_list_display(admin_obj), 

798 *self._check_list_display_links(admin_obj), 

799 *self._check_list_filter(admin_obj), 

800 *self._check_list_select_related(admin_obj), 

801 *self._check_list_per_page(admin_obj), 

802 *self._check_list_max_show_all(admin_obj), 

803 *self._check_list_editable(admin_obj), 

804 *self._check_search_fields(admin_obj), 

805 *self._check_date_hierarchy(admin_obj), 

806 *self._check_action_permission_methods(admin_obj), 

807 *self._check_actions_uniqueness(admin_obj), 

808 ] 

809 

810 def _check_save_as(self, obj): 

811 """Check save_as is a boolean.""" 

812 

813 if not isinstance(obj.save_as, bool): 813 ↛ 814line 813 didn't jump to line 814, because the condition on line 813 was never true

814 return must_be("a boolean", option="save_as", obj=obj, id="admin.E101") 

815 else: 

816 return [] 

817 

818 def _check_save_on_top(self, obj): 

819 """Check save_on_top is a boolean.""" 

820 

821 if not isinstance(obj.save_on_top, bool): 821 ↛ 822line 821 didn't jump to line 822, because the condition on line 821 was never true

822 return must_be("a boolean", option="save_on_top", obj=obj, id="admin.E102") 

823 else: 

824 return [] 

825 

826 def _check_inlines(self, obj): 

827 """Check all inline model admin classes.""" 

828 

829 if not isinstance(obj.inlines, (list, tuple)): 829 ↛ 830line 829 didn't jump to line 830, because the condition on line 829 was never true

830 return must_be( 

831 "a list or tuple", option="inlines", obj=obj, id="admin.E103" 

832 ) 

833 else: 

834 return list( 

835 chain.from_iterable( 

836 self._check_inlines_item(obj, item, "inlines[%d]" % index) 

837 for index, item in enumerate(obj.inlines) 

838 ) 

839 ) 

840 

841 def _check_inlines_item(self, obj, inline, label): 

842 """Check one inline model admin.""" 

843 try: 

844 inline_label = inline.__module__ + "." + inline.__name__ 

845 except AttributeError: 

846 return [ 

847 checks.Error( 

848 "'%s' must inherit from 'InlineModelAdmin'." % obj, 

849 obj=obj.__class__, 

850 id="admin.E104", 

851 ) 

852 ] 

853 

854 from django.contrib.admin.options import InlineModelAdmin 

855 

856 if not _issubclass(inline, InlineModelAdmin): 856 ↛ 857line 856 didn't jump to line 857, because the condition on line 856 was never true

857 return [ 

858 checks.Error( 

859 "'%s' must inherit from 'InlineModelAdmin'." % inline_label, 

860 obj=obj.__class__, 

861 id="admin.E104", 

862 ) 

863 ] 

864 elif not inline.model: 864 ↛ 865line 864 didn't jump to line 865, because the condition on line 864 was never true

865 return [ 

866 checks.Error( 

867 "'%s' must have a 'model' attribute." % inline_label, 

868 obj=obj.__class__, 

869 id="admin.E105", 

870 ) 

871 ] 

872 elif not _issubclass(inline.model, models.Model): 872 ↛ 873line 872 didn't jump to line 873, because the condition on line 872 was never true

873 return must_be( 

874 "a Model", option="%s.model" % inline_label, obj=obj, id="admin.E106" 

875 ) 

876 else: 

877 return inline(obj.model, obj.admin_site).check() 

878 

879 def _check_list_display(self, obj): 

880 """Check that list_display only contains fields or usable attributes.""" 

881 

882 if not isinstance(obj.list_display, (list, tuple)): 882 ↛ 883line 882 didn't jump to line 883, because the condition on line 882 was never true

883 return must_be( 

884 "a list or tuple", option="list_display", obj=obj, id="admin.E107" 

885 ) 

886 else: 

887 return list( 

888 chain.from_iterable( 

889 self._check_list_display_item(obj, item, "list_display[%d]" % index) 

890 for index, item in enumerate(obj.list_display) 

891 ) 

892 ) 

893 

894 def _check_list_display_item(self, obj, item, label): 

895 if callable(item): 895 ↛ 896line 895 didn't jump to line 896, because the condition on line 895 was never true

896 return [] 

897 elif hasattr(obj, item): 

898 return [] 

899 try: 

900 field = obj.model._meta.get_field(item) 

901 except FieldDoesNotExist: 

902 try: 

903 field = getattr(obj.model, item) 

904 except AttributeError: 

905 return [ 

906 checks.Error( 

907 "The value of '%s' refers to '%s', which is not a " 

908 "callable, an attribute of '%s', or an attribute or " 

909 "method on '%s'." 

910 % ( 

911 label, 

912 item, 

913 obj.__class__.__name__, 

914 obj.model._meta.label, 

915 ), 

916 obj=obj.__class__, 

917 id="admin.E108", 

918 ) 

919 ] 

920 if isinstance(field, models.ManyToManyField): 920 ↛ 921line 920 didn't jump to line 921, because the condition on line 920 was never true

921 return [ 

922 checks.Error( 

923 "The value of '%s' must not be a ManyToManyField." % label, 

924 obj=obj.__class__, 

925 id="admin.E109", 

926 ) 

927 ] 

928 return [] 

929 

930 def _check_list_display_links(self, obj): 

931 """Check that list_display_links is a unique subset of list_display.""" 

932 from django.contrib.admin.options import ModelAdmin 

933 

934 if obj.list_display_links is None: 934 ↛ 935line 934 didn't jump to line 935, because the condition on line 934 was never true

935 return [] 

936 elif not isinstance(obj.list_display_links, (list, tuple)): 936 ↛ 937line 936 didn't jump to line 937, because the condition on line 936 was never true

937 return must_be( 

938 "a list, a tuple, or None", 

939 option="list_display_links", 

940 obj=obj, 

941 id="admin.E110", 

942 ) 

943 # Check only if ModelAdmin.get_list_display() isn't overridden. 

944 elif obj.get_list_display.__func__ is ModelAdmin.get_list_display: 944 ↛ 953line 944 didn't jump to line 953, because the condition on line 944 was never false

945 return list( 

946 chain.from_iterable( 

947 self._check_list_display_links_item( 

948 obj, field_name, "list_display_links[%d]" % index 

949 ) 

950 for index, field_name in enumerate(obj.list_display_links) 

951 ) 

952 ) 

953 return [] 

954 

955 def _check_list_display_links_item(self, obj, field_name, label): 

956 if field_name not in obj.list_display: 956 ↛ 957line 956 didn't jump to line 957, because the condition on line 956 was never true

957 return [ 

958 checks.Error( 

959 "The value of '%s' refers to '%s', which is not defined in " 

960 "'list_display'." % (label, field_name), 

961 obj=obj.__class__, 

962 id="admin.E111", 

963 ) 

964 ] 

965 else: 

966 return [] 

967 

968 def _check_list_filter(self, obj): 

969 if not isinstance(obj.list_filter, (list, tuple)): 969 ↛ 970line 969 didn't jump to line 970, because the condition on line 969 was never true

970 return must_be( 

971 "a list or tuple", option="list_filter", obj=obj, id="admin.E112" 

972 ) 

973 else: 

974 return list( 

975 chain.from_iterable( 

976 self._check_list_filter_item(obj, item, "list_filter[%d]" % index) 

977 for index, item in enumerate(obj.list_filter) 

978 ) 

979 ) 

980 

981 def _check_list_filter_item(self, obj, item, label): 

982 """ 

983 Check one item of `list_filter`, i.e. check if it is one of three options: 

984 1. 'field' -- a basic field filter, possibly w/ relationships (e.g. 

985 'field__rel') 

986 2. ('field', SomeFieldListFilter) - a field-based list filter class 

987 3. SomeListFilter - a non-field list filter class 

988 """ 

989 from django.contrib.admin import FieldListFilter, ListFilter 

990 

991 if callable(item) and not isinstance(item, models.Field): 

992 # If item is option 3, it should be a ListFilter... 

993 if not _issubclass(item, ListFilter): 993 ↛ 994line 993 didn't jump to line 994, because the condition on line 993 was never true

994 return must_inherit_from( 

995 parent="ListFilter", option=label, obj=obj, id="admin.E113" 

996 ) 

997 # ... but not a FieldListFilter. 

998 elif issubclass(item, FieldListFilter): 998 ↛ 999line 998 didn't jump to line 999, because the condition on line 998 was never true

999 return [ 

1000 checks.Error( 

1001 "The value of '%s' must not inherit from 'FieldListFilter'." 

1002 % label, 

1003 obj=obj.__class__, 

1004 id="admin.E114", 

1005 ) 

1006 ] 

1007 else: 

1008 return [] 

1009 elif isinstance(item, (tuple, list)): 1009 ↛ 1011line 1009 didn't jump to line 1011, because the condition on line 1009 was never true

1010 # item is option #2 

1011 field, list_filter_class = item 

1012 if not _issubclass(list_filter_class, FieldListFilter): 

1013 return must_inherit_from( 

1014 parent="FieldListFilter", 

1015 option="%s[1]" % label, 

1016 obj=obj, 

1017 id="admin.E115", 

1018 ) 

1019 else: 

1020 return [] 

1021 else: 

1022 # item is option #1 

1023 field = item 

1024 

1025 # Validate the field string 

1026 try: 

1027 get_fields_from_path(obj.model, field) 

1028 except (NotRelationField, FieldDoesNotExist): 

1029 return [ 

1030 checks.Error( 

1031 "The value of '%s' refers to '%s', which does not refer to a " 

1032 "Field." % (label, field), 

1033 obj=obj.__class__, 

1034 id="admin.E116", 

1035 ) 

1036 ] 

1037 else: 

1038 return [] 

1039 

1040 def _check_list_select_related(self, obj): 

1041 """Check that list_select_related is a boolean, a list or a tuple.""" 

1042 

1043 if not isinstance(obj.list_select_related, (bool, list, tuple)): 1043 ↛ 1044line 1043 didn't jump to line 1044, because the condition on line 1043 was never true

1044 return must_be( 

1045 "a boolean, tuple or list", 

1046 option="list_select_related", 

1047 obj=obj, 

1048 id="admin.E117", 

1049 ) 

1050 else: 

1051 return [] 

1052 

1053 def _check_list_per_page(self, obj): 

1054 """Check that list_per_page is an integer.""" 

1055 

1056 if not isinstance(obj.list_per_page, int): 1056 ↛ 1057line 1056 didn't jump to line 1057, because the condition on line 1056 was never true

1057 return must_be( 

1058 "an integer", option="list_per_page", obj=obj, id="admin.E118" 

1059 ) 

1060 else: 

1061 return [] 

1062 

1063 def _check_list_max_show_all(self, obj): 

1064 """Check that list_max_show_all is an integer.""" 

1065 

1066 if not isinstance(obj.list_max_show_all, int): 1066 ↛ 1067line 1066 didn't jump to line 1067, because the condition on line 1066 was never true

1067 return must_be( 

1068 "an integer", option="list_max_show_all", obj=obj, id="admin.E119" 

1069 ) 

1070 else: 

1071 return [] 

1072 

1073 def _check_list_editable(self, obj): 

1074 """Check that list_editable is a sequence of editable fields from 

1075 list_display without first element.""" 

1076 

1077 if not isinstance(obj.list_editable, (list, tuple)): 1077 ↛ 1078line 1077 didn't jump to line 1078, because the condition on line 1077 was never true

1078 return must_be( 

1079 "a list or tuple", option="list_editable", obj=obj, id="admin.E120" 

1080 ) 

1081 else: 

1082 return list( 

1083 chain.from_iterable( 

1084 self._check_list_editable_item( 

1085 obj, item, "list_editable[%d]" % index 

1086 ) 

1087 for index, item in enumerate(obj.list_editable) 

1088 ) 

1089 ) 

1090 

1091 def _check_list_editable_item(self, obj, field_name, label): 

1092 try: 

1093 field = obj.model._meta.get_field(field_name) 

1094 except FieldDoesNotExist: 

1095 return refer_to_missing_field( 

1096 field=field_name, option=label, obj=obj, id="admin.E121" 

1097 ) 

1098 else: 

1099 if field_name not in obj.list_display: 

1100 return [ 

1101 checks.Error( 

1102 "The value of '%s' refers to '%s', which is not " 

1103 "contained in 'list_display'." % (label, field_name), 

1104 obj=obj.__class__, 

1105 id="admin.E122", 

1106 ) 

1107 ] 

1108 elif obj.list_display_links and field_name in obj.list_display_links: 

1109 return [ 

1110 checks.Error( 

1111 "The value of '%s' cannot be in both 'list_editable' and " 

1112 "'list_display_links'." % field_name, 

1113 obj=obj.__class__, 

1114 id="admin.E123", 

1115 ) 

1116 ] 

1117 # If list_display[0] is in list_editable, check that 

1118 # list_display_links is set. See #22792 and #26229 for use cases. 

1119 elif ( 

1120 obj.list_display[0] == field_name 

1121 and not obj.list_display_links 

1122 and obj.list_display_links is not None 

1123 ): 

1124 return [ 

1125 checks.Error( 

1126 "The value of '%s' refers to the first field in 'list_display' " 

1127 "('%s'), which cannot be used unless 'list_display_links' is " 

1128 "set." % (label, obj.list_display[0]), 

1129 obj=obj.__class__, 

1130 id="admin.E124", 

1131 ) 

1132 ] 

1133 elif not field.editable: 

1134 return [ 

1135 checks.Error( 

1136 "The value of '%s' refers to '%s', which is not editable " 

1137 "through the admin." % (label, field_name), 

1138 obj=obj.__class__, 

1139 id="admin.E125", 

1140 ) 

1141 ] 

1142 else: 

1143 return [] 

1144 

1145 def _check_search_fields(self, obj): 

1146 """Check search_fields is a sequence.""" 

1147 

1148 if not isinstance(obj.search_fields, (list, tuple)): 1148 ↛ 1149line 1148 didn't jump to line 1149, because the condition on line 1148 was never true

1149 return must_be( 

1150 "a list or tuple", option="search_fields", obj=obj, id="admin.E126" 

1151 ) 

1152 else: 

1153 return [] 

1154 

1155 def _check_date_hierarchy(self, obj): 

1156 """Check that date_hierarchy refers to DateField or DateTimeField.""" 

1157 

1158 if obj.date_hierarchy is None: 

1159 return [] 

1160 else: 

1161 try: 

1162 field = get_fields_from_path(obj.model, obj.date_hierarchy)[-1] 

1163 except (NotRelationField, FieldDoesNotExist): 

1164 return [ 

1165 checks.Error( 

1166 "The value of 'date_hierarchy' refers to '%s', which " 

1167 "does not refer to a Field." % obj.date_hierarchy, 

1168 obj=obj.__class__, 

1169 id="admin.E127", 

1170 ) 

1171 ] 

1172 else: 

1173 if not isinstance(field, (models.DateField, models.DateTimeField)): 1173 ↛ 1174line 1173 didn't jump to line 1174, because the condition on line 1173 was never true

1174 return must_be( 

1175 "a DateField or DateTimeField", 

1176 option="date_hierarchy", 

1177 obj=obj, 

1178 id="admin.E128", 

1179 ) 

1180 else: 

1181 return [] 

1182 

1183 def _check_action_permission_methods(self, obj): 

1184 """ 

1185 Actions with an allowed_permission attribute require the ModelAdmin to 

1186 implement a has_<perm>_permission() method for each permission. 

1187 """ 

1188 actions = obj._get_base_actions() 

1189 errors = [] 

1190 for func, name, _ in actions: 

1191 if not hasattr(func, "allowed_permissions"): 

1192 continue 

1193 for permission in func.allowed_permissions: 

1194 method_name = "has_%s_permission" % permission 

1195 if not hasattr(obj, method_name): 1195 ↛ 1196line 1195 didn't jump to line 1196, because the condition on line 1195 was never true

1196 errors.append( 

1197 checks.Error( 

1198 "%s must define a %s() method for the %s action." 

1199 % ( 

1200 obj.__class__.__name__, 

1201 method_name, 

1202 func.__name__, 

1203 ), 

1204 obj=obj.__class__, 

1205 id="admin.E129", 

1206 ) 

1207 ) 

1208 return errors 

1209 

1210 def _check_actions_uniqueness(self, obj): 

1211 """Check that every action has a unique __name__.""" 

1212 errors = [] 

1213 names = collections.Counter(name for _, name, _ in obj._get_base_actions()) 

1214 for name, count in names.items(): 

1215 if count > 1: 1215 ↛ 1216line 1215 didn't jump to line 1216, because the condition on line 1215 was never true

1216 errors.append( 

1217 checks.Error( 

1218 "__name__ attributes of actions defined in %s must be " 

1219 "unique. Name %r is not unique." 

1220 % ( 

1221 obj.__class__.__name__, 

1222 name, 

1223 ), 

1224 obj=obj.__class__, 

1225 id="admin.E130", 

1226 ) 

1227 ) 

1228 return errors 

1229 

1230 

1231class InlineModelAdminChecks(BaseModelAdminChecks): 

1232 def check(self, inline_obj, **kwargs): 

1233 parent_model = inline_obj.parent_model 

1234 return [ 

1235 *super().check(inline_obj), 

1236 *self._check_relation(inline_obj, parent_model), 

1237 *self._check_exclude_of_parent_model(inline_obj, parent_model), 

1238 *self._check_extra(inline_obj), 

1239 *self._check_max_num(inline_obj), 

1240 *self._check_min_num(inline_obj), 

1241 *self._check_formset(inline_obj), 

1242 ] 

1243 

1244 def _check_exclude_of_parent_model(self, obj, parent_model): 

1245 # Do not perform more specific checks if the base checks result in an 

1246 # error. 

1247 errors = super()._check_exclude(obj) 

1248 if errors: 1248 ↛ 1249line 1248 didn't jump to line 1249, because the condition on line 1248 was never true

1249 return [] 

1250 

1251 # Skip if `fk_name` is invalid. 

1252 if self._check_relation(obj, parent_model): 1252 ↛ 1253line 1252 didn't jump to line 1253, because the condition on line 1252 was never true

1253 return [] 

1254 

1255 if obj.exclude is None: 1255 ↛ 1258line 1255 didn't jump to line 1258, because the condition on line 1255 was never false

1256 return [] 

1257 

1258 fk = _get_foreign_key(parent_model, obj.model, fk_name=obj.fk_name) 

1259 if fk.name in obj.exclude: 

1260 return [ 

1261 checks.Error( 

1262 "Cannot exclude the field '%s', because it is the foreign key " 

1263 "to the parent model '%s'." 

1264 % ( 

1265 fk.name, 

1266 parent_model._meta.label, 

1267 ), 

1268 obj=obj.__class__, 

1269 id="admin.E201", 

1270 ) 

1271 ] 

1272 else: 

1273 return [] 

1274 

1275 def _check_relation(self, obj, parent_model): 

1276 try: 

1277 _get_foreign_key(parent_model, obj.model, fk_name=obj.fk_name) 

1278 except ValueError as e: 

1279 return [checks.Error(e.args[0], obj=obj.__class__, id="admin.E202")] 

1280 else: 

1281 return [] 

1282 

1283 def _check_extra(self, obj): 

1284 """Check that extra is an integer.""" 

1285 

1286 if not isinstance(obj.extra, int): 1286 ↛ 1287line 1286 didn't jump to line 1287, because the condition on line 1286 was never true

1287 return must_be("an integer", option="extra", obj=obj, id="admin.E203") 

1288 else: 

1289 return [] 

1290 

1291 def _check_max_num(self, obj): 

1292 """Check that max_num is an integer.""" 

1293 

1294 if obj.max_num is None: 1294 ↛ 1296line 1294 didn't jump to line 1296, because the condition on line 1294 was never false

1295 return [] 

1296 elif not isinstance(obj.max_num, int): 

1297 return must_be("an integer", option="max_num", obj=obj, id="admin.E204") 

1298 else: 

1299 return [] 

1300 

1301 def _check_min_num(self, obj): 

1302 """Check that min_num is an integer.""" 

1303 

1304 if obj.min_num is None: 1304 ↛ 1306line 1304 didn't jump to line 1306, because the condition on line 1304 was never false

1305 return [] 

1306 elif not isinstance(obj.min_num, int): 

1307 return must_be("an integer", option="min_num", obj=obj, id="admin.E205") 

1308 else: 

1309 return [] 

1310 

1311 def _check_formset(self, obj): 

1312 """Check formset is a subclass of BaseModelFormSet.""" 

1313 

1314 if not _issubclass(obj.formset, BaseModelFormSet): 1314 ↛ 1315line 1314 didn't jump to line 1315, because the condition on line 1314 was never true

1315 return must_inherit_from( 

1316 parent="BaseModelFormSet", option="formset", obj=obj, id="admin.E206" 

1317 ) 

1318 else: 

1319 return [] 

1320 

1321 

1322def must_be(type, option, obj, id): 

1323 return [ 

1324 checks.Error( 

1325 "The value of '%s' must be %s." % (option, type), 

1326 obj=obj.__class__, 

1327 id=id, 

1328 ), 

1329 ] 

1330 

1331 

1332def must_inherit_from(parent, option, obj, id): 

1333 return [ 

1334 checks.Error( 

1335 "The value of '%s' must inherit from '%s'." % (option, parent), 

1336 obj=obj.__class__, 

1337 id=id, 

1338 ), 

1339 ] 

1340 

1341 

1342def refer_to_missing_field(field, option, obj, id): 

1343 return [ 

1344 checks.Error( 

1345 "The value of '%s' refers to '%s', which is not a field of '%s'." 

1346 % (option, field, obj.model._meta.label), 

1347 obj=obj.__class__, 

1348 id=id, 

1349 ), 

1350 ]