plain.models 0.49.2__py3-none-any.whl → 0.51.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 +27 -0
- plain/models/README.md +26 -42
- plain/models/__init__.py +2 -0
- plain/models/aggregates.py +42 -19
- plain/models/backends/base/base.py +125 -105
- plain/models/backends/base/client.py +11 -3
- plain/models/backends/base/creation.py +24 -14
- plain/models/backends/base/features.py +10 -4
- plain/models/backends/base/introspection.py +37 -20
- plain/models/backends/base/operations.py +187 -91
- plain/models/backends/base/schema.py +338 -218
- plain/models/backends/base/validation.py +13 -4
- plain/models/backends/ddl_references.py +85 -43
- plain/models/backends/mysql/base.py +29 -26
- plain/models/backends/mysql/client.py +7 -2
- plain/models/backends/mysql/compiler.py +13 -4
- plain/models/backends/mysql/creation.py +5 -2
- plain/models/backends/mysql/features.py +24 -22
- plain/models/backends/mysql/introspection.py +22 -13
- plain/models/backends/mysql/operations.py +107 -40
- plain/models/backends/mysql/schema.py +52 -28
- plain/models/backends/mysql/validation.py +13 -6
- plain/models/backends/postgresql/base.py +41 -34
- plain/models/backends/postgresql/client.py +7 -2
- plain/models/backends/postgresql/creation.py +10 -5
- plain/models/backends/postgresql/introspection.py +15 -8
- plain/models/backends/postgresql/operations.py +110 -43
- plain/models/backends/postgresql/schema.py +88 -49
- plain/models/backends/sqlite3/_functions.py +151 -115
- plain/models/backends/sqlite3/base.py +37 -23
- plain/models/backends/sqlite3/client.py +7 -1
- plain/models/backends/sqlite3/creation.py +9 -5
- plain/models/backends/sqlite3/features.py +5 -3
- plain/models/backends/sqlite3/introspection.py +32 -16
- plain/models/backends/sqlite3/operations.py +126 -43
- plain/models/backends/sqlite3/schema.py +127 -92
- plain/models/backends/utils.py +52 -29
- plain/models/backups/cli.py +8 -6
- plain/models/backups/clients.py +16 -7
- plain/models/backups/core.py +24 -13
- plain/models/base.py +221 -229
- plain/models/cli.py +98 -67
- plain/models/config.py +1 -1
- plain/models/connections.py +23 -7
- plain/models/constraints.py +79 -56
- plain/models/database_url.py +1 -1
- plain/models/db.py +6 -2
- plain/models/deletion.py +80 -56
- plain/models/entrypoints.py +1 -1
- plain/models/enums.py +22 -11
- plain/models/exceptions.py +23 -8
- plain/models/expressions.py +441 -258
- plain/models/fields/__init__.py +272 -217
- plain/models/fields/json.py +123 -57
- plain/models/fields/mixins.py +12 -8
- plain/models/fields/related.py +324 -290
- plain/models/fields/related_descriptors.py +33 -24
- plain/models/fields/related_lookups.py +24 -12
- plain/models/fields/related_managers.py +102 -79
- plain/models/fields/reverse_related.py +66 -63
- plain/models/forms.py +101 -75
- plain/models/functions/comparison.py +71 -18
- plain/models/functions/datetime.py +79 -29
- plain/models/functions/math.py +43 -10
- plain/models/functions/mixins.py +24 -7
- plain/models/functions/text.py +104 -25
- plain/models/functions/window.py +12 -6
- plain/models/indexes.py +57 -32
- plain/models/lookups.py +228 -153
- plain/models/meta.py +505 -0
- plain/models/migrations/autodetector.py +86 -43
- plain/models/migrations/exceptions.py +7 -3
- plain/models/migrations/executor.py +33 -7
- plain/models/migrations/graph.py +79 -50
- plain/models/migrations/loader.py +45 -22
- plain/models/migrations/migration.py +23 -18
- plain/models/migrations/operations/base.py +38 -20
- plain/models/migrations/operations/fields.py +95 -48
- plain/models/migrations/operations/models.py +246 -142
- plain/models/migrations/operations/special.py +82 -25
- plain/models/migrations/optimizer.py +7 -2
- plain/models/migrations/questioner.py +58 -31
- plain/models/migrations/recorder.py +27 -16
- plain/models/migrations/serializer.py +50 -39
- plain/models/migrations/state.py +232 -156
- plain/models/migrations/utils.py +30 -14
- plain/models/migrations/writer.py +17 -14
- plain/models/options.py +189 -518
- plain/models/otel.py +16 -6
- plain/models/preflight.py +42 -17
- plain/models/query.py +400 -251
- plain/models/query_utils.py +109 -69
- plain/models/registry.py +40 -21
- plain/models/sql/compiler.py +190 -127
- plain/models/sql/datastructures.py +38 -25
- plain/models/sql/query.py +320 -225
- plain/models/sql/subqueries.py +36 -25
- plain/models/sql/where.py +54 -29
- plain/models/test/pytest.py +15 -11
- plain/models/test/utils.py +4 -2
- plain/models/transaction.py +20 -7
- plain/models/utils.py +17 -6
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/METADATA +27 -43
- plain_models-0.51.0.dist-info/RECORD +123 -0
- plain_models-0.49.2.dist-info/RECORD +0 -122
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/WHEEL +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/licenses/LICENSE +0 -0
plain/models/base.py
CHANGED
@@ -1,7 +1,14 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import copy
|
2
|
-
import inspect
|
3
4
|
import warnings
|
5
|
+
from collections.abc import Iterable, Iterator, Sequence
|
4
6
|
from itertools import chain
|
7
|
+
from typing import TYPE_CHECKING, Any
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from plain.models.meta import Meta
|
11
|
+
from plain.models.options import Options
|
5
12
|
|
6
13
|
import plain.runtime
|
7
14
|
from plain.exceptions import NON_FIELD_ERRORS, ValidationError
|
@@ -22,19 +29,19 @@ from plain.models.exceptions import (
|
|
22
29
|
from plain.models.expressions import RawSQL, Value
|
23
30
|
from plain.models.fields import NOT_PROVIDED, PrimaryKeyField
|
24
31
|
from plain.models.fields.reverse_related import ForeignObjectRel
|
32
|
+
from plain.models.meta import Meta
|
25
33
|
from plain.models.options import Options
|
26
34
|
from plain.models.query import F, Q, QuerySet
|
27
|
-
from plain.packages import packages_registry
|
28
35
|
from plain.preflight import PreflightResult
|
29
36
|
from plain.utils.encoding import force_str
|
30
37
|
from plain.utils.hashable import make_hashable
|
31
38
|
|
32
39
|
|
33
40
|
class Deferred:
|
34
|
-
def __repr__(self):
|
41
|
+
def __repr__(self) -> str:
|
35
42
|
return "<Deferred field>"
|
36
43
|
|
37
|
-
def __str__(self):
|
44
|
+
def __str__(self) -> str:
|
38
45
|
return "<Deferred field>"
|
39
46
|
|
40
47
|
|
@@ -44,7 +51,9 @@ DEFERRED = Deferred()
|
|
44
51
|
class ModelBase(type):
|
45
52
|
"""Metaclass for all models."""
|
46
53
|
|
47
|
-
def __new__(
|
54
|
+
def __new__(
|
55
|
+
cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any], **kwargs: Any
|
56
|
+
) -> type:
|
48
57
|
# Don't do any of this for the root models.Model class.
|
49
58
|
if not bases:
|
50
59
|
return super().__new__(cls, name, bases, attrs)
|
@@ -55,75 +64,14 @@ class ModelBase(type):
|
|
55
64
|
raise TypeError(
|
56
65
|
f"A model can't extend another model: {name} extends {base}"
|
57
66
|
)
|
58
|
-
# Meta has to be defined on the model itself.
|
59
|
-
if hasattr(base, "Meta"):
|
60
|
-
raise TypeError(
|
61
|
-
"Meta can only be defined on a model itself, not a parent class: "
|
62
|
-
f"{name} extends {base}"
|
63
|
-
)
|
64
|
-
|
65
|
-
new_class = super().__new__(cls, name, bases, attrs, **kwargs)
|
66
|
-
|
67
|
-
new_class._setup_meta()
|
68
|
-
|
69
|
-
# Now go back over all the attrs on this class see if they have a contribute_to_class() method.
|
70
|
-
# Attributes with contribute_to_class are fields and meta options.
|
71
|
-
for attr_name, attr_value in inspect.getmembers(new_class):
|
72
|
-
if attr_name.startswith("_"):
|
73
|
-
continue
|
74
|
-
|
75
|
-
if not inspect.isclass(attr_value) and hasattr(
|
76
|
-
attr_value, "contribute_to_class"
|
77
|
-
):
|
78
|
-
if attr_name not in attrs:
|
79
|
-
# If the field came from an inherited class/mixin,
|
80
|
-
# we need to make a copy of it to avoid altering the
|
81
|
-
# original class and other classes that inherit from it.
|
82
|
-
field = copy.deepcopy(attr_value)
|
83
|
-
else:
|
84
|
-
field = attr_value
|
85
|
-
field.contribute_to_class(new_class, attr_name)
|
86
|
-
|
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)
|
93
|
-
|
94
|
-
return new_class
|
95
|
-
|
96
|
-
def _setup_meta(cls):
|
97
|
-
name = cls.__name__
|
98
|
-
module = cls.__module__
|
99
|
-
|
100
|
-
# The model's Meta class, if it has one.
|
101
|
-
meta = getattr(cls, "Meta", None)
|
102
|
-
|
103
|
-
# Look for an application configuration to attach the model to.
|
104
|
-
package_config = packages_registry.get_containing_package_config(module)
|
105
|
-
|
106
|
-
package_label = getattr(meta, "package_label", None)
|
107
|
-
if package_label is None:
|
108
|
-
if package_config is None:
|
109
|
-
raise RuntimeError(
|
110
|
-
f"Model class {module}.{name} doesn't declare an explicit "
|
111
|
-
"package_label and isn't in an application in "
|
112
|
-
"INSTALLED_PACKAGES."
|
113
|
-
)
|
114
|
-
else:
|
115
|
-
package_label = package_config.package_label
|
116
|
-
|
117
|
-
Options(meta, package_label).contribute_to_class(cls, "_meta")
|
118
67
|
|
119
|
-
|
120
|
-
def query(cls) -> QuerySet:
|
121
|
-
"""Create a new QuerySet for this model."""
|
122
|
-
return cls._meta.queryset
|
68
|
+
return super().__new__(cls, name, bases, attrs, **kwargs)
|
123
69
|
|
124
70
|
|
125
71
|
class ModelStateFieldsCacheDescriptor:
|
126
|
-
def __get__(
|
72
|
+
def __get__(
|
73
|
+
self, instance: ModelState | None, cls: type | None = None
|
74
|
+
) -> ModelStateFieldsCacheDescriptor | dict[str, Any]:
|
127
75
|
if instance is None:
|
128
76
|
return self
|
129
77
|
res = instance.fields_cache = {}
|
@@ -142,19 +90,20 @@ class ModelState:
|
|
142
90
|
|
143
91
|
|
144
92
|
class Model(metaclass=ModelBase):
|
145
|
-
|
93
|
+
# Every model gets an automatic id field
|
94
|
+
id = PrimaryKeyField()
|
146
95
|
|
147
|
-
#
|
96
|
+
# Descriptors for other model behavior
|
97
|
+
query = QuerySet()
|
98
|
+
model_options = Options()
|
99
|
+
_model_meta = Meta()
|
148
100
|
DoesNotExist = DoesNotExistDescriptor()
|
149
101
|
MultipleObjectsReturned = MultipleObjectsReturnedDescriptor()
|
150
102
|
|
151
|
-
|
152
|
-
id = PrimaryKeyField()
|
153
|
-
|
154
|
-
def __init__(self, *args, **kwargs):
|
103
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
155
104
|
# Alias some things as locals to avoid repeat global lookups
|
156
105
|
cls = self.__class__
|
157
|
-
|
106
|
+
meta = cls._model_meta
|
158
107
|
_setattr = setattr
|
159
108
|
_DEFERRED = DEFERRED
|
160
109
|
|
@@ -165,12 +114,12 @@ class Model(metaclass=ModelBase):
|
|
165
114
|
# overrides it. It should be one or the other; don't duplicate the work
|
166
115
|
# The reason for the kwargs check is that standard iterator passes in by
|
167
116
|
# args, and instantiation for iteration is 33% faster.
|
168
|
-
if len(args) > len(
|
117
|
+
if len(args) > len(meta.concrete_fields):
|
169
118
|
# Daft, but matches old exception sans the err msg.
|
170
119
|
raise IndexError("Number of args exceeds number of fields")
|
171
120
|
|
172
121
|
if not kwargs:
|
173
|
-
fields_iter = iter(
|
122
|
+
fields_iter = iter(meta.concrete_fields)
|
174
123
|
# The ordering of the zip calls matter - zip throws StopIteration
|
175
124
|
# when an iter throws it. So if the first iter throws it, the second
|
176
125
|
# is *not* consumed. We rely on this, so don't change the order
|
@@ -181,7 +130,7 @@ class Model(metaclass=ModelBase):
|
|
181
130
|
_setattr(self, field.attname, val)
|
182
131
|
else:
|
183
132
|
# Slower, kwargs-ready version.
|
184
|
-
fields_iter = iter(
|
133
|
+
fields_iter = iter(meta.fields)
|
185
134
|
for val, field in zip(args, fields_iter):
|
186
135
|
if val is _DEFERRED:
|
187
136
|
continue
|
@@ -236,7 +185,7 @@ class Model(metaclass=ModelBase):
|
|
236
185
|
_setattr(self, field.attname, val)
|
237
186
|
|
238
187
|
if kwargs:
|
239
|
-
property_names =
|
188
|
+
property_names = meta._property_names
|
240
189
|
unexpected = ()
|
241
190
|
for prop, value in kwargs.items():
|
242
191
|
# Any remaining kwargs must correspond to properties or virtual
|
@@ -246,7 +195,7 @@ class Model(metaclass=ModelBase):
|
|
246
195
|
_setattr(self, prop, value)
|
247
196
|
else:
|
248
197
|
try:
|
249
|
-
|
198
|
+
meta.get_field(prop)
|
250
199
|
except FieldDoesNotExist:
|
251
200
|
unexpected += (prop,)
|
252
201
|
else:
|
@@ -261,24 +210,24 @@ class Model(metaclass=ModelBase):
|
|
261
210
|
super().__init__()
|
262
211
|
|
263
212
|
@classmethod
|
264
|
-
def from_db(cls, field_names, values):
|
265
|
-
if len(values) != len(cls.
|
213
|
+
def from_db(cls, field_names: Iterable[str], values: Sequence[Any]) -> Model:
|
214
|
+
if len(values) != len(cls._model_meta.concrete_fields):
|
266
215
|
values_iter = iter(values)
|
267
216
|
values = [
|
268
217
|
next(values_iter) if f.attname in field_names else DEFERRED
|
269
|
-
for f in cls.
|
218
|
+
for f in cls._model_meta.concrete_fields
|
270
219
|
]
|
271
220
|
new = cls(*values)
|
272
221
|
new._state.adding = False
|
273
222
|
return new
|
274
223
|
|
275
|
-
def __repr__(self):
|
224
|
+
def __repr__(self) -> str:
|
276
225
|
return f"<{self.__class__.__name__}: {self}>"
|
277
226
|
|
278
|
-
def __str__(self):
|
227
|
+
def __str__(self) -> str:
|
279
228
|
return f"{self.__class__.__name__} object ({self.id})"
|
280
229
|
|
281
|
-
def __eq__(self, other):
|
230
|
+
def __eq__(self, other: object) -> bool:
|
282
231
|
if not isinstance(other, Model):
|
283
232
|
return NotImplemented
|
284
233
|
if self.__class__ != other.__class__:
|
@@ -288,18 +237,21 @@ class Model(metaclass=ModelBase):
|
|
288
237
|
return self is other
|
289
238
|
return my_id == other.id
|
290
239
|
|
291
|
-
def __hash__(self):
|
240
|
+
def __hash__(self) -> int:
|
292
241
|
if self.id is None:
|
293
242
|
raise TypeError("Model instances without primary key value are unhashable")
|
294
243
|
return hash(self.id)
|
295
244
|
|
296
|
-
def __reduce__(self):
|
245
|
+
def __reduce__(self) -> tuple[Any, tuple[Any, ...], dict[str, Any]]:
|
297
246
|
data = self.__getstate__()
|
298
247
|
data[PLAIN_VERSION_PICKLE_KEY] = plain.runtime.__version__
|
299
|
-
class_id =
|
248
|
+
class_id = (
|
249
|
+
self.model_options.package_label,
|
250
|
+
self.model_options.object_name,
|
251
|
+
)
|
300
252
|
return model_unpickle, (class_id,), data
|
301
253
|
|
302
|
-
def __getstate__(self):
|
254
|
+
def __getstate__(self) -> dict[str, Any]:
|
303
255
|
"""Hook to allow choosing the attributes to pickle."""
|
304
256
|
state = self.__dict__.copy()
|
305
257
|
state["_state"] = copy.copy(state["_state"])
|
@@ -316,7 +268,7 @@ class Model(metaclass=ModelBase):
|
|
316
268
|
state.pop(attr)
|
317
269
|
return state
|
318
270
|
|
319
|
-
def __setstate__(self, state):
|
271
|
+
def __setstate__(self, state: dict[str, Any]) -> None:
|
320
272
|
pickled_version = state.get(PLAIN_VERSION_PICKLE_KEY)
|
321
273
|
if pickled_version:
|
322
274
|
if pickled_version != plain.runtime.__version__:
|
@@ -337,17 +289,17 @@ class Model(metaclass=ModelBase):
|
|
337
289
|
state[attr] = memoryview(value)
|
338
290
|
self.__dict__.update(state)
|
339
291
|
|
340
|
-
def get_deferred_fields(self):
|
292
|
+
def get_deferred_fields(self) -> set[str]:
|
341
293
|
"""
|
342
294
|
Return a set containing names of deferred fields for this instance.
|
343
295
|
"""
|
344
296
|
return {
|
345
297
|
f.attname
|
346
|
-
for f in self.
|
298
|
+
for f in self._model_meta.concrete_fields
|
347
299
|
if f.attname not in self.__dict__
|
348
300
|
}
|
349
301
|
|
350
|
-
def refresh_from_db(self, fields=None):
|
302
|
+
def refresh_from_db(self, fields: list[str] | None = None) -> None:
|
351
303
|
"""
|
352
304
|
Reload field values from the database.
|
353
305
|
|
@@ -368,7 +320,7 @@ class Model(metaclass=ModelBase):
|
|
368
320
|
prefetched_objects_cache = getattr(self, "_prefetched_objects_cache", ())
|
369
321
|
for field in fields:
|
370
322
|
if field in prefetched_objects_cache:
|
371
|
-
del prefetched_objects_cache[field]
|
323
|
+
del prefetched_objects_cache[field] # type: ignore[misc]
|
372
324
|
fields.remove(field)
|
373
325
|
if not fields:
|
374
326
|
return
|
@@ -378,7 +330,7 @@ class Model(metaclass=ModelBase):
|
|
378
330
|
"are not allowed in fields."
|
379
331
|
)
|
380
332
|
|
381
|
-
db_instance_qs = self.
|
333
|
+
db_instance_qs = self._model_meta.base_queryset.filter(id=self.id)
|
382
334
|
|
383
335
|
# Use provided fields, if not set then reload all non-deferred fields.
|
384
336
|
deferred_fields = self.get_deferred_fields()
|
@@ -388,14 +340,14 @@ class Model(metaclass=ModelBase):
|
|
388
340
|
elif deferred_fields:
|
389
341
|
fields = [
|
390
342
|
f.attname
|
391
|
-
for f in self.
|
343
|
+
for f in self._model_meta.concrete_fields
|
392
344
|
if f.attname not in deferred_fields
|
393
345
|
]
|
394
346
|
db_instance_qs = db_instance_qs.only(*fields)
|
395
347
|
|
396
348
|
db_instance = db_instance_qs.get()
|
397
349
|
non_loaded_fields = db_instance.get_deferred_fields()
|
398
|
-
for field in self.
|
350
|
+
for field in self._model_meta.concrete_fields:
|
399
351
|
if field.attname in non_loaded_fields:
|
400
352
|
# This field wasn't refreshed - skip ahead.
|
401
353
|
continue
|
@@ -405,11 +357,11 @@ class Model(metaclass=ModelBase):
|
|
405
357
|
field.delete_cached_value(self)
|
406
358
|
|
407
359
|
# Clear cached relations.
|
408
|
-
for field in self.
|
360
|
+
for field in self._model_meta.related_objects:
|
409
361
|
if field.is_cached(self):
|
410
362
|
field.delete_cached_value(self)
|
411
363
|
|
412
|
-
def serializable_value(self, field_name):
|
364
|
+
def serializable_value(self, field_name: str) -> Any:
|
413
365
|
"""
|
414
366
|
Return the value of the field name for this instance. If the field is
|
415
367
|
a foreign key, return the id value instead of the object. If there's
|
@@ -421,7 +373,7 @@ class Model(metaclass=ModelBase):
|
|
421
373
|
and not use this method.
|
422
374
|
"""
|
423
375
|
try:
|
424
|
-
field = self.
|
376
|
+
field = self._model_meta.get_field(field_name)
|
425
377
|
except FieldDoesNotExist:
|
426
378
|
return getattr(self, field_name)
|
427
379
|
return getattr(self, field.attname)
|
@@ -429,11 +381,11 @@ class Model(metaclass=ModelBase):
|
|
429
381
|
def save(
|
430
382
|
self,
|
431
383
|
*,
|
432
|
-
clean_and_validate=True,
|
433
|
-
force_insert=False,
|
434
|
-
force_update=False,
|
435
|
-
update_fields=None,
|
436
|
-
):
|
384
|
+
clean_and_validate: bool = True,
|
385
|
+
force_insert: bool = False,
|
386
|
+
force_update: bool = False,
|
387
|
+
update_fields: Iterable[str] | None = None,
|
388
|
+
) -> None:
|
437
389
|
"""
|
438
390
|
Save the current instance. Override this in a subclass if you want to
|
439
391
|
control the saving process.
|
@@ -456,7 +408,7 @@ class Model(metaclass=ModelBase):
|
|
456
408
|
return
|
457
409
|
|
458
410
|
update_fields = frozenset(update_fields)
|
459
|
-
field_names = self.
|
411
|
+
field_names = self._model_meta._non_pk_concrete_field_names
|
460
412
|
non_model_fields = update_fields.difference(field_names)
|
461
413
|
|
462
414
|
if non_model_fields:
|
@@ -471,7 +423,7 @@ class Model(metaclass=ModelBase):
|
|
471
423
|
# on the loaded fields.
|
472
424
|
elif not force_insert and deferred_fields:
|
473
425
|
field_names = set()
|
474
|
-
for field in self.
|
426
|
+
for field in self._model_meta.concrete_fields:
|
475
427
|
if not field.primary_key and not hasattr(field, "through"):
|
476
428
|
field_names.add(field.attname)
|
477
429
|
loaded_fields = field_names.difference(deferred_fields)
|
@@ -490,11 +442,11 @@ class Model(metaclass=ModelBase):
|
|
490
442
|
def save_base(
|
491
443
|
self,
|
492
444
|
*,
|
493
|
-
raw=False,
|
494
|
-
force_insert=False,
|
495
|
-
force_update=False,
|
496
|
-
update_fields=None,
|
497
|
-
):
|
445
|
+
raw: bool = False,
|
446
|
+
force_insert: bool = False,
|
447
|
+
force_update: bool = False,
|
448
|
+
update_fields: Iterable[str] | None = None,
|
449
|
+
) -> None:
|
498
450
|
"""
|
499
451
|
Handle the parts of saving which should be done only once per save,
|
500
452
|
yet need to be done in raw saves, too. This includes some sanity
|
@@ -510,28 +462,29 @@ class Model(metaclass=ModelBase):
|
|
510
462
|
|
511
463
|
with transaction.mark_for_rollback_on_error():
|
512
464
|
self._save_table(
|
513
|
-
raw,
|
514
|
-
cls,
|
515
|
-
force_insert,
|
516
|
-
force_update,
|
517
|
-
update_fields,
|
465
|
+
raw=raw,
|
466
|
+
cls=cls,
|
467
|
+
force_insert=force_insert,
|
468
|
+
force_update=force_update,
|
469
|
+
update_fields=update_fields,
|
518
470
|
)
|
519
471
|
# Once saved, this is no longer a to-be-added instance.
|
520
472
|
self._state.adding = False
|
521
473
|
|
522
474
|
def _save_table(
|
523
475
|
self,
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
476
|
+
*,
|
477
|
+
raw: bool,
|
478
|
+
cls: type[Model],
|
479
|
+
force_insert: bool = False,
|
480
|
+
force_update: bool = False,
|
481
|
+
update_fields: Iterable[str] | None = None,
|
482
|
+
) -> bool:
|
530
483
|
"""
|
531
484
|
Do the heavy-lifting involved in saving. Update or insert the data
|
532
485
|
for a single table.
|
533
486
|
"""
|
534
|
-
meta = cls.
|
487
|
+
meta = cls._model_meta
|
535
488
|
non_pks = [f for f in meta.local_concrete_fields if not f.primary_key]
|
536
489
|
|
537
490
|
if update_fields:
|
@@ -591,7 +544,14 @@ class Model(metaclass=ModelBase):
|
|
591
544
|
setattr(self, field.attname, value)
|
592
545
|
return updated
|
593
546
|
|
594
|
-
def _do_update(
|
547
|
+
def _do_update(
|
548
|
+
self,
|
549
|
+
base_qs: QuerySet,
|
550
|
+
id_val: Any,
|
551
|
+
values: list[tuple[Any, Any, Any]],
|
552
|
+
update_fields: Iterable[str] | None,
|
553
|
+
forced_update: bool,
|
554
|
+
) -> bool:
|
595
555
|
"""
|
596
556
|
Try to update the model. Return True if the model was updated (if an
|
597
557
|
update query was done and a matching row was found in the DB).
|
@@ -606,22 +566,30 @@ class Model(metaclass=ModelBase):
|
|
606
566
|
return update_fields is not None or filtered.exists()
|
607
567
|
return filtered._update(values) > 0
|
608
568
|
|
609
|
-
def _do_insert(
|
569
|
+
def _do_insert(
|
570
|
+
self,
|
571
|
+
manager: QuerySet,
|
572
|
+
fields: Sequence[Any],
|
573
|
+
returning_fields: Sequence[Any],
|
574
|
+
raw: bool,
|
575
|
+
) -> list[Any]:
|
610
576
|
"""
|
611
577
|
Do an INSERT. If returning_fields is defined then this method should
|
612
578
|
return the newly created data for the model.
|
613
579
|
"""
|
614
|
-
return manager._insert(
|
580
|
+
return manager._insert( # type: ignore[return-value, arg-type]
|
615
581
|
[self],
|
616
|
-
fields=fields,
|
617
|
-
returning_fields=returning_fields,
|
582
|
+
fields=fields, # type: ignore[arg-type]
|
583
|
+
returning_fields=returning_fields, # type: ignore[arg-type]
|
618
584
|
raw=raw,
|
619
585
|
)
|
620
586
|
|
621
|
-
def _prepare_related_fields_for_save(
|
587
|
+
def _prepare_related_fields_for_save(
|
588
|
+
self, operation_name: str, fields: Sequence[Any] | None = None
|
589
|
+
) -> None:
|
622
590
|
# Ensure that a model instance without a PK hasn't been assigned to
|
623
591
|
# a ForeignKey on this model. If the field is nullable, allowing the save would result in silent data loss.
|
624
|
-
for field in self.
|
592
|
+
for field in self._model_meta.concrete_fields:
|
625
593
|
if fields and field not in fields:
|
626
594
|
continue
|
627
595
|
# If the related field isn't cached, then an instance hasn't been
|
@@ -655,10 +623,10 @@ class Model(metaclass=ModelBase):
|
|
655
623
|
):
|
656
624
|
field.delete_cached_value(self)
|
657
625
|
|
658
|
-
def delete(self):
|
626
|
+
def delete(self) -> tuple[int, dict[str, int]]:
|
659
627
|
if self.id is None:
|
660
628
|
raise ValueError(
|
661
|
-
f"{self.
|
629
|
+
f"{self.model_options.object_name} object can't be deleted because its id attribute is set "
|
662
630
|
"to None."
|
663
631
|
)
|
664
632
|
collector = Collector(origin=self)
|
@@ -668,7 +636,7 @@ class Model(metaclass=ModelBase):
|
|
668
636
|
def get_field_display(self, field_name: str) -> str:
|
669
637
|
"""Get the display value for a field, especially useful for fields with choices."""
|
670
638
|
# Get the field object from the field name
|
671
|
-
field = self.
|
639
|
+
field = self._model_meta.get_field(field_name)
|
672
640
|
value = getattr(self, field.attname)
|
673
641
|
|
674
642
|
# If field has no choices, just return the value as string
|
@@ -681,24 +649,26 @@ class Model(metaclass=ModelBase):
|
|
681
649
|
choices_dict.get(make_hashable(value), value), strings_only=True
|
682
650
|
)
|
683
651
|
|
684
|
-
def _get_field_value_map(
|
652
|
+
def _get_field_value_map(
|
653
|
+
self, meta: Meta | None, exclude: set[str] | None = None
|
654
|
+
) -> dict[str, Value]:
|
685
655
|
if exclude is None:
|
686
656
|
exclude = set()
|
687
|
-
meta = meta or self.
|
657
|
+
meta = meta or self._model_meta
|
688
658
|
return {
|
689
659
|
field.name: Value(getattr(self, field.attname), field)
|
690
660
|
for field in meta.local_concrete_fields
|
691
661
|
if field.name not in exclude
|
692
662
|
}
|
693
663
|
|
694
|
-
def prepare_database_save(self, field):
|
664
|
+
def prepare_database_save(self, field: Any) -> Any:
|
695
665
|
if self.id is None:
|
696
666
|
raise ValueError(
|
697
667
|
f"Unsaved model instance {self!r} cannot be used in an ORM query."
|
698
668
|
)
|
699
669
|
return getattr(self, field.remote_field.get_related_field().attname)
|
700
670
|
|
701
|
-
def clean(self):
|
671
|
+
def clean(self) -> None:
|
702
672
|
"""
|
703
673
|
Hook for doing any extra model-wide validation after clean() has been
|
704
674
|
called on every field by self.clean_fields. Any ValidationError raised
|
@@ -707,7 +677,7 @@ class Model(metaclass=ModelBase):
|
|
707
677
|
"""
|
708
678
|
pass
|
709
679
|
|
710
|
-
def validate_unique(self, exclude=None):
|
680
|
+
def validate_unique(self, exclude: set[str] | None = None) -> None:
|
711
681
|
"""
|
712
682
|
Check unique constraints on the model and raise ValidationError if any
|
713
683
|
failed.
|
@@ -717,7 +687,9 @@ class Model(metaclass=ModelBase):
|
|
717
687
|
if errors := self._perform_unique_checks(unique_checks):
|
718
688
|
raise ValidationError(errors)
|
719
689
|
|
720
|
-
def _get_unique_checks(
|
690
|
+
def _get_unique_checks(
|
691
|
+
self, exclude: set[str] | None = None
|
692
|
+
) -> list[tuple[type, tuple[str, ...]]]:
|
721
693
|
"""
|
722
694
|
Return a list of checks to perform. Since validate_unique() could be
|
723
695
|
called from a ModelForm, some fields may have been excluded; we can't
|
@@ -732,7 +704,7 @@ class Model(metaclass=ModelBase):
|
|
732
704
|
# Gather a list of checks for fields declared as unique and add them to
|
733
705
|
# the list of checks.
|
734
706
|
|
735
|
-
fields_with_class = [(self.__class__, self.
|
707
|
+
fields_with_class = [(self.__class__, self._model_meta.local_fields)]
|
736
708
|
|
737
709
|
for model_class, fields in fields_with_class:
|
738
710
|
for f in fields:
|
@@ -744,7 +716,9 @@ class Model(metaclass=ModelBase):
|
|
744
716
|
|
745
717
|
return unique_checks
|
746
718
|
|
747
|
-
def _perform_unique_checks(
|
719
|
+
def _perform_unique_checks(
|
720
|
+
self, unique_checks: list[tuple[type, tuple[str, ...]]]
|
721
|
+
) -> dict[str, list[ValidationError]]:
|
748
722
|
errors = {}
|
749
723
|
|
750
724
|
for model_class, unique_check in unique_checks:
|
@@ -753,7 +727,7 @@ class Model(metaclass=ModelBase):
|
|
753
727
|
|
754
728
|
lookup_kwargs = {}
|
755
729
|
for field_name in unique_check:
|
756
|
-
f = self.
|
730
|
+
f = self._model_meta.get_field(field_name)
|
757
731
|
lookup_value = getattr(self, f.attname)
|
758
732
|
# TODO: Handle multiple backends with different feature flags.
|
759
733
|
if lookup_value is None:
|
@@ -768,7 +742,7 @@ class Model(metaclass=ModelBase):
|
|
768
742
|
if len(unique_check) != len(lookup_kwargs):
|
769
743
|
continue
|
770
744
|
|
771
|
-
qs = model_class.query.filter(**lookup_kwargs)
|
745
|
+
qs = model_class.query.filter(**lookup_kwargs) # type: ignore[attr-defined]
|
772
746
|
|
773
747
|
# Exclude the current object from the query if we are editing an
|
774
748
|
# instance (as opposed to creating a new one)
|
@@ -788,18 +762,20 @@ class Model(metaclass=ModelBase):
|
|
788
762
|
|
789
763
|
return errors
|
790
764
|
|
791
|
-
def unique_error_message(
|
792
|
-
|
765
|
+
def unique_error_message(
|
766
|
+
self, model_class: type[Model], unique_check: tuple[str, ...]
|
767
|
+
) -> ValidationError:
|
768
|
+
meta = model_class._model_meta
|
793
769
|
|
794
770
|
params = {
|
795
771
|
"model": self,
|
796
772
|
"model_class": model_class,
|
797
|
-
"model_name":
|
773
|
+
"model_name": model_class.model_options.model_name,
|
798
774
|
"unique_check": unique_check,
|
799
775
|
}
|
800
776
|
|
801
777
|
if len(unique_check) == 1:
|
802
|
-
field =
|
778
|
+
field = meta.get_field(unique_check[0])
|
803
779
|
params["field_label"] = field.name
|
804
780
|
return ValidationError(
|
805
781
|
message=field.error_messages["unique"],
|
@@ -807,7 +783,7 @@ class Model(metaclass=ModelBase):
|
|
807
783
|
params=params,
|
808
784
|
)
|
809
785
|
else:
|
810
|
-
field_names = [
|
786
|
+
field_names = [meta.get_field(f).name for f in unique_check]
|
811
787
|
|
812
788
|
# Put an "and" before the last one
|
813
789
|
field_names[-1] = f"and {field_names[-1]}"
|
@@ -820,7 +796,7 @@ class Model(metaclass=ModelBase):
|
|
820
796
|
params["field_label"] = " ".join(field_names)
|
821
797
|
|
822
798
|
# Use the first field as the message format...
|
823
|
-
message =
|
799
|
+
message = meta.get_field(unique_check[0]).error_messages["unique"]
|
824
800
|
|
825
801
|
return ValidationError(
|
826
802
|
message=message,
|
@@ -828,11 +804,11 @@ class Model(metaclass=ModelBase):
|
|
828
804
|
params=params,
|
829
805
|
)
|
830
806
|
|
831
|
-
def get_constraints(self):
|
832
|
-
constraints = [(self.__class__, self.
|
807
|
+
def get_constraints(self) -> list[tuple[type, list[Any]]]:
|
808
|
+
constraints = [(self.__class__, self.model_options.constraints)]
|
833
809
|
return constraints
|
834
810
|
|
835
|
-
def validate_constraints(self, exclude=None):
|
811
|
+
def validate_constraints(self, exclude: set[str] | None = None) -> None:
|
836
812
|
constraints = self.get_constraints()
|
837
813
|
|
838
814
|
errors = {}
|
@@ -852,8 +828,12 @@ class Model(metaclass=ModelBase):
|
|
852
828
|
raise ValidationError(errors)
|
853
829
|
|
854
830
|
def full_clean(
|
855
|
-
self,
|
856
|
-
|
831
|
+
self,
|
832
|
+
*,
|
833
|
+
exclude: set[str] | Iterable[str] | None = None,
|
834
|
+
validate_unique: bool = True,
|
835
|
+
validate_constraints: bool = True,
|
836
|
+
) -> None:
|
857
837
|
"""
|
858
838
|
Call clean_fields(), clean(), validate_unique(), and
|
859
839
|
validate_constraints() on the model. Raise a ValidationError for any
|
@@ -900,7 +880,7 @@ class Model(metaclass=ModelBase):
|
|
900
880
|
if errors:
|
901
881
|
raise ValidationError(errors)
|
902
882
|
|
903
|
-
def clean_fields(self, exclude=None):
|
883
|
+
def clean_fields(self, exclude: set[str] | None = None) -> None:
|
904
884
|
"""
|
905
885
|
Clean all fields and raise a ValidationError containing a dict
|
906
886
|
of all validation errors if any occur.
|
@@ -909,7 +889,7 @@ class Model(metaclass=ModelBase):
|
|
909
889
|
exclude = set()
|
910
890
|
|
911
891
|
errors = {}
|
912
|
-
for f in self.
|
892
|
+
for f in self._model_meta.fields:
|
913
893
|
if f.name in exclude:
|
914
894
|
continue
|
915
895
|
# Skip validation for empty fields with required=False. The developer
|
@@ -926,7 +906,7 @@ class Model(metaclass=ModelBase):
|
|
926
906
|
raise ValidationError(errors)
|
927
907
|
|
928
908
|
@classmethod
|
929
|
-
def preflight(cls):
|
909
|
+
def preflight(cls) -> list[PreflightResult]:
|
930
910
|
errors = []
|
931
911
|
|
932
912
|
errors += [
|
@@ -956,13 +936,13 @@ class Model(metaclass=ModelBase):
|
|
956
936
|
return errors
|
957
937
|
|
958
938
|
@classmethod
|
959
|
-
def _check_db_table_comment(cls):
|
960
|
-
if not cls.
|
939
|
+
def _check_db_table_comment(cls) -> list[PreflightResult]:
|
940
|
+
if not cls.model_options.db_table_comment:
|
961
941
|
return []
|
962
942
|
errors = []
|
963
943
|
if not (
|
964
944
|
db_connection.features.supports_comments
|
965
|
-
or "supports_comments" in cls.
|
945
|
+
or "supports_comments" in cls.model_options.required_db_features
|
966
946
|
):
|
967
947
|
errors.append(
|
968
948
|
PreflightResult(
|
@@ -976,23 +956,23 @@ class Model(metaclass=ModelBase):
|
|
976
956
|
return errors
|
977
957
|
|
978
958
|
@classmethod
|
979
|
-
def _check_fields(cls):
|
959
|
+
def _check_fields(cls) -> list[PreflightResult]:
|
980
960
|
"""Perform all field checks."""
|
981
961
|
errors = []
|
982
|
-
for field in cls.
|
962
|
+
for field in cls._model_meta.local_fields:
|
983
963
|
errors.extend(field.preflight(from_model=cls))
|
984
|
-
for field in cls.
|
964
|
+
for field in cls._model_meta.local_many_to_many:
|
985
965
|
errors.extend(field.preflight(from_model=cls))
|
986
966
|
return errors
|
987
967
|
|
988
968
|
@classmethod
|
989
|
-
def _check_m2m_through_same_relationship(cls):
|
969
|
+
def _check_m2m_through_same_relationship(cls) -> list[PreflightResult]:
|
990
970
|
"""Check if no relationship model is used by more than one m2m field."""
|
991
971
|
|
992
972
|
errors = []
|
993
973
|
seen_intermediary_signatures = []
|
994
974
|
|
995
|
-
fields = cls.
|
975
|
+
fields = cls._model_meta.local_many_to_many
|
996
976
|
|
997
977
|
# Skip when the target model wasn't found.
|
998
978
|
fields = (f for f in fields if isinstance(f.remote_field.model, ModelBase))
|
@@ -1011,7 +991,7 @@ class Model(metaclass=ModelBase):
|
|
1011
991
|
errors.append(
|
1012
992
|
PreflightResult(
|
1013
993
|
fix="The model has two identical many-to-many relations "
|
1014
|
-
f"through the intermediate model '{f.remote_field.through.
|
994
|
+
f"through the intermediate model '{f.remote_field.through.model_options.label}'.",
|
1015
995
|
obj=cls,
|
1016
996
|
id="models.duplicate_many_to_many_relations",
|
1017
997
|
)
|
@@ -1021,10 +1001,12 @@ class Model(metaclass=ModelBase):
|
|
1021
1001
|
return errors
|
1022
1002
|
|
1023
1003
|
@classmethod
|
1024
|
-
def _check_id_field(cls):
|
1004
|
+
def _check_id_field(cls) -> list[PreflightResult]:
|
1025
1005
|
"""Disallow user-defined fields named ``id``."""
|
1026
1006
|
if any(
|
1027
|
-
f
|
1007
|
+
f
|
1008
|
+
for f in cls._model_meta.local_fields
|
1009
|
+
if f.name == "id" and not f.auto_created
|
1028
1010
|
):
|
1029
1011
|
return [
|
1030
1012
|
PreflightResult(
|
@@ -1036,12 +1018,12 @@ class Model(metaclass=ModelBase):
|
|
1036
1018
|
return []
|
1037
1019
|
|
1038
1020
|
@classmethod
|
1039
|
-
def _check_field_name_clashes(cls):
|
1021
|
+
def _check_field_name_clashes(cls) -> list[PreflightResult]:
|
1040
1022
|
"""Forbid field shadowing in multi-table inheritance."""
|
1041
1023
|
errors = []
|
1042
1024
|
used_fields = {} # name or attname -> field
|
1043
1025
|
|
1044
|
-
for f in cls.
|
1026
|
+
for f in cls._model_meta.local_fields:
|
1045
1027
|
clash = used_fields.get(f.name) or used_fields.get(f.attname) or None
|
1046
1028
|
# Note that we may detect clash between user-defined non-unique
|
1047
1029
|
# field "id" and automatically added unique field "id", both
|
@@ -1054,7 +1036,7 @@ class Model(metaclass=ModelBase):
|
|
1054
1036
|
errors.append(
|
1055
1037
|
PreflightResult(
|
1056
1038
|
fix=f"The field '{f.name}' clashes with the field '{clash.name}' "
|
1057
|
-
f"from model '{clash.model.
|
1039
|
+
f"from model '{clash.model.model_options}'.",
|
1058
1040
|
obj=f,
|
1059
1041
|
id="models.field_name_clash",
|
1060
1042
|
)
|
@@ -1065,12 +1047,12 @@ class Model(metaclass=ModelBase):
|
|
1065
1047
|
return errors
|
1066
1048
|
|
1067
1049
|
@classmethod
|
1068
|
-
def _check_column_name_clashes(cls):
|
1050
|
+
def _check_column_name_clashes(cls) -> list[PreflightResult]:
|
1069
1051
|
# Store a list of column names which have already been used by other fields.
|
1070
1052
|
used_column_names = []
|
1071
1053
|
errors = []
|
1072
1054
|
|
1073
|
-
for f in cls.
|
1055
|
+
for f in cls._model_meta.local_fields:
|
1074
1056
|
_, column_name = f.get_attname_column()
|
1075
1057
|
|
1076
1058
|
# Ensure the column name is not already in use.
|
@@ -1089,7 +1071,7 @@ class Model(metaclass=ModelBase):
|
|
1089
1071
|
return errors
|
1090
1072
|
|
1091
1073
|
@classmethod
|
1092
|
-
def _check_model_name_db_lookup_clashes(cls):
|
1074
|
+
def _check_model_name_db_lookup_clashes(cls) -> list[PreflightResult]:
|
1093
1075
|
errors = []
|
1094
1076
|
model_name = cls.__name__
|
1095
1077
|
if model_name.startswith("_") or model_name.endswith("_"):
|
@@ -1113,12 +1095,14 @@ class Model(metaclass=ModelBase):
|
|
1113
1095
|
return errors
|
1114
1096
|
|
1115
1097
|
@classmethod
|
1116
|
-
def _check_property_name_related_field_accessor_clashes(
|
1098
|
+
def _check_property_name_related_field_accessor_clashes(
|
1099
|
+
cls,
|
1100
|
+
) -> list[PreflightResult]:
|
1117
1101
|
errors = []
|
1118
|
-
property_names = cls.
|
1102
|
+
property_names = cls._model_meta._property_names
|
1119
1103
|
related_field_accessors = (
|
1120
1104
|
f.get_attname()
|
1121
|
-
for f in cls.
|
1105
|
+
for f in cls._model_meta._get_fields(reverse=False)
|
1122
1106
|
if f.is_relation and f.related_model is not None
|
1123
1107
|
)
|
1124
1108
|
for accessor in related_field_accessors:
|
@@ -1134,9 +1118,9 @@ class Model(metaclass=ModelBase):
|
|
1134
1118
|
return errors
|
1135
1119
|
|
1136
1120
|
@classmethod
|
1137
|
-
def _check_single_primary_key(cls):
|
1121
|
+
def _check_single_primary_key(cls) -> list[PreflightResult]:
|
1138
1122
|
errors = []
|
1139
|
-
if sum(1 for f in cls.
|
1123
|
+
if sum(1 for f in cls._model_meta.local_fields if f.primary_key) > 1:
|
1140
1124
|
errors.append(
|
1141
1125
|
PreflightResult(
|
1142
1126
|
fix="The model cannot have more than one field with "
|
@@ -1148,11 +1132,11 @@ class Model(metaclass=ModelBase):
|
|
1148
1132
|
return errors
|
1149
1133
|
|
1150
1134
|
@classmethod
|
1151
|
-
def _check_indexes(cls):
|
1135
|
+
def _check_indexes(cls) -> list[PreflightResult]:
|
1152
1136
|
"""Check fields, names, and conditions of indexes."""
|
1153
1137
|
errors = []
|
1154
1138
|
references = set()
|
1155
|
-
for index in cls.
|
1139
|
+
for index in cls.model_options.indexes:
|
1156
1140
|
# Index name can't start with an underscore or a number, restricted
|
1157
1141
|
# for cross-database compatibility with Oracle.
|
1158
1142
|
if index.name[0] == "_" or index.name[0].isdigit():
|
@@ -1180,8 +1164,8 @@ class Model(metaclass=ModelBase):
|
|
1180
1164
|
)
|
1181
1165
|
if not (
|
1182
1166
|
db_connection.features.supports_partial_indexes
|
1183
|
-
or "supports_partial_indexes" in cls.
|
1184
|
-
) and any(index.condition is not None for index in cls.
|
1167
|
+
or "supports_partial_indexes" in cls.model_options.required_db_features
|
1168
|
+
) and any(index.condition is not None for index in cls.model_options.indexes):
|
1185
1169
|
errors.append(
|
1186
1170
|
PreflightResult(
|
1187
1171
|
fix=f"{db_connection.display_name} does not support indexes with conditions. "
|
@@ -1194,8 +1178,8 @@ class Model(metaclass=ModelBase):
|
|
1194
1178
|
)
|
1195
1179
|
if not (
|
1196
1180
|
db_connection.features.supports_covering_indexes
|
1197
|
-
or "supports_covering_indexes" in cls.
|
1198
|
-
) and any(index.include for index in cls.
|
1181
|
+
or "supports_covering_indexes" in cls.model_options.required_db_features
|
1182
|
+
) and any(index.include for index in cls.model_options.indexes):
|
1199
1183
|
errors.append(
|
1200
1184
|
PreflightResult(
|
1201
1185
|
fix=f"{db_connection.display_name} does not support indexes with non-key columns. "
|
@@ -1208,8 +1192,8 @@ class Model(metaclass=ModelBase):
|
|
1208
1192
|
)
|
1209
1193
|
if not (
|
1210
1194
|
db_connection.features.supports_expression_indexes
|
1211
|
-
or "supports_expression_indexes" in cls.
|
1212
|
-
) and any(index.contains_expressions for index in cls.
|
1195
|
+
or "supports_expression_indexes" in cls.model_options.required_db_features
|
1196
|
+
) and any(index.contains_expressions for index in cls.model_options.indexes):
|
1213
1197
|
errors.append(
|
1214
1198
|
PreflightResult(
|
1215
1199
|
fix=f"{db_connection.display_name} does not support indexes on expressions. "
|
@@ -1221,21 +1205,27 @@ class Model(metaclass=ModelBase):
|
|
1221
1205
|
)
|
1222
1206
|
)
|
1223
1207
|
fields = [
|
1224
|
-
field
|
1208
|
+
field
|
1209
|
+
for index in cls.model_options.indexes
|
1210
|
+
for field, _ in index.fields_orders
|
1211
|
+
]
|
1212
|
+
fields += [
|
1213
|
+
include for index in cls.model_options.indexes for include in index.include
|
1225
1214
|
]
|
1226
|
-
fields += [include for index in cls._meta.indexes for include in index.include]
|
1227
1215
|
fields += references
|
1228
1216
|
errors.extend(cls._check_local_fields(fields, "indexes"))
|
1229
1217
|
return errors
|
1230
1218
|
|
1231
1219
|
@classmethod
|
1232
|
-
def _check_local_fields(
|
1220
|
+
def _check_local_fields(
|
1221
|
+
cls, fields: Iterable[str], option: str
|
1222
|
+
) -> list[PreflightResult]:
|
1233
1223
|
from plain import models
|
1234
1224
|
|
1235
1225
|
# In order to avoid hitting the relation tree prematurely, we use our
|
1236
1226
|
# own fields_map instead of using get_field()
|
1237
1227
|
forward_fields_map = {}
|
1238
|
-
for field in cls.
|
1228
|
+
for field in cls._model_meta._get_fields(reverse=False):
|
1239
1229
|
forward_fields_map[field.name] = field
|
1240
1230
|
if hasattr(field, "attname"):
|
1241
1231
|
forward_fields_map[field.attname] = field
|
@@ -1262,11 +1252,11 @@ class Model(metaclass=ModelBase):
|
|
1262
1252
|
id="models.m2m_field_in_meta_option",
|
1263
1253
|
)
|
1264
1254
|
)
|
1265
|
-
elif field not in cls.
|
1255
|
+
elif field not in cls._model_meta.local_fields:
|
1266
1256
|
errors.append(
|
1267
1257
|
PreflightResult(
|
1268
1258
|
fix=f"'{option}' refers to field '{field_name}' which is not local to model "
|
1269
|
-
f"'{cls.
|
1259
|
+
f"'{cls.model_options.object_name}'. This issue may be caused by multi-table inheritance.",
|
1270
1260
|
obj=cls,
|
1271
1261
|
id="models.non_local_field_reference",
|
1272
1262
|
)
|
@@ -1274,16 +1264,16 @@ class Model(metaclass=ModelBase):
|
|
1274
1264
|
return errors
|
1275
1265
|
|
1276
1266
|
@classmethod
|
1277
|
-
def _check_ordering(cls):
|
1267
|
+
def _check_ordering(cls) -> list[PreflightResult]:
|
1278
1268
|
"""
|
1279
1269
|
Check "ordering" option -- is it a list of strings and do all fields
|
1280
1270
|
exist?
|
1281
1271
|
"""
|
1282
1272
|
|
1283
|
-
if not cls.
|
1273
|
+
if not cls.model_options.ordering:
|
1284
1274
|
return []
|
1285
1275
|
|
1286
|
-
if not isinstance(cls.
|
1276
|
+
if not isinstance(cls.model_options.ordering, list | tuple):
|
1287
1277
|
return [
|
1288
1278
|
PreflightResult(
|
1289
1279
|
fix="'ordering' must be a tuple or list (even if you want to order by "
|
@@ -1294,7 +1284,7 @@ class Model(metaclass=ModelBase):
|
|
1294
1284
|
]
|
1295
1285
|
|
1296
1286
|
errors = []
|
1297
|
-
fields = cls.
|
1287
|
+
fields = cls.model_options.ordering
|
1298
1288
|
|
1299
1289
|
# Skip expressions and '?' fields.
|
1300
1290
|
fields = (f for f in fields if isinstance(f, str) and f != "?")
|
@@ -1318,9 +1308,9 @@ class Model(metaclass=ModelBase):
|
|
1318
1308
|
fld = None
|
1319
1309
|
for part in field.split(LOOKUP_SEP):
|
1320
1310
|
try:
|
1321
|
-
fld = _cls.
|
1311
|
+
fld = _cls._model_meta.get_field(part)
|
1322
1312
|
if fld.is_relation:
|
1323
|
-
_cls = fld.path_infos[-1].
|
1313
|
+
_cls = fld.path_infos[-1].to_meta.model
|
1324
1314
|
else:
|
1325
1315
|
_cls = None
|
1326
1316
|
except (FieldDoesNotExist, AttributeError):
|
@@ -1341,13 +1331,13 @@ class Model(metaclass=ModelBase):
|
|
1341
1331
|
|
1342
1332
|
# Any field name that is not present in field_names does not exist.
|
1343
1333
|
# Also, ordering by m2m fields is not allowed.
|
1344
|
-
|
1334
|
+
meta = cls._model_meta
|
1345
1335
|
valid_fields = set(
|
1346
1336
|
chain.from_iterable(
|
1347
1337
|
(f.name, f.attname)
|
1348
1338
|
if not (f.auto_created and not f.concrete)
|
1349
1339
|
else (f.field.related_query_name(),)
|
1350
|
-
for f in chain(
|
1340
|
+
for f in chain(meta.fields, meta.related_objects)
|
1351
1341
|
)
|
1352
1342
|
)
|
1353
1343
|
|
@@ -1365,7 +1355,7 @@ class Model(metaclass=ModelBase):
|
|
1365
1355
|
return errors
|
1366
1356
|
|
1367
1357
|
@classmethod
|
1368
|
-
def _check_long_column_names(cls):
|
1358
|
+
def _check_long_column_names(cls) -> list[PreflightResult]:
|
1369
1359
|
"""
|
1370
1360
|
Check that any auto-generated column names are shorter than the limits
|
1371
1361
|
for each database in which the model will be created.
|
@@ -1380,7 +1370,7 @@ class Model(metaclass=ModelBase):
|
|
1380
1370
|
if allowed_len is None:
|
1381
1371
|
return errors
|
1382
1372
|
|
1383
|
-
for f in cls.
|
1373
|
+
for f in cls._model_meta.local_fields:
|
1384
1374
|
_, column_name = f.get_attname_column()
|
1385
1375
|
|
1386
1376
|
# Check if auto-generated name for the field is too long
|
@@ -1400,14 +1390,14 @@ class Model(metaclass=ModelBase):
|
|
1400
1390
|
)
|
1401
1391
|
)
|
1402
1392
|
|
1403
|
-
for f in cls.
|
1393
|
+
for f in cls._model_meta.local_many_to_many:
|
1404
1394
|
# Skip nonexistent models.
|
1405
1395
|
if isinstance(f.remote_field.through, str):
|
1406
1396
|
continue
|
1407
1397
|
|
1408
1398
|
# Check if auto-generated name for the M2M field is too long
|
1409
1399
|
# for the database.
|
1410
|
-
for m2m in f.remote_field.through.
|
1400
|
+
for m2m in f.remote_field.through._model_meta.local_fields:
|
1411
1401
|
_, rel_name = m2m.get_attname_column()
|
1412
1402
|
if (
|
1413
1403
|
m2m.db_column is None
|
@@ -1428,7 +1418,7 @@ class Model(metaclass=ModelBase):
|
|
1428
1418
|
return errors
|
1429
1419
|
|
1430
1420
|
@classmethod
|
1431
|
-
def _get_expr_references(cls, expr):
|
1421
|
+
def _get_expr_references(cls, expr: Any) -> Iterator[tuple[str, ...]]:
|
1432
1422
|
if isinstance(expr, Q):
|
1433
1423
|
for child in expr.children:
|
1434
1424
|
if isinstance(child, tuple):
|
@@ -1444,14 +1434,15 @@ class Model(metaclass=ModelBase):
|
|
1444
1434
|
yield from cls._get_expr_references(src_expr)
|
1445
1435
|
|
1446
1436
|
@classmethod
|
1447
|
-
def _check_constraints(cls):
|
1437
|
+
def _check_constraints(cls) -> list[PreflightResult]:
|
1448
1438
|
errors = []
|
1449
1439
|
if not (
|
1450
1440
|
db_connection.features.supports_table_check_constraints
|
1451
|
-
or "supports_table_check_constraints"
|
1441
|
+
or "supports_table_check_constraints"
|
1442
|
+
in cls.model_options.required_db_features
|
1452
1443
|
) and any(
|
1453
1444
|
isinstance(constraint, CheckConstraint)
|
1454
|
-
for constraint in cls.
|
1445
|
+
for constraint in cls.model_options.constraints
|
1455
1446
|
):
|
1456
1447
|
errors.append(
|
1457
1448
|
PreflightResult(
|
@@ -1466,11 +1457,11 @@ class Model(metaclass=ModelBase):
|
|
1466
1457
|
|
1467
1458
|
if not (
|
1468
1459
|
db_connection.features.supports_partial_indexes
|
1469
|
-
or "supports_partial_indexes" in cls.
|
1460
|
+
or "supports_partial_indexes" in cls.model_options.required_db_features
|
1470
1461
|
) and any(
|
1471
1462
|
isinstance(constraint, UniqueConstraint)
|
1472
1463
|
and constraint.condition is not None
|
1473
|
-
for constraint in cls.
|
1464
|
+
for constraint in cls.model_options.constraints
|
1474
1465
|
):
|
1475
1466
|
errors.append(
|
1476
1467
|
PreflightResult(
|
@@ -1486,11 +1477,11 @@ class Model(metaclass=ModelBase):
|
|
1486
1477
|
if not (
|
1487
1478
|
db_connection.features.supports_deferrable_unique_constraints
|
1488
1479
|
or "supports_deferrable_unique_constraints"
|
1489
|
-
in cls.
|
1480
|
+
in cls.model_options.required_db_features
|
1490
1481
|
) and any(
|
1491
1482
|
isinstance(constraint, UniqueConstraint)
|
1492
1483
|
and constraint.deferrable is not None
|
1493
|
-
for constraint in cls.
|
1484
|
+
for constraint in cls.model_options.constraints
|
1494
1485
|
):
|
1495
1486
|
errors.append(
|
1496
1487
|
PreflightResult(
|
@@ -1505,10 +1496,10 @@ class Model(metaclass=ModelBase):
|
|
1505
1496
|
|
1506
1497
|
if not (
|
1507
1498
|
db_connection.features.supports_covering_indexes
|
1508
|
-
or "supports_covering_indexes" in cls.
|
1499
|
+
or "supports_covering_indexes" in cls.model_options.required_db_features
|
1509
1500
|
) and any(
|
1510
1501
|
isinstance(constraint, UniqueConstraint) and constraint.include
|
1511
|
-
for constraint in cls.
|
1502
|
+
for constraint in cls.model_options.constraints
|
1512
1503
|
):
|
1513
1504
|
errors.append(
|
1514
1505
|
PreflightResult(
|
@@ -1523,10 +1514,10 @@ class Model(metaclass=ModelBase):
|
|
1523
1514
|
|
1524
1515
|
if not (
|
1525
1516
|
db_connection.features.supports_expression_indexes
|
1526
|
-
or "supports_expression_indexes" in cls.
|
1517
|
+
or "supports_expression_indexes" in cls.model_options.required_db_features
|
1527
1518
|
) and any(
|
1528
1519
|
isinstance(constraint, UniqueConstraint) and constraint.contains_expressions
|
1529
|
-
for constraint in cls.
|
1520
|
+
for constraint in cls.model_options.constraints
|
1530
1521
|
):
|
1531
1522
|
errors.append(
|
1532
1523
|
PreflightResult(
|
@@ -1541,22 +1532,23 @@ class Model(metaclass=ModelBase):
|
|
1541
1532
|
fields = set(
|
1542
1533
|
chain.from_iterable(
|
1543
1534
|
(*constraint.fields, *constraint.include)
|
1544
|
-
for constraint in cls.
|
1535
|
+
for constraint in cls.model_options.constraints
|
1545
1536
|
if isinstance(constraint, UniqueConstraint)
|
1546
1537
|
)
|
1547
1538
|
)
|
1548
1539
|
references = set()
|
1549
|
-
for constraint in cls.
|
1540
|
+
for constraint in cls.model_options.constraints:
|
1550
1541
|
if isinstance(constraint, UniqueConstraint):
|
1551
1542
|
if (
|
1552
1543
|
db_connection.features.supports_partial_indexes
|
1553
|
-
or "supports_partial_indexes"
|
1544
|
+
or "supports_partial_indexes"
|
1545
|
+
not in cls.model_options.required_db_features
|
1554
1546
|
) and isinstance(constraint.condition, Q):
|
1555
1547
|
references.update(cls._get_expr_references(constraint.condition))
|
1556
1548
|
if (
|
1557
1549
|
db_connection.features.supports_expression_indexes
|
1558
1550
|
or "supports_expression_indexes"
|
1559
|
-
not in cls.
|
1551
|
+
not in cls.model_options.required_db_features
|
1560
1552
|
) and constraint.contains_expressions:
|
1561
1553
|
for expression in constraint.expressions:
|
1562
1554
|
references.update(cls._get_expr_references(expression))
|
@@ -1564,7 +1556,7 @@ class Model(metaclass=ModelBase):
|
|
1564
1556
|
if (
|
1565
1557
|
db_connection.features.supports_table_check_constraints
|
1566
1558
|
or "supports_table_check_constraints"
|
1567
|
-
not in cls.
|
1559
|
+
not in cls.model_options.required_db_features
|
1568
1560
|
):
|
1569
1561
|
if isinstance(constraint.check, Q):
|
1570
1562
|
references.update(cls._get_expr_references(constraint.check))
|
@@ -1588,7 +1580,7 @@ class Model(metaclass=ModelBase):
|
|
1588
1580
|
# If it has no lookups it cannot result in a JOIN.
|
1589
1581
|
continue
|
1590
1582
|
try:
|
1591
|
-
field = cls.
|
1583
|
+
field = cls._model_meta.get_field(field_name)
|
1592
1584
|
if not field.is_relation or field.many_to_many or field.one_to_many:
|
1593
1585
|
continue
|
1594
1586
|
except FieldDoesNotExist:
|
@@ -1617,7 +1609,7 @@ class Model(metaclass=ModelBase):
|
|
1617
1609
|
########
|
1618
1610
|
|
1619
1611
|
|
1620
|
-
def model_unpickle(model_id):
|
1612
|
+
def model_unpickle(model_id: tuple[str, str] | type[Model]) -> Model:
|
1621
1613
|
"""Used to unpickle Model subclasses with deferred fields."""
|
1622
1614
|
if isinstance(model_id, tuple):
|
1623
1615
|
model = models_registry.get_model(*model_id)
|
@@ -1627,4 +1619,4 @@ def model_unpickle(model_id):
|
|
1627
1619
|
return model.__new__(model)
|
1628
1620
|
|
1629
1621
|
|
1630
|
-
model_unpickle.__safe_for_unpickle__ = True
|
1622
|
+
model_unpickle.__safe_for_unpickle__ = True # type: ignore[attr-defined]
|