plain.models 0.48.0__py3-none-any.whl → 0.49.1__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 +26 -0
- plain/models/__init__.py +0 -3
- plain/models/aggregates.py +1 -1
- plain/models/backends/mysql/compiler.py +1 -1
- plain/models/backends/sqlite3/operations.py +1 -1
- plain/models/base.py +36 -94
- plain/models/constraints.py +2 -1
- plain/models/deletion.py +3 -5
- plain/models/exceptions.py +108 -0
- plain/models/expressions.py +1 -1
- plain/models/fields/__init__.py +3 -41
- plain/models/fields/related.py +15 -22
- plain/models/fields/related_descriptors.py +1 -3
- plain/models/fields/related_managers.py +1 -1
- plain/models/fields/reverse_related.py +3 -5
- plain/models/forms.py +1 -1
- plain/models/lookups.py +1 -1
- plain/models/migrations/state.py +1 -1
- plain/models/options.py +4 -16
- plain/models/query.py +12 -10
- plain/models/query_utils.py +2 -2
- plain/models/sql/compiler.py +6 -6
- plain/models/sql/datastructures.py +1 -1
- plain/models/sql/query.py +4 -4
- plain/models/sql/subqueries.py +3 -3
- plain/models/sql/where.py +1 -1
- {plain_models-0.48.0.dist-info → plain_models-0.49.1.dist-info}/METADATA +1 -1
- {plain_models-0.48.0.dist-info → plain_models-0.49.1.dist-info}/RECORD +31 -31
- {plain_models-0.48.0.dist-info → plain_models-0.49.1.dist-info}/WHEEL +0 -0
- {plain_models-0.48.0.dist-info → plain_models-0.49.1.dist-info}/entry_points.txt +0 -0
- {plain_models-0.48.0.dist-info → plain_models-0.49.1.dist-info}/licenses/LICENSE +0 -0
plain/models/CHANGELOG.md
CHANGED
@@ -1,5 +1,31 @@
|
|
1
1
|
# plain-models changelog
|
2
2
|
|
3
|
+
## [0.49.1](https://github.com/dropseed/plain/releases/plain-models@0.49.1) (2025-09-29)
|
4
|
+
|
5
|
+
### What's changed
|
6
|
+
|
7
|
+
- Fixed `get_field_display()` method to accept field name as string instead of field object ([1c20405](https://github.com/dropseed/plain/commit/1c20405ac3))
|
8
|
+
|
9
|
+
### Upgrade instructions
|
10
|
+
|
11
|
+
- No changes required
|
12
|
+
|
13
|
+
## [0.49.0](https://github.com/dropseed/plain/releases/plain-models@0.49.0) (2025-09-29)
|
14
|
+
|
15
|
+
### What's changed
|
16
|
+
|
17
|
+
- 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))
|
18
|
+
- 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))
|
19
|
+
- The `get_next_by_*` and `get_previous_by_*` methods for date fields have been removed ([3a5b8a8](https://github.com/dropseed/plain/commit/3a5b8a89d1))
|
20
|
+
- 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))
|
21
|
+
- Model `DoesNotExist` and `MultipleObjectsReturned` exceptions now use descriptors for better performance ([8f54ea3](https://github.com/dropseed/plain/commit/8f54ea3a62))
|
22
|
+
|
23
|
+
### Upgrade instructions
|
24
|
+
|
25
|
+
- 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`)
|
26
|
+
- Replace any usage of `instance.get_FOO_display()` with `instance.get_field_display("FOO")` where FOO is the field name
|
27
|
+
- 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()`)
|
28
|
+
|
3
29
|
## [0.48.0](https://github.com/dropseed/plain/releases/plain-models@0.48.0) (2025-09-26)
|
4
30
|
|
5
31
|
### 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",
|
plain/models/aggregates.py
CHANGED
@@ -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
|
@@ -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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
198
|
-
|
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.
|
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,22 @@ class Model(metaclass=ModelBase):
|
|
711
665
|
collector.collect([self])
|
712
666
|
return collector.delete()
|
713
667
|
|
714
|
-
def
|
668
|
+
def get_field_display(self, field_name: str) -> str:
|
669
|
+
"""Get the display value for a field, especially useful for fields with choices."""
|
670
|
+
# Get the field object from the field name
|
671
|
+
field = self._meta.get_field(field_name)
|
715
672
|
value = getattr(self, field.attname)
|
673
|
+
|
674
|
+
# If field has no choices, just return the value as string
|
675
|
+
if not hasattr(field, "flatchoices") or not field.flatchoices:
|
676
|
+
return force_str(value, strings_only=True)
|
677
|
+
|
678
|
+
# For fields with choices, look up the display value
|
716
679
|
choices_dict = dict(make_hashable(field.flatchoices))
|
717
|
-
# force_str() to coerce lazy strings.
|
718
680
|
return force_str(
|
719
681
|
choices_dict.get(make_hashable(value), value), strings_only=True
|
720
682
|
)
|
721
683
|
|
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
684
|
def _get_field_value_map(self, meta, exclude=None):
|
743
685
|
if exclude is None:
|
744
686
|
exclude = set()
|
plain/models/constraints.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
from enum import Enum
|
2
2
|
from types import NoneType
|
3
3
|
|
4
|
-
from plain.exceptions import
|
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
|
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
|
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
|
367
|
+
concrete_models.add(model)
|
370
368
|
found = True
|
371
369
|
if not found:
|
372
370
|
return
|
plain/models/exceptions.py
CHANGED
@@ -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
|
|
plain/models/expressions.py
CHANGED
@@ -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
|
plain/models/fields/__init__.py
CHANGED
@@ -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,
|
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
|
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
|
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
|
|
plain/models/fields/related.py
CHANGED
@@ -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
|
307
|
-
super().contribute_to_class(cls, name
|
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
|
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
|
586
|
-
super().contribute_to_class(cls, name
|
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
|
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
|
-
|
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.
|
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
|
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
|
-
#
|
1113
|
-
|
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
|
1249
|
-
super().contribute_to_class(cls, name
|
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
|
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
|
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
|
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,
|
plain/models/migrations/state.py
CHANGED
@@ -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.
|
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
|
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.
|
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
|
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
|
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
|
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
|
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.
|
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
|
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
|
2179
|
+
except FieldDoesNotExist:
|
2178
2180
|
pass
|
2179
2181
|
else:
|
2180
2182
|
msg = "to_attr={} conflicts with a field on the {} model."
|
plain/models/query_utils.py
CHANGED
@@ -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
|
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:
|
plain/models/sql/compiler.py
CHANGED
@@ -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
|
944
|
-
#
|
945
|
-
#
|
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
|
-
|
1334
|
+
model = klass_info["model"]
|
1335
1335
|
for select_index in klass_info["select_fields"]:
|
1336
|
-
if self.select[select_index][0].target.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():
|
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
|
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
|
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
|
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
|
)
|
plain/models/sql/subqueries.py
CHANGED
@@ -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
|
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().
|
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,30 +1,30 @@
|
|
1
1
|
plain/models/AGENTS.md,sha256=xQQW-z-DehnCUyjiGSBfLqUjoSUdo_W1b0JmwYmWieA,209
|
2
|
-
plain/models/CHANGELOG.md,sha256=
|
2
|
+
plain/models/CHANGELOG.md,sha256=zZeR5QuIb6G-lpmlyCcSsta_Fk_G516D7nY7TkMomms,20192
|
3
3
|
plain/models/README.md,sha256=lqzWJrEIxBCHC1P8X1YoRjbsMFlu0-kG4ujP76B_ZO4,8572
|
4
|
-
plain/models/__init__.py,sha256=
|
5
|
-
plain/models/aggregates.py,sha256=
|
6
|
-
plain/models/base.py,sha256
|
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=-eaUUAnLTPMckmO2PrIfWsqXSQAhOIZFwD_i7DrcI1I,63952
|
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=
|
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=
|
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=
|
19
|
-
plain/models/expressions.py,sha256=
|
20
|
-
plain/models/forms.py,sha256=
|
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=
|
23
|
-
plain/models/options.py,sha256=
|
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=
|
27
|
-
plain/models/query_utils.py,sha256=
|
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=
|
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=
|
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
|
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=
|
78
|
-
plain/models/fields/related_descriptors.py,sha256=
|
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=
|
81
|
-
plain/models/fields/reverse_related.py,sha256=
|
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=
|
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=
|
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=
|
112
|
-
plain/models/sql/query.py,sha256=
|
113
|
-
plain/models/sql/subqueries.py,sha256=
|
114
|
-
plain/models/sql/where.py,sha256=
|
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.
|
119
|
-
plain_models-0.
|
120
|
-
plain_models-0.
|
121
|
-
plain_models-0.
|
122
|
-
plain_models-0.
|
118
|
+
plain_models-0.49.1.dist-info/METADATA,sha256=ZGN3wPRNBPZcLfhw3SkUIBTXPObUL1Onb96Pd1OdrqY,8884
|
119
|
+
plain_models-0.49.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
120
|
+
plain_models-0.49.1.dist-info/entry_points.txt,sha256=IYJAW9MpL3PXyXFWmKmALagAGXC_5rzBn2eEGJlcV04,112
|
121
|
+
plain_models-0.49.1.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
|
122
|
+
plain_models-0.49.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|