Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain-models/plain/models/options.py: 63%

421 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-23 11:16 -0600

1import bisect 

2import copy 

3import inspect 

4from collections import defaultdict 

5 

6from plain.exceptions import FieldDoesNotExist 

7from plain.models.constraints import UniqueConstraint 

8from plain.models.db import connections 

9from plain.models.fields import BigAutoField 

10from plain.models.fields.proxy import OrderWrt 

11from plain.models.manager import Manager 

12from plain.models.query_utils import PathInfo 

13from plain.packages import packages 

14from plain.runtime import settings 

15from plain.utils.datastructures import ImmutableList, OrderedSet 

16from plain.utils.functional import cached_property 

17 

18PROXY_PARENTS = object() 

19 

20EMPTY_RELATION_TREE = () 

21 

22IMMUTABLE_WARNING = ( 

23 "The return type of '%s' should never be mutated. If you want to manipulate this " 

24 "list for your own use, make a copy first." 

25) 

26 

27DEFAULT_NAMES = ( 

28 "db_table", 

29 "db_table_comment", 

30 "ordering", 

31 "get_latest_by", 

32 "order_with_respect_to", 

33 "package_label", 

34 "db_tablespace", 

35 "abstract", 

36 "managed", 

37 "swappable", 

38 "auto_created", 

39 "packages", 

40 "select_on_save", 

41 "default_related_name", 

42 "required_db_features", 

43 "required_db_vendor", 

44 "base_manager_name", 

45 "default_manager_name", 

46 "indexes", 

47 "constraints", 

48) 

49 

50 

51def make_immutable_fields_list(name, data): 

52 return ImmutableList(data, warning=IMMUTABLE_WARNING % name) 

53 

54 

55class Options: 

56 FORWARD_PROPERTIES = { 

57 "fields", 

58 "many_to_many", 

59 "concrete_fields", 

60 "local_concrete_fields", 

61 "_non_pk_concrete_field_names", 

62 "_forward_fields_map", 

63 "managers", 

64 "managers_map", 

65 "base_manager", 

66 "default_manager", 

67 } 

68 REVERSE_PROPERTIES = {"related_objects", "fields_map", "_relation_tree"} 

69 

70 default_packages = packages 

71 

72 def __init__(self, meta, package_label=None): 

73 self._get_fields_cache = {} 

74 self.local_fields = [] 

75 self.local_many_to_many = [] 

76 self.private_fields = [] 

77 self.local_managers = [] 

78 self.base_manager_name = None 

79 self.default_manager_name = None 

80 self.model_name = None 

81 self.db_table = "" 

82 self.db_table_comment = "" 

83 self.ordering = [] 

84 self._ordering_clash = False 

85 self.indexes = [] 

86 self.constraints = [] 

87 self.select_on_save = False 

88 self.object_name = None 

89 self.package_label = package_label 

90 self.get_latest_by = None 

91 self.order_with_respect_to = None 

92 self.db_tablespace = settings.DEFAULT_TABLESPACE 

93 self.required_db_features = [] 

94 self.required_db_vendor = None 

95 self.meta = meta 

96 self.pk = None 

97 self.auto_field = None 

98 self.abstract = False 

99 self.managed = True 

100 # For any non-abstract class, the concrete class is the model 

101 # in the end of the proxy_for_model chain. In particular, for 

102 # concrete models, the concrete_model is always the class itself. 

103 self.concrete_model = None 

104 self.swappable = None 

105 self.parents = {} 

106 self.auto_created = False 

107 

108 # List of all lookups defined in ForeignKey 'limit_choices_to' options 

109 # from *other* models. Needed for some admin checks. Internal use only. 

110 self.related_fkey_lookups = [] 

111 

112 # A custom app registry to use, if you're making a separate model set. 

113 self.packages = self.default_packages 

114 

115 self.default_related_name = None 

116 

117 @property 

118 def label(self): 

119 return f"{self.package_label}.{self.object_name}" 

120 

121 @property 

122 def label_lower(self): 

123 return f"{self.package_label}.{self.model_name}" 

124 

125 @property 

126 def package_config(self): 

127 # Don't go through get_package_config to avoid triggering imports. 

128 return self.packages.package_configs.get(self.package_label) 

129 

130 def contribute_to_class(self, cls, name): 

131 from plain.models.backends.utils import truncate_name 

132 from plain.models.db import connection 

133 

134 cls._meta = self 

135 self.model = cls 

136 # First, construct the default values for these options. 

137 self.object_name = cls.__name__ 

138 self.model_name = self.object_name.lower() 

139 

140 # Store the original user-defined values for each option, 

141 # for use when serializing the model definition 

142 self.original_attrs = {} 

143 

144 # Next, apply any overridden values from 'class Meta'. 

145 if self.meta: 

146 meta_attrs = self.meta.__dict__.copy() 

147 for name in self.meta.__dict__: 

148 # Ignore any private attributes that Plain doesn't care about. 

149 # NOTE: We can't modify a dictionary's contents while looping 

150 # over it, so we loop over the *original* dictionary instead. 

151 if name.startswith("_"): 

152 del meta_attrs[name] 

153 for attr_name in DEFAULT_NAMES: 

154 if attr_name in meta_attrs: 

155 setattr(self, attr_name, meta_attrs.pop(attr_name)) 

156 self.original_attrs[attr_name] = getattr(self, attr_name) 

157 elif hasattr(self.meta, attr_name): 

158 setattr(self, attr_name, getattr(self.meta, attr_name)) 

159 self.original_attrs[attr_name] = getattr(self, attr_name) 

160 

161 # Package label/class name interpolation for names of constraints and 

162 # indexes. 

163 if not getattr(cls._meta, "abstract", False): 

164 for attr_name in {"constraints", "indexes"}: 

165 objs = getattr(self, attr_name, []) 

166 setattr(self, attr_name, self._format_names_with_class(cls, objs)) 

167 

168 # order_with_respect_and ordering are mutually exclusive. 

169 self._ordering_clash = bool(self.ordering and self.order_with_respect_to) 

170 

171 # Any leftover attributes must be invalid. 

172 if meta_attrs != {}: 

173 raise TypeError( 

174 "'class Meta' got invalid attribute(s): {}".format( 

175 ",".join(meta_attrs) 

176 ) 

177 ) 

178 

179 del self.meta 

180 

181 # If the db_table wasn't provided, use the package_label + model_name. 

182 if not self.db_table: 

183 self.db_table = f"{self.package_label}_{self.model_name}" 

184 self.db_table = truncate_name( 

185 self.db_table, connection.ops.max_name_length() 

186 ) 

187 

188 def _format_names_with_class(self, cls, objs): 

189 """Package label/class name interpolation for object names.""" 

190 new_objs = [] 

191 for obj in objs: 

192 obj = obj.clone() 

193 obj.name = obj.name % { 

194 "package_label": cls._meta.package_label.lower(), 

195 "class": cls.__name__.lower(), 

196 } 

197 new_objs.append(obj) 

198 return new_objs 

199 

200 def _prepare(self, model): 

201 if self.order_with_respect_to: 

202 # The app registry will not be ready at this point, so we cannot 

203 # use get_field(). 

204 query = self.order_with_respect_to 

205 try: 

206 self.order_with_respect_to = next( 

207 f 

208 for f in self._get_fields(reverse=False) 

209 if f.name == query or f.attname == query 

210 ) 

211 except StopIteration: 

212 raise FieldDoesNotExist( 

213 f"{self.object_name} has no field named '{query}'" 

214 ) 

215 

216 self.ordering = ("_order",) 

217 if not any( 

218 isinstance(field, OrderWrt) for field in model._meta.local_fields 

219 ): 

220 model.add_to_class("_order", OrderWrt()) 

221 else: 

222 self.order_with_respect_to = None 

223 

224 if self.pk is None: 

225 if self.parents: 

226 # Promote the first parent link in lieu of adding yet another 

227 # field. 

228 field = next(iter(self.parents.values())) 

229 # Look for a local field with the same name as the 

230 # first parent link. If a local field has already been 

231 # created, use it instead of promoting the parent 

232 already_created = [ 

233 fld for fld in self.local_fields if fld.name == field.name 

234 ] 

235 if already_created: 

236 field = already_created[0] 

237 field.primary_key = True 

238 self.setup_pk(field) 

239 else: 

240 auto = BigAutoField(primary_key=True, auto_created=True) 

241 model.add_to_class("id", auto) 

242 

243 def add_manager(self, manager): 

244 self.local_managers.append(manager) 

245 self._expire_cache() 

246 

247 def add_field(self, field, private=False): 

248 # Insert the given field in the order in which it was created, using 

249 # the "creation_counter" attribute of the field. 

250 # Move many-to-many related fields from self.fields into 

251 # self.many_to_many. 

252 if private: 

253 self.private_fields.append(field) 

254 elif field.is_relation and field.many_to_many: 

255 bisect.insort(self.local_many_to_many, field) 

256 else: 

257 bisect.insort(self.local_fields, field) 

258 self.setup_pk(field) 

259 

260 # If the field being added is a relation to another known field, 

261 # expire the cache on this field and the forward cache on the field 

262 # being referenced, because there will be new relationships in the 

263 # cache. Otherwise, expire the cache of references *to* this field. 

264 # The mechanism for getting at the related model is slightly odd - 

265 # ideally, we'd just ask for field.related_model. However, related_model 

266 # is a cached property, and all the models haven't been loaded yet, so 

267 # we need to make sure we don't cache a string reference. 

268 if ( 

269 field.is_relation 

270 and hasattr(field.remote_field, "model") 

271 and field.remote_field.model 

272 ): 

273 try: 

274 field.remote_field.model._meta._expire_cache(forward=False) 

275 except AttributeError: 

276 pass 

277 self._expire_cache() 

278 else: 

279 self._expire_cache(reverse=False) 

280 

281 def setup_pk(self, field): 

282 if not self.pk and field.primary_key: 

283 self.pk = field 

284 

285 def __repr__(self): 

286 return f"<Options for {self.object_name}>" 

287 

288 def __str__(self): 

289 return self.label_lower 

290 

291 def can_migrate(self, connection): 

292 """ 

293 Return True if the model can/should be migrated on the `connection`. 

294 `connection` can be either a real connection or a connection alias. 

295 """ 

296 if self.swapped or not self.managed: 

297 return False 

298 if isinstance(connection, str): 

299 connection = connections[connection] 

300 if self.required_db_vendor: 

301 return self.required_db_vendor == connection.vendor 

302 if self.required_db_features: 

303 return all( 

304 getattr(connection.features, feat, False) 

305 for feat in self.required_db_features 

306 ) 

307 return True 

308 

309 @property 

310 def swapped(self): 

311 """ 

312 Has this model been swapped out for another? If so, return the model 

313 name of the replacement; otherwise, return None. 

314 

315 For historical reasons, model name lookups using get_model() are 

316 case insensitive, so we make sure we are case insensitive here. 

317 """ 

318 if self.swappable: 

319 swapped_for = getattr(settings, self.swappable, None) 

320 if swapped_for: 

321 try: 

322 swapped_label, swapped_object = swapped_for.split(".") 

323 except ValueError: 

324 # setting not in the format package_label.model_name 

325 # raising ImproperlyConfigured here causes problems with 

326 # test cleanup code - instead it is raised in get_user_model 

327 # or as part of validation. 

328 return swapped_for 

329 

330 if f"{swapped_label}.{swapped_object.lower()}" != self.label_lower: 

331 return swapped_for 

332 return None 

333 

334 @cached_property 

335 def managers(self): 

336 managers = [] 

337 seen_managers = set() 

338 bases = (b for b in self.model.mro() if hasattr(b, "_meta")) 

339 for depth, base in enumerate(bases): 

340 for manager in base._meta.local_managers: 

341 if manager.name in seen_managers: 

342 continue 

343 

344 manager = copy.copy(manager) 

345 manager.model = self.model 

346 seen_managers.add(manager.name) 

347 managers.append((depth, manager.creation_counter, manager)) 

348 

349 return make_immutable_fields_list( 

350 "managers", 

351 (m[2] for m in sorted(managers)), 

352 ) 

353 

354 @cached_property 

355 def managers_map(self): 

356 return {manager.name: manager for manager in self.managers} 

357 

358 @cached_property 

359 def base_manager(self): 

360 base_manager_name = self.base_manager_name 

361 if not base_manager_name: 

362 # Get the first parent's base_manager_name if there's one. 

363 for parent in self.model.mro()[1:]: 

364 if hasattr(parent, "_meta"): 

365 if parent._base_manager.name != "_base_manager": 

366 base_manager_name = parent._base_manager.name 

367 break 

368 

369 if base_manager_name: 

370 try: 

371 return self.managers_map[base_manager_name] 

372 except KeyError: 

373 raise ValueError( 

374 f"{self.object_name} has no manager named {base_manager_name!r}" 

375 ) 

376 

377 manager = Manager() 

378 manager.name = "_base_manager" 

379 manager.model = self.model 

380 manager.auto_created = True 

381 return manager 

382 

383 @cached_property 

384 def default_manager(self): 

385 default_manager_name = self.default_manager_name 

386 if not default_manager_name and not self.local_managers: 

387 # Get the first parent's default_manager_name if there's one. 

388 for parent in self.model.mro()[1:]: 

389 if hasattr(parent, "_meta"): 

390 default_manager_name = parent._meta.default_manager_name 

391 break 

392 

393 if default_manager_name: 

394 try: 

395 return self.managers_map[default_manager_name] 

396 except KeyError: 

397 raise ValueError( 

398 f"{self.object_name} has no manager named {default_manager_name!r}" 

399 ) 

400 

401 if self.managers: 

402 return self.managers[0] 

403 

404 @cached_property 

405 def fields(self): 

406 """ 

407 Return a list of all forward fields on the model and its parents, 

408 excluding ManyToManyFields. 

409 

410 Private API intended only to be used by Plain itself; get_fields() 

411 combined with filtering of field properties is the public API for 

412 obtaining this field list. 

413 """ 

414 

415 # For legacy reasons, the fields property should only contain forward 

416 # fields that are not private or with a m2m cardinality. Therefore we 

417 # pass these three filters as filters to the generator. 

418 # The third lambda is a longwinded way of checking f.related_model - we don't 

419 # use that property directly because related_model is a cached property, 

420 # and all the models may not have been loaded yet; we don't want to cache 

421 # the string reference to the related_model. 

422 def is_not_an_m2m_field(f): 

423 return not (f.is_relation and f.many_to_many) 

424 

425 def is_not_a_generic_relation(f): 

426 return not (f.is_relation and f.one_to_many) 

427 

428 def is_not_a_generic_foreign_key(f): 

429 return not ( 

430 f.is_relation 

431 and f.many_to_one 

432 and not (hasattr(f.remote_field, "model") and f.remote_field.model) 

433 ) 

434 

435 return make_immutable_fields_list( 

436 "fields", 

437 ( 

438 f 

439 for f in self._get_fields(reverse=False) 

440 if is_not_an_m2m_field(f) 

441 and is_not_a_generic_relation(f) 

442 and is_not_a_generic_foreign_key(f) 

443 ), 

444 ) 

445 

446 @cached_property 

447 def concrete_fields(self): 

448 """ 

449 Return a list of all concrete fields on the model and its parents. 

450 

451 Private API intended only to be used by Plain itself; get_fields() 

452 combined with filtering of field properties is the public API for 

453 obtaining this field list. 

454 """ 

455 return make_immutable_fields_list( 

456 "concrete_fields", (f for f in self.fields if f.concrete) 

457 ) 

458 

459 @cached_property 

460 def local_concrete_fields(self): 

461 """ 

462 Return a list of all concrete fields on the model. 

463 

464 Private API intended only to be used by Plain itself; get_fields() 

465 combined with filtering of field properties is the public API for 

466 obtaining this field list. 

467 """ 

468 return make_immutable_fields_list( 

469 "local_concrete_fields", (f for f in self.local_fields if f.concrete) 

470 ) 

471 

472 @cached_property 

473 def many_to_many(self): 

474 """ 

475 Return a list of all many to many fields on the model and its parents. 

476 

477 Private API intended only to be used by Plain itself; get_fields() 

478 combined with filtering of field properties is the public API for 

479 obtaining this list. 

480 """ 

481 return make_immutable_fields_list( 

482 "many_to_many", 

483 ( 

484 f 

485 for f in self._get_fields(reverse=False) 

486 if f.is_relation and f.many_to_many 

487 ), 

488 ) 

489 

490 @cached_property 

491 def related_objects(self): 

492 """ 

493 Return all related objects pointing to the current model. The related 

494 objects can come from a one-to-one, one-to-many, or many-to-many field 

495 relation type. 

496 

497 Private API intended only to be used by Plain itself; get_fields() 

498 combined with filtering of field properties is the public API for 

499 obtaining this field list. 

500 """ 

501 all_related_fields = self._get_fields( 

502 forward=False, reverse=True, include_hidden=True 

503 ) 

504 return make_immutable_fields_list( 

505 "related_objects", 

506 ( 

507 obj 

508 for obj in all_related_fields 

509 if not obj.hidden or obj.field.many_to_many 

510 ), 

511 ) 

512 

513 @cached_property 

514 def _forward_fields_map(self): 

515 res = {} 

516 fields = self._get_fields(reverse=False) 

517 for field in fields: 

518 res[field.name] = field 

519 # Due to the way Plain's internals work, get_field() should also 

520 # be able to fetch a field by attname. In the case of a concrete 

521 # field with relation, includes the *_id name too 

522 try: 

523 res[field.attname] = field 

524 except AttributeError: 

525 pass 

526 return res 

527 

528 @cached_property 

529 def fields_map(self): 

530 res = {} 

531 fields = self._get_fields(forward=False, include_hidden=True) 

532 for field in fields: 

533 res[field.name] = field 

534 # Due to the way Plain's internals work, get_field() should also 

535 # be able to fetch a field by attname. In the case of a concrete 

536 # field with relation, includes the *_id name too 

537 try: 

538 res[field.attname] = field 

539 except AttributeError: 

540 pass 

541 return res 

542 

543 def get_field(self, field_name): 

544 """ 

545 Return a field instance given the name of a forward or reverse field. 

546 """ 

547 try: 

548 # In order to avoid premature loading of the relation tree 

549 # (expensive) we prefer checking if the field is a forward field. 

550 return self._forward_fields_map[field_name] 

551 except KeyError: 

552 # If the app registry is not ready, reverse fields are 

553 # unavailable, therefore we throw a FieldDoesNotExist exception. 

554 if not self.packages.models_ready: 

555 raise FieldDoesNotExist( 

556 f"{self.object_name} has no field named '{field_name}'. The app cache isn't ready yet, " 

557 "so if this is an auto-created related field, it won't " 

558 "be available yet." 

559 ) 

560 

561 try: 

562 # Retrieve field instance by name from cached or just-computed 

563 # field map. 

564 return self.fields_map[field_name] 

565 except KeyError: 

566 raise FieldDoesNotExist( 

567 f"{self.object_name} has no field named '{field_name}'" 

568 ) 

569 

570 def get_base_chain(self, model): 

571 """ 

572 Return a list of parent classes leading to `model` (ordered from 

573 closest to most distant ancestor). This has to handle the case where 

574 `model` is a grandparent or even more distant relation. 

575 """ 

576 if not self.parents: 

577 return [] 

578 if model in self.parents: 

579 return [model] 

580 for parent in self.parents: 

581 res = parent._meta.get_base_chain(model) 

582 if res: 

583 res.insert(0, parent) 

584 return res 

585 return [] 

586 

587 def get_parent_list(self): 

588 """ 

589 Return all the ancestors of this model as a list ordered by MRO. 

590 Useful for determining if something is an ancestor, regardless of lineage. 

591 """ 

592 result = OrderedSet(self.parents) 

593 for parent in self.parents: 

594 for ancestor in parent._meta.get_parent_list(): 

595 result.add(ancestor) 

596 return list(result) 

597 

598 def get_ancestor_link(self, ancestor): 

599 """ 

600 Return the field on the current model which points to the given 

601 "ancestor". This is possible an indirect link (a pointer to a parent 

602 model, which points, eventually, to the ancestor). Used when 

603 constructing table joins for model inheritance. 

604 

605 Return None if the model isn't an ancestor of this one. 

606 """ 

607 if ancestor in self.parents: 

608 return self.parents[ancestor] 

609 for parent in self.parents: 

610 # Tries to get a link field from the immediate parent 

611 parent_link = parent._meta.get_ancestor_link(ancestor) 

612 if parent_link: 

613 # In case of a proxied model, the first link 

614 # of the chain to the ancestor is that parent 

615 # links 

616 return self.parents[parent] or parent_link 

617 

618 def get_path_to_parent(self, parent): 

619 """ 

620 Return a list of PathInfos containing the path from the current 

621 model to the parent model, or an empty list if parent is not a 

622 parent of the current model. 

623 """ 

624 if self.model is parent: 

625 return [] 

626 # Skip the chain of proxy to the concrete proxied model. 

627 proxied_model = self.concrete_model 

628 path = [] 

629 opts = self 

630 for int_model in self.get_base_chain(parent): 

631 if int_model is proxied_model: 

632 opts = int_model._meta 

633 else: 

634 final_field = opts.parents[int_model] 

635 targets = (final_field.remote_field.get_related_field(),) 

636 opts = int_model._meta 

637 path.append( 

638 PathInfo( 

639 from_opts=final_field.model._meta, 

640 to_opts=opts, 

641 target_fields=targets, 

642 join_field=final_field, 

643 m2m=False, 

644 direct=True, 

645 filtered_relation=None, 

646 ) 

647 ) 

648 return path 

649 

650 def get_path_from_parent(self, parent): 

651 """ 

652 Return a list of PathInfos containing the path from the parent 

653 model to the current model, or an empty list if parent is not a 

654 parent of the current model. 

655 """ 

656 if self.model is parent: 

657 return [] 

658 model = self.concrete_model 

659 # Get a reversed base chain including both the current and parent 

660 # models. 

661 chain = model._meta.get_base_chain(parent) 

662 chain.reverse() 

663 chain.append(model) 

664 # Construct a list of the PathInfos between models in chain. 

665 path = [] 

666 for i, ancestor in enumerate(chain[:-1]): 

667 child = chain[i + 1] 

668 link = child._meta.get_ancestor_link(ancestor) 

669 path.extend(link.reverse_path_infos) 

670 return path 

671 

672 def _populate_directed_relation_graph(self): 

673 """ 

674 This method is used by each model to find its reverse objects. As this 

675 method is very expensive and is accessed frequently (it looks up every 

676 field in a model, in every app), it is computed on first access and then 

677 is set as a property on every model. 

678 """ 

679 related_objects_graph = defaultdict(list) 

680 

681 all_models = self.packages.get_models(include_auto_created=True) 

682 for model in all_models: 

683 opts = model._meta 

684 # Abstract model's fields are copied to child models, hence we will 

685 # see the fields from the child models. 

686 if opts.abstract: 

687 continue 

688 fields_with_relations = ( 

689 f 

690 for f in opts._get_fields(reverse=False, include_parents=False) 

691 if f.is_relation and f.related_model is not None 

692 ) 

693 for f in fields_with_relations: 

694 if not isinstance(f.remote_field.model, str): 

695 remote_label = f.remote_field.model._meta.concrete_model._meta.label 

696 related_objects_graph[remote_label].append(f) 

697 

698 for model in all_models: 

699 # Set the relation_tree using the internal __dict__. In this way 

700 # we avoid calling the cached property. In attribute lookup, 

701 # __dict__ takes precedence over a data descriptor (such as 

702 # @cached_property). This means that the _meta._relation_tree is 

703 # only called if related_objects is not in __dict__. 

704 related_objects = related_objects_graph[ 

705 model._meta.concrete_model._meta.label 

706 ] 

707 model._meta.__dict__["_relation_tree"] = related_objects 

708 # It seems it is possible that self is not in all_models, so guard 

709 # against that with default for get(). 

710 return self.__dict__.get("_relation_tree", EMPTY_RELATION_TREE) 

711 

712 @cached_property 

713 def _relation_tree(self): 

714 return self._populate_directed_relation_graph() 

715 

716 def _expire_cache(self, forward=True, reverse=True): 

717 # This method is usually called by packages.cache_clear(), when the 

718 # registry is finalized, or when a new field is added. 

719 if forward: 

720 for cache_key in self.FORWARD_PROPERTIES: 

721 if cache_key in self.__dict__: 

722 delattr(self, cache_key) 

723 if reverse and not self.abstract: 

724 for cache_key in self.REVERSE_PROPERTIES: 

725 if cache_key in self.__dict__: 

726 delattr(self, cache_key) 

727 self._get_fields_cache = {} 

728 

729 def get_fields(self, include_parents=True, include_hidden=False): 

730 """ 

731 Return a list of fields associated to the model. By default, include 

732 forward and reverse fields, fields derived from inheritance, but not 

733 hidden fields. The returned fields can be changed using the parameters: 

734 

735 - include_parents: include fields derived from inheritance 

736 - include_hidden: include fields that have a related_name that 

737 starts with a "+" 

738 """ 

739 if include_parents is False: 

740 include_parents = PROXY_PARENTS 

741 return self._get_fields( 

742 include_parents=include_parents, include_hidden=include_hidden 

743 ) 

744 

745 def _get_fields( 

746 self, 

747 forward=True, 

748 reverse=True, 

749 include_parents=True, 

750 include_hidden=False, 

751 seen_models=None, 

752 ): 

753 """ 

754 Internal helper function to return fields of the model. 

755 * If forward=True, then fields defined on this model are returned. 

756 * If reverse=True, then relations pointing to this model are returned. 

757 * If include_hidden=True, then fields with is_hidden=True are returned. 

758 * The include_parents argument toggles if fields from parent models 

759 should be included. It has three values: True, False, and 

760 PROXY_PARENTS. When set to PROXY_PARENTS, the call will return all 

761 fields defined for the current model or any of its parents in the 

762 parent chain to the model's concrete model. 

763 """ 

764 if include_parents not in (True, False, PROXY_PARENTS): 

765 raise TypeError(f"Invalid argument for include_parents: {include_parents}") 

766 # This helper function is used to allow recursion in ``get_fields()`` 

767 # implementation and to provide a fast way for Plain's internals to 

768 # access specific subsets of fields. 

769 

770 # We must keep track of which models we have already seen. Otherwise we 

771 # could include the same field multiple times from different models. 

772 topmost_call = seen_models is None 

773 if topmost_call: 

774 seen_models = set() 

775 seen_models.add(self.model) 

776 

777 # Creates a cache key composed of all arguments 

778 cache_key = (forward, reverse, include_parents, include_hidden, topmost_call) 

779 

780 try: 

781 # In order to avoid list manipulation. Always return a shallow copy 

782 # of the results. 

783 return self._get_fields_cache[cache_key] 

784 except KeyError: 

785 pass 

786 

787 fields = [] 

788 # Recursively call _get_fields() on each parent, with the same 

789 # options provided in this call. 

790 if include_parents is not False: 

791 for parent in self.parents: 

792 # In diamond inheritance it is possible that we see the same 

793 # model from two different routes. In that case, avoid adding 

794 # fields from the same parent again. 

795 if parent in seen_models: 

796 continue 

797 if ( 

798 parent._meta.concrete_model != self.concrete_model 

799 and include_parents == PROXY_PARENTS 

800 ): 

801 continue 

802 for obj in parent._meta._get_fields( 

803 forward=forward, 

804 reverse=reverse, 

805 include_parents=include_parents, 

806 include_hidden=include_hidden, 

807 seen_models=seen_models, 

808 ): 

809 if ( 

810 not getattr(obj, "parent_link", False) 

811 or obj.model == self.concrete_model 

812 ): 

813 fields.append(obj) 

814 if reverse: 

815 # Tree is computed once and cached until the app cache is expired. 

816 # It is composed of a list of fields pointing to the current model 

817 # from other models. 

818 all_fields = self._relation_tree 

819 for field in all_fields: 

820 # If hidden fields should be included or the relation is not 

821 # intentionally hidden, add to the fields dict. 

822 if include_hidden or not field.remote_field.hidden: 

823 fields.append(field.remote_field) 

824 

825 if forward: 

826 fields += self.local_fields 

827 fields += self.local_many_to_many 

828 # Private fields are recopied to each child model, and they get a 

829 # different model as field.model in each child. Hence we have to 

830 # add the private fields separately from the topmost call. If we 

831 # did this recursively similar to local_fields, we would get field 

832 # instances with field.model != self.model. 

833 if topmost_call: 

834 fields += self.private_fields 

835 

836 # In order to avoid list manipulation. Always 

837 # return a shallow copy of the results 

838 fields = make_immutable_fields_list("get_fields()", fields) 

839 

840 # Store result into cache for later access 

841 self._get_fields_cache[cache_key] = fields 

842 return fields 

843 

844 @cached_property 

845 def total_unique_constraints(self): 

846 """ 

847 Return a list of total unique constraints. Useful for determining set 

848 of fields guaranteed to be unique for all rows. 

849 """ 

850 return [ 

851 constraint 

852 for constraint in self.constraints 

853 if ( 

854 isinstance(constraint, UniqueConstraint) 

855 and constraint.condition is None 

856 and not constraint.contains_expressions 

857 ) 

858 ] 

859 

860 @cached_property 

861 def _property_names(self): 

862 """Return a set of the names of the properties defined on the model.""" 

863 names = [] 

864 for name in dir(self.model): 

865 attr = inspect.getattr_static(self.model, name) 

866 if isinstance(attr, property): 

867 names.append(name) 

868 return frozenset(names) 

869 

870 @cached_property 

871 def _non_pk_concrete_field_names(self): 

872 """ 

873 Return a set of the non-pk concrete field names defined on the model. 

874 """ 

875 names = [] 

876 for field in self.concrete_fields: 

877 if not field.primary_key: 

878 names.append(field.name) 

879 if field.name != field.attname: 

880 names.append(field.attname) 

881 return frozenset(names) 

882 

883 @cached_property 

884 def db_returning_fields(self): 

885 """ 

886 Private API intended only to be used by Plain itself. 

887 Fields to be returned after a database insert. 

888 """ 

889 return [ 

890 field 

891 for field in self._get_fields( 

892 forward=True, reverse=False, include_parents=PROXY_PARENTS 

893 ) 

894 if getattr(field, "db_returning", False) 

895 ]