Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain-models/plain/models/options.py: 68%
421 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-23 11:16 -0600
« 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
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
18PROXY_PARENTS = object()
20EMPTY_RELATION_TREE = ()
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)
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)
51def make_immutable_fields_list(name, data):
52 return ImmutableList(data, warning=IMMUTABLE_WARNING % name)
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"}
70 default_packages = packages
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
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 = []
112 # A custom app registry to use, if you're making a separate model set.
113 self.packages = self.default_packages
115 self.default_related_name = None
117 @property
118 def label(self):
119 return f"{self.package_label}.{self.object_name}"
121 @property
122 def label_lower(self):
123 return f"{self.package_label}.{self.model_name}"
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)
130 def contribute_to_class(self, cls, name):
131 from plain.models.backends.utils import truncate_name
132 from plain.models.db import connection
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()
140 # Store the original user-defined values for each option,
141 # for use when serializing the model definition
142 self.original_attrs = {}
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)
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))
168 # order_with_respect_and ordering are mutually exclusive.
169 self._ordering_clash = bool(self.ordering and self.order_with_respect_to)
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 )
179 del self.meta
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 )
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
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 )
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
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)
243 def add_manager(self, manager):
244 self.local_managers.append(manager)
245 self._expire_cache()
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)
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)
281 def setup_pk(self, field):
282 if not self.pk and field.primary_key:
283 self.pk = field
285 def __repr__(self):
286 return f"<Options for {self.object_name}>"
288 def __str__(self):
289 return self.label_lower
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
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.
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
330 if f"{swapped_label}.{swapped_object.lower()}" != self.label_lower:
331 return swapped_for
332 return None
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
344 manager = copy.copy(manager)
345 manager.model = self.model
346 seen_managers.add(manager.name)
347 managers.append((depth, manager.creation_counter, manager))
349 return make_immutable_fields_list(
350 "managers",
351 (m[2] for m in sorted(managers)),
352 )
354 @cached_property
355 def managers_map(self):
356 return {manager.name: manager for manager in self.managers}
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
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 )
377 manager = Manager()
378 manager.name = "_base_manager"
379 manager.model = self.model
380 manager.auto_created = True
381 return manager
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
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 )
401 if self.managers:
402 return self.managers[0]
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.
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 """
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)
425 def is_not_a_generic_relation(f):
426 return not (f.is_relation and f.one_to_many)
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 )
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 )
446 @cached_property
447 def concrete_fields(self):
448 """
449 Return a list of all concrete fields on the model and its parents.
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 )
459 @cached_property
460 def local_concrete_fields(self):
461 """
462 Return a list of all concrete fields on the model.
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 )
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.
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 )
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.
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 )
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
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
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 )
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 )
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 []
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)
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.
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
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
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
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)
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)
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)
712 @cached_property
713 def _relation_tree(self):
714 return self._populate_directed_relation_graph()
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 = {}
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:
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 )
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.
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)
777 # Creates a cache key composed of all arguments
778 cache_key = (forward, reverse, include_parents, include_hidden, topmost_call)
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
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)
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
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)
840 # Store result into cache for later access
841 self._get_fields_cache[cache_key] = fields
842 return fields
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 ]
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)
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)
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 ]