plain.models
Model your data and store it in a database.
# app/users/models.py
from plain import models
from plain.passwords.models import PasswordField
class User(models.Model):
email = models.EmailField(unique=True)
password = PasswordField()
is_staff = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.email
Create, update, and delete instances of your models:
from .models import User
# Create a new user
user = User.objects.create(
email="[email protected]",
password="password",
)
# Update a user
user.email = "[email protected]"
user.save()
# Delete a user
user.delete()
# Query for users
staff_users = User.objects.filter(is_staff=True)
Installation
# app/settings.py
INSTALLED_PACKAGES = [
...
"plain.models",
]
To connect to a database, you can provide a DATABASE_URL
environment variable.
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
Or you can manually define the DATABASES
setting.
# app/settings.py
DATABASES = {
"default": {
"ENGINE": "plain.models.backends.postgresql",
"NAME": "dbname",
"USER": "user",
"PASSWORD": "password",
"HOST": "localhost",
"PORT": "5432",
}
}
Multiple backends are supported, including Postgres, MySQL, and SQLite.
Querying
Migrations
Fields
Validation
Indexes and constraints
Managers
Forms
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): %s" % ",".join(meta_attrs)
175 )
176
177 del self.meta
178
179 # If the db_table wasn't provided, use the package_label + model_name.
180 if not self.db_table:
181 self.db_table = f"{self.package_label}_{self.model_name}"
182 self.db_table = truncate_name(
183 self.db_table, connection.ops.max_name_length()
184 )
185
186 def _format_names_with_class(self, cls, objs):
187 """Package label/class name interpolation for object names."""
188 new_objs = []
189 for obj in objs:
190 obj = obj.clone()
191 obj.name = obj.name % {
192 "package_label": cls._meta.package_label.lower(),
193 "class": cls.__name__.lower(),
194 }
195 new_objs.append(obj)
196 return new_objs
197
198 def _prepare(self, model):
199 if self.order_with_respect_to:
200 # The app registry will not be ready at this point, so we cannot
201 # use get_field().
202 query = self.order_with_respect_to
203 try:
204 self.order_with_respect_to = next(
205 f
206 for f in self._get_fields(reverse=False)
207 if f.name == query or f.attname == query
208 )
209 except StopIteration:
210 raise FieldDoesNotExist(
211 f"{self.object_name} has no field named '{query}'"
212 )
213
214 self.ordering = ("_order",)
215 if not any(
216 isinstance(field, OrderWrt) for field in model._meta.local_fields
217 ):
218 model.add_to_class("_order", OrderWrt())
219 else:
220 self.order_with_respect_to = None
221
222 if self.pk is None:
223 if self.parents:
224 # Promote the first parent link in lieu of adding yet another
225 # field.
226 field = next(iter(self.parents.values()))
227 # Look for a local field with the same name as the
228 # first parent link. If a local field has already been
229 # created, use it instead of promoting the parent
230 already_created = [
231 fld for fld in self.local_fields if fld.name == field.name
232 ]
233 if already_created:
234 field = already_created[0]
235 field.primary_key = True
236 self.setup_pk(field)
237 else:
238 auto = BigAutoField(primary_key=True, auto_created=True)
239 model.add_to_class("id", auto)
240
241 def add_manager(self, manager):
242 self.local_managers.append(manager)
243 self._expire_cache()
244
245 def add_field(self, field, private=False):
246 # Insert the given field in the order in which it was created, using
247 # the "creation_counter" attribute of the field.
248 # Move many-to-many related fields from self.fields into
249 # self.many_to_many.
250 if private:
251 self.private_fields.append(field)
252 elif field.is_relation and field.many_to_many:
253 bisect.insort(self.local_many_to_many, field)
254 else:
255 bisect.insort(self.local_fields, field)
256 self.setup_pk(field)
257
258 # If the field being added is a relation to another known field,
259 # expire the cache on this field and the forward cache on the field
260 # being referenced, because there will be new relationships in the
261 # cache. Otherwise, expire the cache of references *to* this field.
262 # The mechanism for getting at the related model is slightly odd -
263 # ideally, we'd just ask for field.related_model. However, related_model
264 # is a cached property, and all the models haven't been loaded yet, so
265 # we need to make sure we don't cache a string reference.
266 if (
267 field.is_relation
268 and hasattr(field.remote_field, "model")
269 and field.remote_field.model
270 ):
271 try:
272 field.remote_field.model._meta._expire_cache(forward=False)
273 except AttributeError:
274 pass
275 self._expire_cache()
276 else:
277 self._expire_cache(reverse=False)
278
279 def setup_pk(self, field):
280 if not self.pk and field.primary_key:
281 self.pk = field
282
283 def __repr__(self):
284 return "<Options for %s>" % self.object_name
285
286 def __str__(self):
287 return self.label_lower
288
289 def can_migrate(self, connection):
290 """
291 Return True if the model can/should be migrated on the `connection`.
292 `connection` can be either a real connection or a connection alias.
293 """
294 if self.swapped or not self.managed:
295 return False
296 if isinstance(connection, str):
297 connection = connections[connection]
298 if self.required_db_vendor:
299 return self.required_db_vendor == connection.vendor
300 if self.required_db_features:
301 return all(
302 getattr(connection.features, feat, False)
303 for feat in self.required_db_features
304 )
305 return True
306
307 @property
308 def swapped(self):
309 """
310 Has this model been swapped out for another? If so, return the model
311 name of the replacement; otherwise, return None.
312
313 For historical reasons, model name lookups using get_model() are
314 case insensitive, so we make sure we are case insensitive here.
315 """
316 if self.swappable:
317 swapped_for = getattr(settings, self.swappable, None)
318 if swapped_for:
319 try:
320 swapped_label, swapped_object = swapped_for.split(".")
321 except ValueError:
322 # setting not in the format package_label.model_name
323 # raising ImproperlyConfigured here causes problems with
324 # test cleanup code - instead it is raised in get_user_model
325 # or as part of validation.
326 return swapped_for
327
328 if f"{swapped_label}.{swapped_object.lower()}" != self.label_lower:
329 return swapped_for
330 return None
331
332 @cached_property
333 def managers(self):
334 managers = []
335 seen_managers = set()
336 bases = (b for b in self.model.mro() if hasattr(b, "_meta"))
337 for depth, base in enumerate(bases):
338 for manager in base._meta.local_managers:
339 if manager.name in seen_managers:
340 continue
341
342 manager = copy.copy(manager)
343 manager.model = self.model
344 seen_managers.add(manager.name)
345 managers.append((depth, manager.creation_counter, manager))
346
347 return make_immutable_fields_list(
348 "managers",
349 (m[2] for m in sorted(managers)),
350 )
351
352 @cached_property
353 def managers_map(self):
354 return {manager.name: manager for manager in self.managers}
355
356 @cached_property
357 def base_manager(self):
358 base_manager_name = self.base_manager_name
359 if not base_manager_name:
360 # Get the first parent's base_manager_name if there's one.
361 for parent in self.model.mro()[1:]:
362 if hasattr(parent, "_meta"):
363 if parent._base_manager.name != "_base_manager":
364 base_manager_name = parent._base_manager.name
365 break
366
367 if base_manager_name:
368 try:
369 return self.managers_map[base_manager_name]
370 except KeyError:
371 raise ValueError(
372 f"{self.object_name} has no manager named {base_manager_name!r}"
373 )
374
375 manager = Manager()
376 manager.name = "_base_manager"
377 manager.model = self.model
378 manager.auto_created = True
379 return manager
380
381 @cached_property
382 def default_manager(self):
383 default_manager_name = self.default_manager_name
384 if not default_manager_name and not self.local_managers:
385 # Get the first parent's default_manager_name if there's one.
386 for parent in self.model.mro()[1:]:
387 if hasattr(parent, "_meta"):
388 default_manager_name = parent._meta.default_manager_name
389 break
390
391 if default_manager_name:
392 try:
393 return self.managers_map[default_manager_name]
394 except KeyError:
395 raise ValueError(
396 f"{self.object_name} has no manager named {default_manager_name!r}"
397 )
398
399 if self.managers:
400 return self.managers[0]
401
402 @cached_property
403 def fields(self):
404 """
405 Return a list of all forward fields on the model and its parents,
406 excluding ManyToManyFields.
407
408 Private API intended only to be used by Plain itself; get_fields()
409 combined with filtering of field properties is the public API for
410 obtaining this field list.
411 """
412
413 # For legacy reasons, the fields property should only contain forward
414 # fields that are not private or with a m2m cardinality. Therefore we
415 # pass these three filters as filters to the generator.
416 # The third lambda is a longwinded way of checking f.related_model - we don't
417 # use that property directly because related_model is a cached property,
418 # and all the models may not have been loaded yet; we don't want to cache
419 # the string reference to the related_model.
420 def is_not_an_m2m_field(f):
421 return not (f.is_relation and f.many_to_many)
422
423 def is_not_a_generic_relation(f):
424 return not (f.is_relation and f.one_to_many)
425
426 def is_not_a_generic_foreign_key(f):
427 return not (
428 f.is_relation
429 and f.many_to_one
430 and not (hasattr(f.remote_field, "model") and f.remote_field.model)
431 )
432
433 return make_immutable_fields_list(
434 "fields",
435 (
436 f
437 for f in self._get_fields(reverse=False)
438 if is_not_an_m2m_field(f)
439 and is_not_a_generic_relation(f)
440 and is_not_a_generic_foreign_key(f)
441 ),
442 )
443
444 @cached_property
445 def concrete_fields(self):
446 """
447 Return a list of all concrete fields on the model and its parents.
448
449 Private API intended only to be used by Plain itself; get_fields()
450 combined with filtering of field properties is the public API for
451 obtaining this field list.
452 """
453 return make_immutable_fields_list(
454 "concrete_fields", (f for f in self.fields if f.concrete)
455 )
456
457 @cached_property
458 def local_concrete_fields(self):
459 """
460 Return a list of all concrete fields on the model.
461
462 Private API intended only to be used by Plain itself; get_fields()
463 combined with filtering of field properties is the public API for
464 obtaining this field list.
465 """
466 return make_immutable_fields_list(
467 "local_concrete_fields", (f for f in self.local_fields if f.concrete)
468 )
469
470 @cached_property
471 def many_to_many(self):
472 """
473 Return a list of all many to many fields on the model and its parents.
474
475 Private API intended only to be used by Plain itself; get_fields()
476 combined with filtering of field properties is the public API for
477 obtaining this list.
478 """
479 return make_immutable_fields_list(
480 "many_to_many",
481 (
482 f
483 for f in self._get_fields(reverse=False)
484 if f.is_relation and f.many_to_many
485 ),
486 )
487
488 @cached_property
489 def related_objects(self):
490 """
491 Return all related objects pointing to the current model. The related
492 objects can come from a one-to-one, one-to-many, or many-to-many field
493 relation type.
494
495 Private API intended only to be used by Plain itself; get_fields()
496 combined with filtering of field properties is the public API for
497 obtaining this field list.
498 """
499 all_related_fields = self._get_fields(
500 forward=False, reverse=True, include_hidden=True
501 )
502 return make_immutable_fields_list(
503 "related_objects",
504 (
505 obj
506 for obj in all_related_fields
507 if not obj.hidden or obj.field.many_to_many
508 ),
509 )
510
511 @cached_property
512 def _forward_fields_map(self):
513 res = {}
514 fields = self._get_fields(reverse=False)
515 for field in fields:
516 res[field.name] = field
517 # Due to the way Plain's internals work, get_field() should also
518 # be able to fetch a field by attname. In the case of a concrete
519 # field with relation, includes the *_id name too
520 try:
521 res[field.attname] = field
522 except AttributeError:
523 pass
524 return res
525
526 @cached_property
527 def fields_map(self):
528 res = {}
529 fields = self._get_fields(forward=False, include_hidden=True)
530 for field in fields:
531 res[field.name] = field
532 # Due to the way Plain's internals work, get_field() should also
533 # be able to fetch a field by attname. In the case of a concrete
534 # field with relation, includes the *_id name too
535 try:
536 res[field.attname] = field
537 except AttributeError:
538 pass
539 return res
540
541 def get_field(self, field_name):
542 """
543 Return a field instance given the name of a forward or reverse field.
544 """
545 try:
546 # In order to avoid premature loading of the relation tree
547 # (expensive) we prefer checking if the field is a forward field.
548 return self._forward_fields_map[field_name]
549 except KeyError:
550 # If the app registry is not ready, reverse fields are
551 # unavailable, therefore we throw a FieldDoesNotExist exception.
552 if not self.packages.models_ready:
553 raise FieldDoesNotExist(
554 "{} has no field named '{}'. The app cache isn't ready yet, "
555 "so if this is an auto-created related field, it won't "
556 "be available yet.".format(self.object_name, field_name)
557 )
558
559 try:
560 # Retrieve field instance by name from cached or just-computed
561 # field map.
562 return self.fields_map[field_name]
563 except KeyError:
564 raise FieldDoesNotExist(
565 f"{self.object_name} has no field named '{field_name}'"
566 )
567
568 def get_base_chain(self, model):
569 """
570 Return a list of parent classes leading to `model` (ordered from
571 closest to most distant ancestor). This has to handle the case where
572 `model` is a grandparent or even more distant relation.
573 """
574 if not self.parents:
575 return []
576 if model in self.parents:
577 return [model]
578 for parent in self.parents:
579 res = parent._meta.get_base_chain(model)
580 if res:
581 res.insert(0, parent)
582 return res
583 return []
584
585 def get_parent_list(self):
586 """
587 Return all the ancestors of this model as a list ordered by MRO.
588 Useful for determining if something is an ancestor, regardless of lineage.
589 """
590 result = OrderedSet(self.parents)
591 for parent in self.parents:
592 for ancestor in parent._meta.get_parent_list():
593 result.add(ancestor)
594 return list(result)
595
596 def get_ancestor_link(self, ancestor):
597 """
598 Return the field on the current model which points to the given
599 "ancestor". This is possible an indirect link (a pointer to a parent
600 model, which points, eventually, to the ancestor). Used when
601 constructing table joins for model inheritance.
602
603 Return None if the model isn't an ancestor of this one.
604 """
605 if ancestor in self.parents:
606 return self.parents[ancestor]
607 for parent in self.parents:
608 # Tries to get a link field from the immediate parent
609 parent_link = parent._meta.get_ancestor_link(ancestor)
610 if parent_link:
611 # In case of a proxied model, the first link
612 # of the chain to the ancestor is that parent
613 # links
614 return self.parents[parent] or parent_link
615
616 def get_path_to_parent(self, parent):
617 """
618 Return a list of PathInfos containing the path from the current
619 model to the parent model, or an empty list if parent is not a
620 parent of the current model.
621 """
622 if self.model is parent:
623 return []
624 # Skip the chain of proxy to the concrete proxied model.
625 proxied_model = self.concrete_model
626 path = []
627 opts = self
628 for int_model in self.get_base_chain(parent):
629 if int_model is proxied_model:
630 opts = int_model._meta
631 else:
632 final_field = opts.parents[int_model]
633 targets = (final_field.remote_field.get_related_field(),)
634 opts = int_model._meta
635 path.append(
636 PathInfo(
637 from_opts=final_field.model._meta,
638 to_opts=opts,
639 target_fields=targets,
640 join_field=final_field,
641 m2m=False,
642 direct=True,
643 filtered_relation=None,
644 )
645 )
646 return path
647
648 def get_path_from_parent(self, parent):
649 """
650 Return a list of PathInfos containing the path from the parent
651 model to the current model, or an empty list if parent is not a
652 parent of the current model.
653 """
654 if self.model is parent:
655 return []
656 model = self.concrete_model
657 # Get a reversed base chain including both the current and parent
658 # models.
659 chain = model._meta.get_base_chain(parent)
660 chain.reverse()
661 chain.append(model)
662 # Construct a list of the PathInfos between models in chain.
663 path = []
664 for i, ancestor in enumerate(chain[:-1]):
665 child = chain[i + 1]
666 link = child._meta.get_ancestor_link(ancestor)
667 path.extend(link.reverse_path_infos)
668 return path
669
670 def _populate_directed_relation_graph(self):
671 """
672 This method is used by each model to find its reverse objects. As this
673 method is very expensive and is accessed frequently (it looks up every
674 field in a model, in every app), it is computed on first access and then
675 is set as a property on every model.
676 """
677 related_objects_graph = defaultdict(list)
678
679 all_models = self.packages.get_models(include_auto_created=True)
680 for model in all_models:
681 opts = model._meta
682 # Abstract model's fields are copied to child models, hence we will
683 # see the fields from the child models.
684 if opts.abstract:
685 continue
686 fields_with_relations = (
687 f
688 for f in opts._get_fields(reverse=False, include_parents=False)
689 if f.is_relation and f.related_model is not None
690 )
691 for f in fields_with_relations:
692 if not isinstance(f.remote_field.model, str):
693 remote_label = f.remote_field.model._meta.concrete_model._meta.label
694 related_objects_graph[remote_label].append(f)
695
696 for model in all_models:
697 # Set the relation_tree using the internal __dict__. In this way
698 # we avoid calling the cached property. In attribute lookup,
699 # __dict__ takes precedence over a data descriptor (such as
700 # @cached_property). This means that the _meta._relation_tree is
701 # only called if related_objects is not in __dict__.
702 related_objects = related_objects_graph[
703 model._meta.concrete_model._meta.label
704 ]
705 model._meta.__dict__["_relation_tree"] = related_objects
706 # It seems it is possible that self is not in all_models, so guard
707 # against that with default for get().
708 return self.__dict__.get("_relation_tree", EMPTY_RELATION_TREE)
709
710 @cached_property
711 def _relation_tree(self):
712 return self._populate_directed_relation_graph()
713
714 def _expire_cache(self, forward=True, reverse=True):
715 # This method is usually called by packages.cache_clear(), when the
716 # registry is finalized, or when a new field is added.
717 if forward:
718 for cache_key in self.FORWARD_PROPERTIES:
719 if cache_key in self.__dict__:
720 delattr(self, cache_key)
721 if reverse and not self.abstract:
722 for cache_key in self.REVERSE_PROPERTIES:
723 if cache_key in self.__dict__:
724 delattr(self, cache_key)
725 self._get_fields_cache = {}
726
727 def get_fields(self, include_parents=True, include_hidden=False):
728 """
729 Return a list of fields associated to the model. By default, include
730 forward and reverse fields, fields derived from inheritance, but not
731 hidden fields. The returned fields can be changed using the parameters:
732
733 - include_parents: include fields derived from inheritance
734 - include_hidden: include fields that have a related_name that
735 starts with a "+"
736 """
737 if include_parents is False:
738 include_parents = PROXY_PARENTS
739 return self._get_fields(
740 include_parents=include_parents, include_hidden=include_hidden
741 )
742
743 def _get_fields(
744 self,
745 forward=True,
746 reverse=True,
747 include_parents=True,
748 include_hidden=False,
749 seen_models=None,
750 ):
751 """
752 Internal helper function to return fields of the model.
753 * If forward=True, then fields defined on this model are returned.
754 * If reverse=True, then relations pointing to this model are returned.
755 * If include_hidden=True, then fields with is_hidden=True are returned.
756 * The include_parents argument toggles if fields from parent models
757 should be included. It has three values: True, False, and
758 PROXY_PARENTS. When set to PROXY_PARENTS, the call will return all
759 fields defined for the current model or any of its parents in the
760 parent chain to the model's concrete model.
761 """
762 if include_parents not in (True, False, PROXY_PARENTS):
763 raise TypeError(f"Invalid argument for include_parents: {include_parents}")
764 # This helper function is used to allow recursion in ``get_fields()``
765 # implementation and to provide a fast way for Plain's internals to
766 # access specific subsets of fields.
767
768 # We must keep track of which models we have already seen. Otherwise we
769 # could include the same field multiple times from different models.
770 topmost_call = seen_models is None
771 if topmost_call:
772 seen_models = set()
773 seen_models.add(self.model)
774
775 # Creates a cache key composed of all arguments
776 cache_key = (forward, reverse, include_parents, include_hidden, topmost_call)
777
778 try:
779 # In order to avoid list manipulation. Always return a shallow copy
780 # of the results.
781 return self._get_fields_cache[cache_key]
782 except KeyError:
783 pass
784
785 fields = []
786 # Recursively call _get_fields() on each parent, with the same
787 # options provided in this call.
788 if include_parents is not False:
789 for parent in self.parents:
790 # In diamond inheritance it is possible that we see the same
791 # model from two different routes. In that case, avoid adding
792 # fields from the same parent again.
793 if parent in seen_models:
794 continue
795 if (
796 parent._meta.concrete_model != self.concrete_model
797 and include_parents == PROXY_PARENTS
798 ):
799 continue
800 for obj in parent._meta._get_fields(
801 forward=forward,
802 reverse=reverse,
803 include_parents=include_parents,
804 include_hidden=include_hidden,
805 seen_models=seen_models,
806 ):
807 if (
808 not getattr(obj, "parent_link", False)
809 or obj.model == self.concrete_model
810 ):
811 fields.append(obj)
812 if reverse:
813 # Tree is computed once and cached until the app cache is expired.
814 # It is composed of a list of fields pointing to the current model
815 # from other models.
816 all_fields = self._relation_tree
817 for field in all_fields:
818 # If hidden fields should be included or the relation is not
819 # intentionally hidden, add to the fields dict.
820 if include_hidden or not field.remote_field.hidden:
821 fields.append(field.remote_field)
822
823 if forward:
824 fields += self.local_fields
825 fields += self.local_many_to_many
826 # Private fields are recopied to each child model, and they get a
827 # different model as field.model in each child. Hence we have to
828 # add the private fields separately from the topmost call. If we
829 # did this recursively similar to local_fields, we would get field
830 # instances with field.model != self.model.
831 if topmost_call:
832 fields += self.private_fields
833
834 # In order to avoid list manipulation. Always
835 # return a shallow copy of the results
836 fields = make_immutable_fields_list("get_fields()", fields)
837
838 # Store result into cache for later access
839 self._get_fields_cache[cache_key] = fields
840 return fields
841
842 @cached_property
843 def total_unique_constraints(self):
844 """
845 Return a list of total unique constraints. Useful for determining set
846 of fields guaranteed to be unique for all rows.
847 """
848 return [
849 constraint
850 for constraint in self.constraints
851 if (
852 isinstance(constraint, UniqueConstraint)
853 and constraint.condition is None
854 and not constraint.contains_expressions
855 )
856 ]
857
858 @cached_property
859 def _property_names(self):
860 """Return a set of the names of the properties defined on the model."""
861 names = []
862 for name in dir(self.model):
863 attr = inspect.getattr_static(self.model, name)
864 if isinstance(attr, property):
865 names.append(name)
866 return frozenset(names)
867
868 @cached_property
869 def _non_pk_concrete_field_names(self):
870 """
871 Return a set of the non-pk concrete field names defined on the model.
872 """
873 names = []
874 for field in self.concrete_fields:
875 if not field.primary_key:
876 names.append(field.name)
877 if field.name != field.attname:
878 names.append(field.attname)
879 return frozenset(names)
880
881 @cached_property
882 def db_returning_fields(self):
883 """
884 Private API intended only to be used by Plain itself.
885 Fields to be returned after a database insert.
886 """
887 return [
888 field
889 for field in self._get_fields(
890 forward=True, reverse=False, include_parents=PROXY_PARENTS
891 )
892 if getattr(field, "db_returning", False)
893 ]