plain.models 0.48.0__py3-none-any.whl → 0.49.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
plain/models/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # plain-models changelog
2
2
 
3
+ ## [0.49.0](https://github.com/dropseed/plain/releases/plain-models@0.49.0) (2025-09-29)
4
+
5
+ ### What's changed
6
+
7
+ - Model exceptions (`FieldDoesNotExist`, `FieldError`, `ObjectDoesNotExist`, `MultipleObjectsReturned`, `EmptyResultSet`, `FullResultSet`) have been moved from `plain.exceptions` to `plain.models.exceptions` ([1c02564](https://github.com/dropseed/plain/commit/1c02564561))
8
+ - The `get_FOO_display()` methods for fields with choices have been replaced with a single `get_field_display(field_name)` method ([e796e71](https://github.com/dropseed/plain/commit/e796e71e02))
9
+ - The `get_next_by_*` and `get_previous_by_*` methods for date fields have been removed ([3a5b8a8](https://github.com/dropseed/plain/commit/3a5b8a89d1))
10
+ - The `id` primary key field is now defined directly on the Model base class instead of being added dynamically via Options ([e164dc7](https://github.com/dropseed/plain/commit/e164dc7982))
11
+ - Model `DoesNotExist` and `MultipleObjectsReturned` exceptions now use descriptors for better performance ([8f54ea3](https://github.com/dropseed/plain/commit/8f54ea3a62))
12
+
13
+ ### Upgrade instructions
14
+
15
+ - Update imports for model exceptions from `plain.exceptions` to `plain.models.exceptions` (e.g., `from plain.exceptions import ObjectDoesNotExist` becomes `from plain.models.exceptions import ObjectDoesNotExist`)
16
+ - Replace any usage of `instance.get_FOO_display()` with `instance.get_field_display("FOO")` where FOO is the field name
17
+ - Remove any usage of `get_next_by_*` and `get_previous_by_*` methods - use QuerySet ordering instead (e.g., `Model.query.filter(date__gt=obj.date).order_by("date").first()`)
18
+
3
19
  ## [0.48.0](https://github.com/dropseed/plain/releases/plain-models@0.48.0) (2025-09-26)
4
20
 
5
21
  ### What's changed
plain/models/__init__.py CHANGED
@@ -1,5 +1,3 @@
1
- from plain.exceptions import ObjectDoesNotExist
2
-
3
1
  from . import (
4
2
  preflight, # noqa
5
3
  )
@@ -78,7 +76,6 @@ from .fields.reverse_related import ( # isort:skip
78
76
 
79
77
  __all__ = aggregates_all + constraints_all + enums_all + fields_all + indexes_all
80
78
  __all__ += [
81
- "ObjectDoesNotExist",
82
79
  "CASCADE",
83
80
  "DO_NOTHING",
84
81
  "PROTECT",
@@ -2,7 +2,7 @@
2
2
  Classes to represent the definitions of aggregate functions.
3
3
  """
4
4
 
5
- from plain.exceptions import FieldError, FullResultSet
5
+ from plain.models.exceptions import FieldError, FullResultSet
6
6
  from plain.models.expressions import Case, Func, Star, Value, When
7
7
  from plain.models.fields import IntegerField
8
8
  from plain.models.functions.comparison import Coalesce
@@ -1,4 +1,4 @@
1
- from plain.exceptions import FieldError, FullResultSet
1
+ from plain.models.exceptions import FieldError, FullResultSet
2
2
  from plain.models.expressions import Col
3
3
  from plain.models.sql import compiler
4
4
 
@@ -4,10 +4,10 @@ import uuid
4
4
  from functools import cached_property, lru_cache
5
5
 
6
6
  from plain import models
7
- from plain.exceptions import FieldError
8
7
  from plain.models.backends.base.operations import BaseDatabaseOperations
9
8
  from plain.models.constants import OnConflict
10
9
  from plain.models.db import DatabaseError, NotSupportedError
10
+ from plain.models.exceptions import FieldError
11
11
  from plain.models.expressions import Col
12
12
  from plain.utils import timezone
13
13
  from plain.utils.dateparse import parse_date, parse_datetime, parse_time
plain/models/base.py CHANGED
@@ -4,13 +4,7 @@ import warnings
4
4
  from itertools import chain
5
5
 
6
6
  import plain.runtime
7
- from plain.exceptions import (
8
- NON_FIELD_ERRORS,
9
- FieldDoesNotExist,
10
- MultipleObjectsReturned,
11
- ObjectDoesNotExist,
12
- ValidationError,
13
- )
7
+ from plain.exceptions import NON_FIELD_ERRORS, ValidationError
14
8
  from plain.models import models_registry, transaction
15
9
  from plain.models.constants import LOOKUP_SEP
16
10
  from plain.models.constraints import CheckConstraint, UniqueConstraint
@@ -20,8 +14,13 @@ from plain.models.db import (
20
14
  db_connection,
21
15
  )
22
16
  from plain.models.deletion import Collector
17
+ from plain.models.exceptions import (
18
+ DoesNotExistDescriptor,
19
+ FieldDoesNotExist,
20
+ MultipleObjectsReturnedDescriptor,
21
+ )
23
22
  from plain.models.expressions import RawSQL, Value
24
- from plain.models.fields import NOT_PROVIDED
23
+ from plain.models.fields import NOT_PROVIDED, PrimaryKeyField
25
24
  from plain.models.fields.reverse_related import ForeignObjectRel
26
25
  from plain.models.options import Options
27
26
  from plain.models.query import F, Q, QuerySet
@@ -42,11 +41,6 @@ class Deferred:
42
41
  DEFERRED = Deferred()
43
42
 
44
43
 
45
- def _has_contribute_to_class(value):
46
- # Only call contribute_to_class() if it's bound.
47
- return not inspect.isclass(value) and hasattr(value, "contribute_to_class")
48
-
49
-
50
44
  class ModelBase(type):
51
45
  """Metaclass for all models."""
52
46
 
@@ -71,7 +65,6 @@ class ModelBase(type):
71
65
  new_class = super().__new__(cls, name, bases, attrs, **kwargs)
72
66
 
73
67
  new_class._setup_meta()
74
- new_class._add_exceptions()
75
68
 
76
69
  # Now go back over all the attrs on this class see if they have a contribute_to_class() method.
77
70
  # Attributes with contribute_to_class are fields and meta options.
@@ -79,7 +72,9 @@ class ModelBase(type):
79
72
  if attr_name.startswith("_"):
80
73
  continue
81
74
 
82
- if _has_contribute_to_class(attr_value):
75
+ if not inspect.isclass(attr_value) and hasattr(
76
+ attr_value, "contribute_to_class"
77
+ ):
83
78
  if attr_name not in attrs:
84
79
  # If the field came from an inherited class/mixin,
85
80
  # we need to make a copy of it to avoid altering the
@@ -87,25 +82,17 @@ class ModelBase(type):
87
82
  field = copy.deepcopy(attr_value)
88
83
  else:
89
84
  field = attr_value
90
- new_class.add_to_class(attr_name, field)
91
-
92
- new_class._meta.concrete_model = new_class
93
-
94
- # Copy indexes so that index names are unique when models extend another class.
95
- new_class._meta.indexes = [
96
- copy.deepcopy(idx) for idx in new_class._meta.indexes
97
- ]
85
+ field.contribute_to_class(new_class, attr_name)
98
86
 
99
- new_class._prepare()
87
+ # Set the name of _meta.indexes. This can't be done in
88
+ # Options.contribute_to_class() because fields haven't been added to
89
+ # the model at that point.
90
+ for index in new_class._meta.indexes:
91
+ if not index.name:
92
+ index.set_name_with_model(new_class)
100
93
 
101
94
  return new_class
102
95
 
103
- def add_to_class(cls, name, value):
104
- if _has_contribute_to_class(value):
105
- value.contribute_to_class(cls, name)
106
- else:
107
- setattr(cls, name, value)
108
-
109
96
  def _setup_meta(cls):
110
97
  name = cls.__name__
111
98
  module = cls.__module__
@@ -127,45 +114,7 @@ class ModelBase(type):
127
114
  else:
128
115
  package_label = package_config.package_label
129
116
 
130
- cls.add_to_class("_meta", Options(meta, package_label))
131
-
132
- def _add_exceptions(cls):
133
- cls.DoesNotExist = type(
134
- "DoesNotExist",
135
- (ObjectDoesNotExist,),
136
- {
137
- "__module__": cls.__module__,
138
- "__qualname__": f"{cls.__qualname__}.DoesNotExist",
139
- },
140
- )
141
-
142
- cls.MultipleObjectsReturned = type(
143
- "MultipleObjectsReturned",
144
- (MultipleObjectsReturned,),
145
- {
146
- "__module__": cls.__module__,
147
- "__qualname__": f"{cls.__qualname__}.MultipleObjectsReturned",
148
- },
149
- )
150
-
151
- def _prepare(cls):
152
- """Create some methods once self._meta has been populated."""
153
- opts = cls._meta
154
- opts._prepare(cls)
155
-
156
- # Give the class a docstring -- its definition.
157
- if cls.__doc__ is None:
158
- cls.__doc__ = "{}({})".format(
159
- cls.__name__,
160
- ", ".join(f.name for f in opts.fields),
161
- )
162
-
163
- # Set the name of _meta.indexes. This can't be done in
164
- # Options.contribute_to_class() because fields haven't been added to
165
- # the model at that point.
166
- for index in cls._meta.indexes:
167
- if not index.name:
168
- index.set_name_with_model(cls)
117
+ Options(meta, package_label).contribute_to_class(cls, "_meta")
169
118
 
170
119
  @property
171
120
  def query(cls) -> QuerySet:
@@ -194,8 +143,13 @@ class ModelState:
194
143
 
195
144
  class Model(metaclass=ModelBase):
196
145
  _meta: Options
197
- DoesNotExist: type[ObjectDoesNotExist]
198
- MultipleObjectsReturned: type[MultipleObjectsReturned]
146
+
147
+ # Use descriptors for exception classes instead of metaclass generation
148
+ DoesNotExist = DoesNotExistDescriptor()
149
+ MultipleObjectsReturned = MultipleObjectsReturnedDescriptor()
150
+
151
+ # Every model gets an automatic id field
152
+ id = PrimaryKeyField()
199
153
 
200
154
  def __init__(self, *args, **kwargs):
201
155
  # Alias some things as locals to avoid repeat global lookups
@@ -327,7 +281,7 @@ class Model(metaclass=ModelBase):
327
281
  def __eq__(self, other):
328
282
  if not isinstance(other, Model):
329
283
  return NotImplemented
330
- if self._meta.concrete_model != other._meta.concrete_model:
284
+ if self.__class__ != other.__class__:
331
285
  return False
332
286
  my_id = self.id
333
287
  if my_id is None:
@@ -711,34 +665,20 @@ class Model(metaclass=ModelBase):
711
665
  collector.collect([self])
712
666
  return collector.delete()
713
667
 
714
- def _get_FIELD_display(self, field):
668
+ def get_field_display(self, field):
669
+ """Get the display value for a field, especially useful for fields with choices."""
715
670
  value = getattr(self, field.attname)
671
+
672
+ # If field has no choices, just return the value as string
673
+ if not hasattr(field, "flatchoices") or not field.flatchoices:
674
+ return force_str(value, strings_only=True)
675
+
676
+ # For fields with choices, look up the display value
716
677
  choices_dict = dict(make_hashable(field.flatchoices))
717
- # force_str() to coerce lazy strings.
718
678
  return force_str(
719
679
  choices_dict.get(make_hashable(value), value), strings_only=True
720
680
  )
721
681
 
722
- def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
723
- if not self.id:
724
- raise ValueError("get_next/get_previous cannot be used on unsaved objects.")
725
- op = "gt" if is_next else "lt"
726
- order = "" if is_next else "-"
727
- param = getattr(self, field.attname)
728
- q = Q.create([(field.name, param), (f"id__{op}", self.id)], connector=Q.AND)
729
- q = Q.create([q, (f"{field.name}__{op}", param)], connector=Q.OR)
730
- qs = (
731
- self.__class__.query.filter(**kwargs)
732
- .filter(q)
733
- .order_by(f"{order}{field.name}", f"{order}id")
734
- )
735
- try:
736
- return qs[0]
737
- except IndexError:
738
- raise self.DoesNotExist(
739
- f"{self.__class__._meta.object_name} matching query does not exist."
740
- )
741
-
742
682
  def _get_field_value_map(self, meta, exclude=None):
743
683
  if exclude is None:
744
684
  exclude = set()
@@ -1,7 +1,8 @@
1
1
  from enum import Enum
2
2
  from types import NoneType
3
3
 
4
- from plain.exceptions import FieldError, ValidationError
4
+ from plain.exceptions import ValidationError
5
+ from plain.models.exceptions import FieldError
5
6
  from plain.models.expressions import Exists, ExpressionList, F, OrderBy
6
7
  from plain.models.indexes import IndexExpression
7
8
  from plain.models.lookups import Exact
plain/models/deletion.py CHANGED
@@ -139,9 +139,7 @@ class Collector:
139
139
  def add_dependency(self, model, dependency, reverse_dependency=False):
140
140
  if reverse_dependency:
141
141
  model, dependency = dependency, model
142
- self.dependencies[model._meta.concrete_model].add(
143
- dependency._meta.concrete_model
144
- )
142
+ self.dependencies[model].add(dependency)
145
143
  self.data.setdefault(dependency, self.data.default_factory())
146
144
 
147
145
  def add_field_update(self, field, value, objs):
@@ -363,10 +361,10 @@ class Collector:
363
361
  for model in models:
364
362
  if model in sorted_models:
365
363
  continue
366
- dependencies = self.dependencies.get(model._meta.concrete_model)
364
+ dependencies = self.dependencies.get(model)
367
365
  if not (dependencies and dependencies.difference(concrete_models)):
368
366
  sorted_models.append(model)
369
- concrete_models.add(model._meta.concrete_model)
367
+ concrete_models.add(model)
370
368
  found = True
371
369
  if not found:
372
370
  return
@@ -1,3 +1,111 @@
1
+ from typing import Any
2
+
3
+ # MARK: Database Query Exceptions
4
+
5
+
6
+ class EmptyResultSet(Exception):
7
+ """A database query predicate is impossible."""
8
+
9
+ pass
10
+
11
+
12
+ class FullResultSet(Exception):
13
+ """A database query predicate is matches everything."""
14
+
15
+ pass
16
+
17
+
18
+ # MARK: Model and Field Errors
19
+
20
+
21
+ class FieldDoesNotExist(Exception):
22
+ """The requested model field does not exist"""
23
+
24
+ pass
25
+
26
+
27
+ class FieldError(Exception):
28
+ """Some kind of problem with a model field."""
29
+
30
+ pass
31
+
32
+
33
+ class ObjectDoesNotExist(Exception):
34
+ """The requested object does not exist"""
35
+
36
+ pass
37
+
38
+
39
+ class MultipleObjectsReturned(Exception):
40
+ """The query returned multiple objects when only one was expected."""
41
+
42
+ pass
43
+
44
+
45
+ # MARK: Model Exception Descriptors
46
+
47
+
48
+ class DoesNotExistDescriptor:
49
+ """Descriptor that creates a unique DoesNotExist exception class per model."""
50
+
51
+ def __init__(self) -> None:
52
+ self._exceptions_by_class: dict[type, type[ObjectDoesNotExist]] = {}
53
+
54
+ def __get__(self, instance: Any, owner: type | None) -> type[ObjectDoesNotExist]:
55
+ if owner is None:
56
+ return ObjectDoesNotExist # Return base class as fallback
57
+
58
+ # Create a unique exception class for this model if we haven't already
59
+ if owner not in self._exceptions_by_class:
60
+ exc_class = type(
61
+ "DoesNotExist",
62
+ (ObjectDoesNotExist,),
63
+ {
64
+ "__module__": owner.__module__,
65
+ "__qualname__": f"{owner.__qualname__}.DoesNotExist",
66
+ },
67
+ )
68
+ self._exceptions_by_class[owner] = exc_class
69
+
70
+ return self._exceptions_by_class[owner]
71
+
72
+ def __set__(self, instance: Any, value: Any) -> None:
73
+ raise AttributeError("Cannot set DoesNotExist")
74
+
75
+
76
+ class MultipleObjectsReturnedDescriptor:
77
+ """Descriptor that creates a unique MultipleObjectsReturned exception class per model."""
78
+
79
+ def __init__(self) -> None:
80
+ self._exceptions_by_class: dict[type, type[MultipleObjectsReturned]] = {}
81
+
82
+ def __get__(
83
+ self, instance: Any, owner: type | None
84
+ ) -> type[MultipleObjectsReturned]:
85
+ if owner is None:
86
+ return MultipleObjectsReturned # Return base class as fallback
87
+
88
+ # Create a unique exception class for this model if we haven't already
89
+ if owner not in self._exceptions_by_class:
90
+ exc_class = type(
91
+ "MultipleObjectsReturned",
92
+ (MultipleObjectsReturned,),
93
+ {
94
+ "__module__": owner.__module__,
95
+ "__qualname__": f"{owner.__qualname__}.MultipleObjectsReturned",
96
+ },
97
+ )
98
+ self._exceptions_by_class[owner] = exc_class
99
+
100
+ return self._exceptions_by_class[owner]
101
+
102
+ def __set__(self, instance: Any, value: Any) -> None:
103
+ raise AttributeError("Cannot set MultipleObjectsReturned")
104
+
105
+
106
+ # MARK: Database Exceptions (PEP-249)
107
+
108
+
1
109
  class Error(Exception):
2
110
  pass
3
111
 
@@ -8,7 +8,6 @@ from functools import cached_property
8
8
  from types import NoneType
9
9
  from uuid import UUID
10
10
 
11
- from plain.exceptions import EmptyResultSet, FieldError, FullResultSet
12
11
  from plain.models import fields
13
12
  from plain.models.constants import LOOKUP_SEP
14
13
  from plain.models.db import (
@@ -16,6 +15,7 @@ from plain.models.db import (
16
15
  NotSupportedError,
17
16
  db_connection,
18
17
  )
18
+ from plain.models.exceptions import EmptyResultSet, FieldError, FullResultSet
19
19
  from plain.models.query_utils import Q
20
20
  from plain.utils.deconstruct import deconstructible
21
21
  from plain.utils.hashable import make_hashable
@@ -7,7 +7,7 @@ import operator
7
7
  import uuid
8
8
  import warnings
9
9
  from base64 import b64decode, b64encode
10
- from functools import cached_property, partialmethod, total_ordering
10
+ from functools import cached_property, total_ordering
11
11
 
12
12
  from plain import exceptions, validators
13
13
  from plain.models.constants import LOOKUP_SEP
@@ -759,29 +759,15 @@ class Field(RegisterLookupMixin):
759
759
  self.attname, self.column = self.get_attname_column()
760
760
  self.concrete = self.column is not None
761
761
 
762
- def contribute_to_class(self, cls, name, private_only=False):
762
+ def contribute_to_class(self, cls, name):
763
763
  """
764
764
  Register the field with the model class it belongs to.
765
-
766
- If private_only is True, create a separate instance of this field
767
- for every subclass of cls, even if cls is not an abstract model.
768
765
  """
769
766
  self.set_attributes_from_name(name)
770
767
  self.model = cls
771
- cls._meta.add_field(self, private=private_only)
768
+ cls._meta.add_field(self)
772
769
  if self.column:
773
770
  setattr(cls, self.attname, self.descriptor_class(self))
774
- if self.choices is not None:
775
- # Don't override a get_FOO_display() method defined explicitly on
776
- # this class, but don't check methods derived from inheritance, to
777
- # allow overriding inherited choices. For more complex inheritance
778
- # structures users should override contribute_to_class().
779
- if f"get_{self.name}_display" not in cls.__dict__:
780
- setattr(
781
- cls,
782
- f"get_{self.name}_display",
783
- partialmethod(cls._get_FIELD_display, field=self),
784
- )
785
771
 
786
772
  def get_attname(self):
787
773
  return self.name
@@ -1200,24 +1186,6 @@ class DateField(DateTimeCheckMixin, Field):
1200
1186
  else:
1201
1187
  return super().pre_save(model_instance, add)
1202
1188
 
1203
- def contribute_to_class(self, cls, name, **kwargs):
1204
- super().contribute_to_class(cls, name, **kwargs)
1205
- if not self.allow_null:
1206
- setattr(
1207
- cls,
1208
- f"get_next_by_{self.name}",
1209
- partialmethod(
1210
- cls._get_next_or_previous_by_FIELD, field=self, is_next=True
1211
- ),
1212
- )
1213
- setattr(
1214
- cls,
1215
- f"get_previous_by_{self.name}",
1216
- partialmethod(
1217
- cls._get_next_or_previous_by_FIELD, field=self, is_next=False
1218
- ),
1219
- )
1220
-
1221
1189
  def get_prep_value(self, value):
1222
1190
  value = super().get_prep_value(value)
1223
1191
  return self.to_python(value)
@@ -1319,9 +1287,6 @@ class DateTimeField(DateField):
1319
1287
  else:
1320
1288
  return super().pre_save(model_instance, add)
1321
1289
 
1322
- # contribute_to_class is inherited from DateField, it registers
1323
- # get_next_by_FOO and get_prev_by_FOO
1324
-
1325
1290
  def get_prep_value(self, value):
1326
1291
  value = super().get_prep_value(value)
1327
1292
  value = self.to_python(value)
@@ -2145,9 +2110,6 @@ class PrimaryKeyField(BigIntegerField):
2145
2110
  value = connection.ops.validate_autopk_value(value)
2146
2111
  return value
2147
2112
 
2148
- def contribute_to_class(self, cls, name, **kwargs):
2149
- super().contribute_to_class(cls, name, **kwargs)
2150
-
2151
2113
  def get_internal_type(self):
2152
2114
  return "PrimaryKeyField"
2153
2115
 
@@ -3,6 +3,7 @@ from functools import cached_property, partial
3
3
  from plain import exceptions
4
4
  from plain.models.constants import LOOKUP_SEP
5
5
  from plain.models.deletion import SET_DEFAULT, SET_NULL
6
+ from plain.models.exceptions import FieldDoesNotExist, FieldError
6
7
  from plain.models.query_utils import PathInfo, Q
7
8
  from plain.models.utils import make_model_tuple
8
9
  from plain.preflight import PreflightResult
@@ -303,8 +304,8 @@ class RelatedField(FieldCacheMixin, Field):
303
304
  # columns from another table.
304
305
  return None
305
306
 
306
- def contribute_to_class(self, cls, name, private_only=False, **kwargs):
307
- super().contribute_to_class(cls, name, private_only=private_only, **kwargs)
307
+ def contribute_to_class(self, cls, name):
308
+ super().contribute_to_class(cls, name)
308
309
 
309
310
  self.opts = cls._meta
310
311
 
@@ -407,7 +408,7 @@ class RelatedField(FieldCacheMixin, Field):
407
408
  """
408
409
  target_fields = self.path_infos[-1].target_fields
409
410
  if len(target_fields) > 1:
410
- raise exceptions.FieldError(
411
+ raise FieldError(
411
412
  "The relation has multiple target fields, but only single target field "
412
413
  "was asked for"
413
414
  )
@@ -582,15 +583,15 @@ class ForeignKey(RelatedField):
582
583
  def reverse_path_infos(self):
583
584
  return self.get_reverse_path_info()
584
585
 
585
- def contribute_to_class(self, cls, name, private_only=False, **kwargs):
586
- super().contribute_to_class(cls, name, private_only=private_only, **kwargs)
586
+ def contribute_to_class(self, cls, name):
587
+ super().contribute_to_class(cls, name)
587
588
  setattr(cls, self.name, self.forward_related_accessor_class(self))
588
589
 
589
590
  def contribute_to_related_class(self, cls, related):
590
591
  # Internal FK's - i.e., those with a related name ending with '+'
591
592
  if not self.remote_field.is_hidden():
592
593
  setattr(
593
- cls._meta.concrete_model,
594
+ cls,
594
595
  related.get_accessor_name(),
595
596
  self.related_accessor_class(related),
596
597
  )
@@ -696,13 +697,10 @@ class ForeignKey(RelatedField):
696
697
  related_fields = [(from_field, to_field)]
697
698
 
698
699
  for from_field, to_field in related_fields:
699
- if (
700
- to_field
701
- and to_field.model != self.remote_field.model._meta.concrete_model
702
- ):
703
- raise exceptions.FieldError(
700
+ if to_field and to_field.model != self.remote_field.model:
701
+ raise FieldError(
704
702
  f"'{self.model._meta.label}.{self.name}' refers to field '{to_field.name}' which is not local to model "
705
- f"'{self.remote_field.model._meta.concrete_model._meta.label}'."
703
+ f"'{self.remote_field.model._meta.label}'."
706
704
  )
707
705
  return related_fields
708
706
 
@@ -1075,7 +1073,7 @@ class ManyToManyField(RelatedField):
1075
1073
 
1076
1074
  try:
1077
1075
  field = through._meta.get_field(field_name)
1078
- except exceptions.FieldDoesNotExist:
1076
+ except FieldDoesNotExist:
1079
1077
  errors.append(
1080
1078
  PreflightResult(
1081
1079
  fix=f"The intermediary model '{qualified_model_name}' has no field '{field_name}'. {fix}",
@@ -1109,13 +1107,8 @@ class ManyToManyField(RelatedField):
1109
1107
  }
1110
1108
  m2m_db_table = self.m2m_db_table()
1111
1109
  model = registered_tables.get(m2m_db_table)
1112
- # The second condition allows multiple m2m relations on a model if
1113
- # some point to a through model that proxies another through model.
1114
- if (
1115
- model
1116
- and model._meta.concrete_model
1117
- != self.remote_field.through._meta.concrete_model
1118
- ):
1110
+ # Check if there's already a m2m field using the same through model.
1111
+ if model and model != self.remote_field.through:
1119
1112
  clashing_obj = model._meta.label
1120
1113
  return [
1121
1114
  PreflightResult(
@@ -1245,8 +1238,8 @@ class ManyToManyField(RelatedField):
1245
1238
  break
1246
1239
  return getattr(self, cache_attr)
1247
1240
 
1248
- def contribute_to_class(self, cls, name, **kwargs):
1249
- super().contribute_to_class(cls, name, **kwargs)
1241
+ def contribute_to_class(self, cls, name):
1242
+ super().contribute_to_class(cls, name)
1250
1243
 
1251
1244
  def resolve_through_model(_, model, field):
1252
1245
  field.remote_field.through = model
@@ -208,9 +208,7 @@ class ForwardManyToOneDescriptor:
208
208
  value = value if value else None
209
209
 
210
210
  # An object must be an instance of the related class.
211
- if value is not None and not isinstance(
212
- value, self.field.remote_field.model._meta.concrete_model
213
- ):
211
+ if value is not None and not isinstance(value, self.field.remote_field.model):
214
212
  raise ValueError(
215
213
  f'Cannot assign "{value!r}": "{instance._meta.object_name}.{self.field.name}" must be a "{self.field.remote_field.model._meta.object_name}" instance.'
216
214
  )
@@ -80,7 +80,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
80
80
  """
81
81
  Filter the queryset for the instance this manager is bound to.
82
82
  """
83
- from plain.exceptions import FieldError
83
+ from plain.models.exceptions import FieldError
84
84
 
85
85
  queryset._defer_next_filter = True
86
86
  queryset = queryset.filter(**self.core_filters)
@@ -11,7 +11,7 @@ they're the closest concept currently available.
11
11
 
12
12
  from functools import cached_property
13
13
 
14
- from plain import exceptions
14
+ from plain.models.exceptions import FieldDoesNotExist, FieldError
15
15
  from plain.utils.hashable import make_hashable
16
16
 
17
17
  from . import BLANK_CHOICE_DASH
@@ -79,9 +79,7 @@ class ForeignObjectRel(FieldCacheMixin):
79
79
  """
80
80
  target_fields = self.path_infos[-1].target_fields
81
81
  if len(target_fields) > 1:
82
- raise exceptions.FieldError(
83
- "Can't use target_field for multicolumn relations."
84
- )
82
+ raise FieldError("Can't use target_field for multicolumn relations.")
85
83
  return target_fields[0]
86
84
 
87
85
  @cached_property
@@ -274,7 +272,7 @@ class ManyToOneRel(ForeignObjectRel):
274
272
  """
275
273
  field = self.model._meta.get_field("id")
276
274
  if not field.concrete:
277
- raise exceptions.FieldDoesNotExist("No related field named 'id'")
275
+ raise FieldDoesNotExist("No related field named 'id'")
278
276
  return field
279
277
 
280
278
  def set_field_name(self):
plain/models/forms.py CHANGED
@@ -7,13 +7,13 @@ from itertools import chain
7
7
 
8
8
  from plain.exceptions import (
9
9
  NON_FIELD_ERRORS,
10
- FieldError,
11
10
  ImproperlyConfigured,
12
11
  ValidationError,
13
12
  )
14
13
  from plain.forms import fields
15
14
  from plain.forms.fields import ChoiceField, Field
16
15
  from plain.forms.forms import BaseForm, DeclarativeFieldsMetaclass
16
+ from plain.models.exceptions import FieldError
17
17
 
18
18
  __all__ = (
19
19
  "ModelForm",
plain/models/lookups.py CHANGED
@@ -2,7 +2,7 @@ import itertools
2
2
  import math
3
3
  from functools import cached_property
4
4
 
5
- from plain.exceptions import EmptyResultSet, FullResultSet
5
+ from plain.models.exceptions import EmptyResultSet, FullResultSet
6
6
  from plain.models.expressions import Expression, Func, Value
7
7
  from plain.models.fields import (
8
8
  BooleanField,
@@ -4,7 +4,7 @@ from contextlib import contextmanager
4
4
  from functools import cached_property, partial
5
5
 
6
6
  from plain import models
7
- from plain.exceptions import FieldDoesNotExist
7
+ from plain.models.exceptions import FieldDoesNotExist
8
8
  from plain.models.fields import NOT_PROVIDED
9
9
  from plain.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT
10
10
  from plain.models.migrations.utils import field_is_referenced, get_references
plain/models/options.py CHANGED
@@ -3,11 +3,10 @@ import inspect
3
3
  from collections import defaultdict
4
4
  from functools import cached_property
5
5
 
6
- from plain.exceptions import FieldDoesNotExist
7
6
  from plain.models import models_registry
8
7
  from plain.models.constraints import UniqueConstraint
9
8
  from plain.models.db import db_connection
10
- from plain.models.fields import PrimaryKeyField
9
+ from plain.models.exceptions import FieldDoesNotExist
11
10
  from plain.models.query import QuerySet
12
11
  from plain.utils.datastructures import ImmutableList
13
12
 
@@ -70,11 +69,6 @@ class Options:
70
69
  self.required_db_vendor = None
71
70
  self.meta = meta
72
71
 
73
- # For any non-abstract class, the concrete class is the model
74
- # in the end of the proxy_for_model chain. In particular, for
75
- # concrete models, the concrete_model is always the class itself.
76
- self.concrete_model = None
77
-
78
72
  # List of all lookups defined in ForeignKey 'limit_choices_to' options
79
73
  # from *other* models. Needed for some admin checks. Internal use only.
80
74
  self.related_fkey_lookups = []
@@ -156,11 +150,7 @@ class Options:
156
150
  new_objs.append(obj)
157
151
  return new_objs
158
152
 
159
- def _prepare(self, model):
160
- if not any(f.name == "id" for f in self.local_fields):
161
- model.add_to_class("id", PrimaryKeyField())
162
-
163
- def add_field(self, field, private=False):
153
+ def add_field(self, field):
164
154
  # Insert the given field in the order in which it was created, using
165
155
  # the "creation_counter" attribute of the field.
166
156
  # Move many-to-many related fields from self.fields into
@@ -417,7 +407,7 @@ class Options:
417
407
  )
418
408
  for f in fields_with_relations:
419
409
  if not isinstance(f.remote_field.model, str):
420
- remote_label = f.remote_field.model._meta.concrete_model._meta.label
410
+ remote_label = f.remote_field.model._meta.label
421
411
  related_objects_graph[remote_label].append(f)
422
412
 
423
413
  for model in all_models:
@@ -426,9 +416,7 @@ class Options:
426
416
  # __dict__ takes precedence over a data descriptor (such as
427
417
  # @cached_property). This means that the _meta._relation_tree is
428
418
  # only called if related_objects is not in __dict__.
429
- related_objects = related_objects_graph[
430
- model._meta.concrete_model._meta.label
431
- ]
419
+ related_objects = related_objects_graph[model._meta.label]
432
420
  model._meta.__dict__["_relation_tree"] = related_objects
433
421
  # It seems it is possible that self is not in all_models, so guard
434
422
  # against that with default for get().
plain/models/query.py CHANGED
@@ -9,7 +9,6 @@ from functools import cached_property
9
9
  from itertools import chain, islice
10
10
 
11
11
  import plain.runtime
12
- from plain import exceptions
13
12
  from plain.exceptions import ValidationError
14
13
  from plain.models import (
15
14
  sql,
@@ -22,6 +21,11 @@ from plain.models.db import (
22
21
  NotSupportedError,
23
22
  db_connection,
24
23
  )
24
+ from plain.models.exceptions import (
25
+ FieldDoesNotExist,
26
+ FieldError,
27
+ ObjectDoesNotExist,
28
+ )
25
29
  from plain.models.expressions import Case, F, Value, When
26
30
  from plain.models.fields import (
27
31
  DateField,
@@ -129,9 +133,7 @@ class RawModelIterable(BaseIterable):
129
133
  ) = self.queryset.resolve_model_init_order()
130
134
  model_cls = self.queryset.model
131
135
  if "id" not in model_init_names:
132
- raise exceptions.FieldDoesNotExist(
133
- "Raw query must include the primary key"
134
- )
136
+ raise FieldDoesNotExist("Raw query must include the primary key")
135
137
  fields = [self.queryset.model_fields.get(c) for c in self.queryset.columns]
136
138
  converters = compiler.get_converters(
137
139
  [f.get_col(f.model._meta.db_table) if f else None for f in fields]
@@ -850,12 +852,12 @@ class QuerySet:
850
852
  for param in params:
851
853
  try:
852
854
  self.model._meta.get_field(param)
853
- except exceptions.FieldDoesNotExist:
855
+ except FieldDoesNotExist:
854
856
  # It's okay to use a model's property if it has a setter.
855
857
  if not (param in property_names and getattr(self.model, param).fset):
856
858
  invalid_params.append(param)
857
859
  if invalid_params:
858
- raise exceptions.FieldError(
860
+ raise FieldError(
859
861
  "Invalid field name(s) for model {}: '{}'.".format(
860
862
  self.model._meta.object_name,
861
863
  "', '".join(sorted(invalid_params)),
@@ -982,7 +984,7 @@ class QuerySet:
982
984
  descending = True
983
985
  if annotation := query.annotations.get(alias):
984
986
  if getattr(annotation, "contains_aggregate", False):
985
- raise exceptions.FieldError(
987
+ raise FieldError(
986
988
  f"Cannot update when ordering by an aggregate: {annotation}"
987
989
  )
988
990
  if descending:
@@ -1034,7 +1036,7 @@ class QuerySet:
1034
1036
  "Cannot call QuerySet.contains() after .values() or .values_list()."
1035
1037
  )
1036
1038
  try:
1037
- if obj._meta.concrete_model != self.model._meta.concrete_model:
1039
+ if obj.__class__ != self.model:
1038
1040
  return False
1039
1041
  except AttributeError:
1040
1042
  raise TypeError("'obj' must be a model instance.")
@@ -2039,7 +2041,7 @@ def prefetch_related_objects(model_instances, *related_lookups):
2039
2041
  else:
2040
2042
  try:
2041
2043
  new_obj = getattr(obj, through_attr)
2042
- except exceptions.ObjectDoesNotExist:
2044
+ except ObjectDoesNotExist:
2043
2045
  continue
2044
2046
  if new_obj is None:
2045
2047
  continue
@@ -2174,7 +2176,7 @@ def prefetch_one_level(instances, prefetcher, lookup, level):
2174
2176
  model = instances[0].__class__
2175
2177
  try:
2176
2178
  model._meta.get_field(to_attr)
2177
- except exceptions.FieldDoesNotExist:
2179
+ except FieldDoesNotExist:
2178
2180
  pass
2179
2181
  else:
2180
2182
  msg = "to_attr={} conflicts with a field on the {} model."
@@ -11,9 +11,9 @@ import inspect
11
11
  import logging
12
12
  from collections import namedtuple
13
13
 
14
- from plain.exceptions import FieldError
15
14
  from plain.models.constants import LOOKUP_SEP
16
15
  from plain.models.db import DatabaseError, db_connection
16
+ from plain.models.exceptions import FieldError
17
17
  from plain.utils import tree
18
18
 
19
19
  logger = logging.getLogger("plain.models")
@@ -355,7 +355,7 @@ def check_rel_lookup_compatibility(model, target_opts, field):
355
355
  """
356
356
 
357
357
  def check(opts):
358
- return model._meta.concrete_model == opts.concrete_model
358
+ return model == opts.model
359
359
 
360
360
  # If the field is a primary key, then doing a query against the field's
361
361
  # model is ok, too. Consider the case:
@@ -4,9 +4,9 @@ import re
4
4
  from functools import cached_property, partial
5
5
  from itertools import chain
6
6
 
7
- from plain.exceptions import EmptyResultSet, FieldError, FullResultSet
8
7
  from plain.models.constants import LOOKUP_SEP
9
8
  from plain.models.db import DatabaseError, NotSupportedError
9
+ from plain.models.exceptions import EmptyResultSet, FieldError, FullResultSet
10
10
  from plain.models.expressions import F, OrderBy, RawSQL, Ref, Value
11
11
  from plain.models.functions import Cast, Random
12
12
  from plain.models.lookups import Lookup
@@ -940,9 +940,9 @@ class SQLCompiler:
940
940
  start_alias = start_alias or self.query.get_initial_alias()
941
941
 
942
942
  for field in opts.concrete_fields:
943
- model = field.model._meta.concrete_model
944
- # A proxy model will have a different model and concrete_model. We
945
- # will assign None if the field belongs to this model.
943
+ model = field.model
944
+ # Since we no longer have proxy models or inheritance,
945
+ # the field's model should always be the same as opts.model.
946
946
  if model == opts.model:
947
947
  model = None
948
948
  if select_mask and field not in select_mask:
@@ -1331,9 +1331,9 @@ class SQLCompiler:
1331
1331
  select_fields is filled recursively, so it also contains fields
1332
1332
  from the parent models.
1333
1333
  """
1334
- concrete_model = klass_info["model"]._meta.concrete_model
1334
+ model = klass_info["model"]
1335
1335
  for select_index in klass_info["select_fields"]:
1336
- if self.select[select_index][0].target.model == concrete_model:
1336
+ if self.select[select_index][0].target.model == model:
1337
1337
  return self.select[select_index][0]
1338
1338
 
1339
1339
  def _get_field_choices():
@@ -3,7 +3,7 @@ Useful auxiliary data structures for query construction. Not useful outside
3
3
  the SQL domain.
4
4
  """
5
5
 
6
- from plain.exceptions import FullResultSet
6
+ from plain.models.exceptions import FullResultSet
7
7
  from plain.models.sql.constants import INNER, LOUTER
8
8
 
9
9
 
plain/models/sql/query.py CHANGED
@@ -17,10 +17,10 @@ from functools import cached_property
17
17
  from itertools import chain, count, product
18
18
  from string import ascii_uppercase
19
19
 
20
- from plain.exceptions import FieldDoesNotExist, FieldError
21
20
  from plain.models.aggregates import Count
22
21
  from plain.models.constants import LOOKUP_SEP
23
22
  from plain.models.db import NotSupportedError, db_connection
23
+ from plain.models.exceptions import FieldDoesNotExist, FieldError
24
24
  from plain.models.expressions import (
25
25
  BaseExpression,
26
26
  Col,
@@ -706,7 +706,7 @@ class Query(BaseExpression):
706
706
  if not field.is_relation:
707
707
  raise FieldError(next(iter(field_mask)))
708
708
  field_select_mask = select_mask.setdefault(field, {})
709
- related_model = field.remote_field.model._meta.concrete_model
709
+ related_model = field.remote_field.model
710
710
  self._get_defer_select_mask(
711
711
  related_model._meta, field_mask, field_select_mask
712
712
  )
@@ -721,7 +721,7 @@ class Query(BaseExpression):
721
721
  else:
722
722
  field = opts.get_field(field_name).field
723
723
  field_select_mask = select_mask.setdefault(field, {})
724
- related_model = field.model._meta.concrete_model
724
+ related_model = field.model
725
725
  self._get_defer_select_mask(
726
726
  related_model._meta, field_mask, field_select_mask
727
727
  )
@@ -738,7 +738,7 @@ class Query(BaseExpression):
738
738
  if field_mask:
739
739
  if not field.is_relation:
740
740
  raise FieldError(next(iter(field_mask)))
741
- related_model = field.remote_field.model._meta.concrete_model
741
+ related_model = field.remote_field.model
742
742
  self._get_only_select_mask(
743
743
  related_model._meta, field_mask, field_select_mask
744
744
  )
@@ -2,7 +2,7 @@
2
2
  Query subclasses which provide extra functionality beyond simple data retrieval.
3
3
  """
4
4
 
5
- from plain.exceptions import FieldError
5
+ from plain.models.exceptions import FieldError
6
6
  from plain.models.sql.constants import CURSOR, GET_ITERATOR_CHUNK_SIZE, NO_RESULTS
7
7
  from plain.models.sql.query import Query
8
8
 
@@ -87,13 +87,13 @@ class UpdateQuery(Query):
87
87
  direct = (
88
88
  not (field.auto_created and not field.concrete) or not field.concrete
89
89
  )
90
- model = field.model._meta.concrete_model
90
+ model = field.model
91
91
  if not direct or (field.is_relation and field.many_to_many):
92
92
  raise FieldError(
93
93
  f"Cannot update model field {field!r} (only non-relations and "
94
94
  "foreign keys permitted)."
95
95
  )
96
- if model is not self.get_meta().concrete_model:
96
+ if model is not self.get_meta().model:
97
97
  self.add_related_update(model, field, val)
98
98
  continue
99
99
  values_seq.append((field, model, val))
plain/models/sql/where.py CHANGED
@@ -5,7 +5,7 @@ Code to manage the creation and SQL rendering of 'where' constraints.
5
5
  import operator
6
6
  from functools import cached_property, reduce
7
7
 
8
- from plain.exceptions import EmptyResultSet, FullResultSet
8
+ from plain.models.exceptions import EmptyResultSet, FullResultSet
9
9
  from plain.models.expressions import Case, When
10
10
  from plain.models.lookups import Exact
11
11
  from plain.utils import tree
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.models
3
- Version: 0.48.0
3
+ Version: 0.49.0
4
4
  Summary: Model your data and store it in a database.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-File: LICENSE
@@ -1,30 +1,30 @@
1
1
  plain/models/AGENTS.md,sha256=xQQW-z-DehnCUyjiGSBfLqUjoSUdo_W1b0JmwYmWieA,209
2
- plain/models/CHANGELOG.md,sha256=_EJXRX89opgWTq_FnrLrs2Q7BOKxbrINWsYU4qwZIqQ,18245
2
+ plain/models/CHANGELOG.md,sha256=f-zsBeKsovIyXp5YozHM7Fz8ujJS1II-oF2POrLSrAQ,19875
3
3
  plain/models/README.md,sha256=lqzWJrEIxBCHC1P8X1YoRjbsMFlu0-kG4ujP76B_ZO4,8572
4
- plain/models/__init__.py,sha256=aB9HhIKBh0iK3LZztInAE-rDF-yKsdfcjfMtwtN5vnI,2920
5
- plain/models/aggregates.py,sha256=P0mhsMl1VZt2CVHMuCHnNI8SxZ9citjDLEgioN6NOpo,7240
6
- plain/models/base.py,sha256=4NkK-gkcxbIWZm7c9Q51eDwLClIieWZvlRg6l7vJOxE,65692
4
+ plain/models/__init__.py,sha256=t6scDQ-bGQD3k-U_DE9K5JJ3pnmkC7ZHchFnWA-BcW8,2845
5
+ plain/models/aggregates.py,sha256=P1nAyp1XORdlx1D-__Nx9AehUeuKmcjeS6heqjB7A7k,7247
6
+ plain/models/base.py,sha256=7fN3nPpO1vlE8eJ7C26g_kaoo2kImujmturCkpnAc2w,63835
7
7
  plain/models/cli.py,sha256=rqrgG__OyqtaDaKLB6XZnS6Zv7esU9K9EAyKCO35McA,39548
8
8
  plain/models/config.py,sha256=-m15VY1ZJKWdPGt-5i9fnMvz9LBzPfSRgWmWeEV8Dao,382
9
9
  plain/models/connections.py,sha256=RBNa2FZ0x3C9un6PaYL-IYzH_OesRSpdHNGKvYHGiOM,2276
10
10
  plain/models/constants.py,sha256=ndnj9TOTKW0p4YcIPLOLEbsH6mOgFi6B1-rIzr_iwwU,210
11
- plain/models/constraints.py,sha256=Mm9gm5D7EKmo486dL481-hrTcxi2gxgqyUUtbGrkLjs,16749
11
+ plain/models/constraints.py,sha256=DC6BW6tB4WcChakyRsi6qiiWolC3v5oG1IAFvQe3ESk,16784
12
12
  plain/models/database_url.py,sha256=iidKVhOylf5N6t1EMPRySRQiv6LiuRjYRECB_UJ3MI8,6419
13
13
  plain/models/db.py,sha256=FpdfLYrRX2THUzDy4QdJ_OpSo9IFKLerZIEQ-T2x8zA,1348
14
14
  plain/models/default_settings.py,sha256=cDym1o_DtHySWgDRIdjgEM0YxjgYU51ZqzWVA3vpzTk,569
15
- plain/models/deletion.py,sha256=GnjhPHLRAsrec7Aa0sczqtOEDV41AGKj6QBQdhl508A,17611
15
+ plain/models/deletion.py,sha256=7EdE_ZtKI3-Plb4QIlWYqQJa1l8gC9-KHH6GazoWjT4,17505
16
16
  plain/models/entrypoints.py,sha256=EC14mW19tK9dCumaNHnv4_9jQV8jomQ8jXy8Ib89VBw,191
17
17
  plain/models/enums.py,sha256=Zr-JKt2aeYsSADtAm69fDRfajS7jYwop2vWQVLJ9YYI,2726
18
- plain/models/exceptions.py,sha256=IqzK60-hY3TYsgOMxlWwgpVa21E7ydC-gqUG4tNvVJc,2042
19
- plain/models/expressions.py,sha256=hN6sfOxqxpP0qmYOUotsFAAn2-bnl35iHwyINyXA7CI,62763
20
- plain/models/forms.py,sha256=VreclYMITYh3D-sfZV3iSQH9QjNmwNwIYQL_90owOVM,25805
18
+ plain/models/exceptions.py,sha256=xxwsr2rhV4J4RNJoGu5yaKWQUTiREFx6ARnKGRcxl7k,4995
19
+ plain/models/expressions.py,sha256=1bWyeSzlTbBawZa-aaDEMSiTSnroMA2DRswsAu-5mtw,62770
20
+ plain/models/forms.py,sha256=nqA5_bByjNxKoc51O48Gycp6z2oMmozqyyzfwnc5V2o,25836
21
21
  plain/models/indexes.py,sha256=fazIZPJgCX5_Bhwk7MQy3YbWOxpHvaCe1dDLGGldTuY,11540
22
- plain/models/lookups.py,sha256=eCsxQXUcOoAa_U_fAAd3edcgXI1wfyFW8hPgUh8TwTo,24776
23
- plain/models/options.py,sha256=BOnu9NDVcgL0tJhan5gBbaK1SWNeg4NVTPNAzkKT3NE,21528
22
+ plain/models/lookups.py,sha256=77F4AE0ukDRqAj-uLQ5oWgmK1dZcuVujspefyEO7gOE,24783
23
+ plain/models/options.py,sha256=_UWTLyQo4D2sx4AmIRgXQBo3HqBHWujY3XutLu_O8LQ,20999
24
24
  plain/models/otel.py,sha256=36QSJS6UXv1YPJTqeSmEvdMVHRkXa_zgqqItJaXc59g,7619
25
25
  plain/models/preflight.py,sha256=_cBX7AnfQDNtZfoW0ydxH8WQM3ftCqcH0-tPhqS5q8c,8973
26
- plain/models/query.py,sha256=YxIpgRndQyXsSpNLl3uPGvu-UNQzK1CXFsf2U75XVao,90714
27
- plain/models/query_utils.py,sha256=zxAdfwDbOmaN_SJODl4Wl9gs-q2EzOjXbsBFTWWhh8g,14174
26
+ plain/models/query.py,sha256=p-qpwN-eR7Dvk68QA6W7QgVD5ejoYwYYsv4rbXxB7WA,90652
27
+ plain/models/query_utils.py,sha256=x_n2lKsSjx-5ULbZsGx0AhGpMNVYizdbl_BxDlUK8lQ,14151
28
28
  plain/models/registry.py,sha256=5yxVgT_W8GlyL2bsGT2HvMQB5sKolXucP2qrhr7Wlnk,8126
29
29
  plain/models/transaction.py,sha256=KqkRDT6aqMgbPA_ch7qO8a9NyDvwY_2FaxM7FkBkcgY,9357
30
30
  plain/models/utils.py,sha256=rD47CAMH4SsznTe-kUnRUdnaZeZHVv1fwLUiU3KOFW0,1630
@@ -43,7 +43,7 @@ plain/models/backends/base/validation.py,sha256=ATkhZ36RgKHrXhdiOzIK5kRHW56eSggL
43
43
  plain/models/backends/mysql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
44
  plain/models/backends/mysql/base.py,sha256=FCANhKAs2013oLPdC9y4PQCRb9NYsp1Zca_wha6TDT4,14286
45
45
  plain/models/backends/mysql/client.py,sha256=YGjk4f5VOuIKcj5SOOJeBqiSDF0Ft9m1aUPOmLzdD6c,2973
46
- plain/models/backends/mysql/compiler.py,sha256=NaYgcGyFUZ2lTOXXJj87hLMMMnXRAS1jUYkJq1NoCv4,3289
46
+ plain/models/backends/mysql/compiler.py,sha256=DorAoufST7QYQUwkXRZFl4xyp2rykrGBhkHZlW1vCr4,3296
47
47
  plain/models/backends/mysql/creation.py,sha256=ozuc3mz65bjFz6sn5BFLRRGQsUYaxwIGjQyKod31c1Y,952
48
48
  plain/models/backends/mysql/features.py,sha256=OPHaiqPmAFAUZqnZdLOqbCmxmuGMnIqa62fJqAkYQ0o,5875
49
49
  plain/models/backends/mysql/introspection.py,sha256=R3U912qnWwJDmTiX7QzGAXTc2LAShIhp4C6xHTABM68,13960
@@ -65,20 +65,20 @@ plain/models/backends/sqlite3/client.py,sha256=dvxY10LFJHKY6ykOuCE6e45Wo4fAH4IPD
65
65
  plain/models/backends/sqlite3/creation.py,sha256=dH4rqZj79TCMHmaBash4I_io15QCxnam92fuWrRK_R8,2788
66
66
  plain/models/backends/sqlite3/features.py,sha256=V7ueGfbUSzBNGgMRtCptXSH_0b51lyVfzi-98BX4afo,2366
67
67
  plain/models/backends/sqlite3/introspection.py,sha256=6nD5sbjxlJ1ocmwBxmyZqg7KT7fHKbjmmGZqAzmVpZc,17063
68
- plain/models/backends/sqlite3/operations.py,sha256=K2PFCsvzGhBrPEzz1AXFenge2B4Ap8lsQAABsC4e_UI,15302
68
+ plain/models/backends/sqlite3/operations.py,sha256=jgVfxFKP3fnIo9PzfxZtm5BYTo3XgHUncvtTtI6WRKY,15309
69
69
  plain/models/backends/sqlite3/schema.py,sha256=sbtI0PBGe0fK0IOFWh0bkpVntCMMVsfzPb9dpL7o4r8,22566
70
70
  plain/models/backups/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
71
  plain/models/backups/cli.py,sha256=AKVh_Go0LxnxFi2jQjmpPEbbJvYCurjJsGCWESm1X8A,2960
72
72
  plain/models/backups/clients.py,sha256=gHYQkukqamR_uksMr9wy3tBpMxK6DyUyj7nndNIlbo8,4744
73
73
  plain/models/backups/core.py,sha256=09IZUhBEe1Yej3PC8AidtkaI0c8tt7VnqGBCWK-WrFg,3318
74
- plain/models/fields/__init__.py,sha256=-daSts1wN63oU_XXQUlNPmk81-JPQzXwY1FdCMRnTng,74732
74
+ plain/models/fields/__init__.py,sha256=Pxso0fIHOhf9Czrw-W5x0B7u_mBbhf9KgNzW9QBoJrk,73096
75
75
  plain/models/fields/json.py,sha256=4WHZX5MI-GZapcaaHltPgc6f9pHAezjfaTYNh_PX8rQ,18041
76
76
  plain/models/fields/mixins.py,sha256=wmu3JJEOimijgepjMEFPN8u74mHpefgnsB4u5ZzVCUY,1890
77
- plain/models/fields/related.py,sha256=7Ku8E5hxQxVgJy3afWZfuDlHDzrky_4mkKwPlqLpM6o,51065
78
- plain/models/fields/related_descriptors.py,sha256=nsVgLjpOlrha90eTfg7ad_il6_uI_YG0d4bH51LP3Os,15180
77
+ plain/models/fields/related.py,sha256=C69F4umO_oNTbpE5XhI5PBV_tLttBA-jV4lPcsqq6kA,50669
78
+ plain/models/fields/related_descriptors.py,sha256=Ox7sX6mpsDfvNRZBm323GwseOqlaK4270WREydCRjrQ,15137
79
79
  plain/models/fields/related_lookups.py,sha256=9y6AfEcg8xRRZne2LXFP6jym9mecFlB_toYih7lD8Uw,7781
80
- plain/models/fields/related_managers.py,sha256=LefSf8ss8XZ_97NoKfRKHTijheb44exJ_UBLGcNgQdc,25053
81
- plain/models/fields/reverse_related.py,sha256=SNFytCI3BeAlB5kY6UQrv6QGqtRMo_aHWPx_-fCkfu4,10404
80
+ plain/models/fields/related_managers.py,sha256=MxxOY5iMmHgpZR7qt9ZPbdvw8pN_B39siBJgdDXgS2A,25060
81
+ plain/models/fields/reverse_related.py,sha256=uAePNDx6h-7duDeJyMPBPm0DpUM39YZKxrO6G8Lgm_A,10389
82
82
  plain/models/functions/__init__.py,sha256=aglCm_JtzDYk2KmxubDN_78CGG3JCfRWnfJ74Oj5YJ4,2658
83
83
  plain/models/functions/comparison.py,sha256=9uAiEuNXZiGFzJKBvktsHwx58Qpa2cPQkr6pUWsGcKo,6554
84
84
  plain/models/functions/datetime.py,sha256=ov1H0Oq9qHSeu8L5CZsln0_SMU2C5M_P5HvKxppA24o,13089
@@ -97,7 +97,7 @@ plain/models/migrations/optimizer.py,sha256=HH-uz-jnWw_Ni6F2_rRW1nax1Dxmf1s_F_8s
97
97
  plain/models/migrations/questioner.py,sha256=qAsePI5JHiSJrlY_kmpgMuK9Dom22q17edov7RtBeGw,11967
98
98
  plain/models/migrations/recorder.py,sha256=_ncIVLJ4ns6AaO0vVmCoXfABlOFXDKu8NTPsutqKjK4,3653
99
99
  plain/models/migrations/serializer.py,sha256=yBP9TyUZlSh_8qdw1I4VotbsZqoomz3mBs7ASQLsDH8,12459
100
- plain/models/migrations/state.py,sha256=EB3BCST2SxpijGjdEhh2Jqp-wr4Fb29bhvOZP3ZWdqg,32120
100
+ plain/models/migrations/state.py,sha256=yX04Y5M87RylEqIpy6pbzWdTw_9Fv2bhtT0ODbPAiZg,32127
101
101
  plain/models/migrations/utils.py,sha256=Ih_mu6UbdUSt-ZtHaB0xIXHDrBANuFZyftTQ56BFJYs,4174
102
102
  plain/models/migrations/writer.py,sha256=N8Rnjv5ccsA_CTcS7zZyppzyHFOUQVJy0l6RZYjwF-0,10981
103
103
  plain/models/migrations/operations/__init__.py,sha256=YKZsQsJ4G5iw9F4o6dOSgSCuLiiKuApvneoV4jqjZaA,752
@@ -106,17 +106,17 @@ plain/models/migrations/operations/fields.py,sha256=ARL945rbztAnMsbd0lvQRsQJEmxY
106
106
  plain/models/migrations/operations/models.py,sha256=jwhck8ygfi8cW8baVeGUB6coVVv4HnGVSFVb2IW_aTU,24947
107
107
  plain/models/migrations/operations/special.py,sha256=cKieE9AQcFrpvEw5_TJCum56uu6cfN1p-azY7sKdB7E,4944
108
108
  plain/models/sql/__init__.py,sha256=FoRCcab-kh_XY8C4eldgLy9-zuk-M63Nyi9cFsYjclU,225
109
- plain/models/sql/compiler.py,sha256=FVa_v4Q8izuFc9VQTrW4jAqM1uqRnTVdOeP7IKt3yM8,84691
109
+ plain/models/sql/compiler.py,sha256=MumFROrMBIRhWpxlq85sj25YeZ1UZMTA7UQfRzzJLwQ,84632
110
110
  plain/models/sql/constants.py,sha256=usb1LSh9WNGPsurWAGppDkV0wYJJg5GEegKibQdS718,533
111
- plain/models/sql/datastructures.py,sha256=FC88CVCukLyU621JrmKLBhmgvotEHgAhIOYfVvJpuR0,7084
112
- plain/models/sql/query.py,sha256=ASR6jeRTjGlnO5E_hb5cnUYQH2_JrpHa_M545xOG7dg,108745
113
- plain/models/sql/subqueries.py,sha256=1YYlgoDrx_mW19MWWawLdgDTJnLgvvkGBQ30YQ702W4,5860
114
- plain/models/sql/where.py,sha256=ezE9Clt2BmKo-I7ARsgqZ_aVA-1UdayCwr6ULSWZL6c,12635
111
+ plain/models/sql/datastructures.py,sha256=OvMfckKGQOb0pb1UAXhlV-P0FAvvV9oSR0R82i0u0JU,7091
112
+ plain/models/sql/query.py,sha256=DkLdkDzQuJWzdtyzCTQldQIHyoJu8JGoTuiLolCA16w,108689
113
+ plain/models/sql/subqueries.py,sha256=DGWpBO4wYVT0lSSDoNYcy_VyCLuSueZvoDfrgQu7V1g,5837
114
+ plain/models/sql/where.py,sha256=P_2EFg-BMIu9jTZFiiPwz1E74jlSitiP-taShCp5OhU,12642
115
115
  plain/models/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
116
  plain/models/test/pytest.py,sha256=KD5-mxonBxOYIhUh9Ql5uJOIiC9R4t-LYfb6sjA0UdE,3486
117
117
  plain/models/test/utils.py,sha256=S3d6zf3OFWDxB_kBJr0tDvwn51bjwDVWKPumv37N-p8,467
118
- plain_models-0.48.0.dist-info/METADATA,sha256=rE7jXG5-QLDFTyIJQnTb9QGt6Z23o_LZ16soPfLKvlI,8884
119
- plain_models-0.48.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
120
- plain_models-0.48.0.dist-info/entry_points.txt,sha256=IYJAW9MpL3PXyXFWmKmALagAGXC_5rzBn2eEGJlcV04,112
121
- plain_models-0.48.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
122
- plain_models-0.48.0.dist-info/RECORD,,
118
+ plain_models-0.49.0.dist-info/METADATA,sha256=QulZ-TJtNAyutLdHzWvvhGNaZ5uayc3hgl3VFycIgzw,8884
119
+ plain_models-0.49.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
120
+ plain_models-0.49.0.dist-info/entry_points.txt,sha256=IYJAW9MpL3PXyXFWmKmALagAGXC_5rzBn2eEGJlcV04,112
121
+ plain_models-0.49.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
122
+ plain_models-0.49.0.dist-info/RECORD,,