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

1 import bisect

2 import copy

3 import inspect

4 from collections import defaultdict

5

6 from plain.exceptions import FieldDoesNotExist

7 from plain.models.constraints import UniqueConstraint

8 from plain.models.db import connections

9 from plain.models.fields import BigAutoField

10 from plain.models.fields.proxy import OrderWrt

11 from plain.models.manager import Manager

12 from plain.models.query_utils import PathInfo

13 from plain.packages import packages

14 from plain.runtime import settings

15 from plain.utils.datastructures import ImmutableList, OrderedSet

16 from plain.utils.functional import cached_property

17

18 PROXY_PARENTS = object()

19

20 EMPTY_RELATION_TREE = ()

21

22 IMMUTABLE_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

27 DEFAULT_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

51 def make_immutable_fields_list(name, data):

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

53

54

55 class 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 ]