Plain is headed towards 1.0! Subscribe for development updates →

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

Migration docs

Fields

Field docs

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        ]