plain.models 0.50.0__py3-none-any.whl → 0.51.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 +24 -0
- plain/models/README.md +26 -42
- plain/models/__init__.py +2 -0
- plain/models/backends/base/creation.py +2 -2
- plain/models/backends/base/introspection.py +8 -4
- plain/models/backends/base/schema.py +89 -71
- plain/models/backends/base/validation.py +1 -1
- plain/models/backends/mysql/compiler.py +1 -1
- plain/models/backends/mysql/operations.py +1 -1
- plain/models/backends/mysql/schema.py +4 -4
- plain/models/backends/postgresql/operations.py +1 -1
- plain/models/backends/postgresql/schema.py +3 -3
- plain/models/backends/sqlite3/operations.py +1 -1
- plain/models/backends/sqlite3/schema.py +61 -50
- plain/models/base.py +116 -163
- plain/models/cli.py +4 -4
- plain/models/constraints.py +14 -9
- plain/models/deletion.py +15 -14
- plain/models/expressions.py +21 -5
- plain/models/fields/__init__.py +20 -16
- plain/models/fields/json.py +3 -3
- plain/models/fields/related.py +73 -71
- plain/models/fields/related_descriptors.py +2 -2
- plain/models/fields/related_lookups.py +1 -1
- plain/models/fields/related_managers.py +21 -32
- plain/models/fields/reverse_related.py +8 -8
- plain/models/forms.py +12 -12
- plain/models/indexes.py +5 -4
- plain/models/meta.py +505 -0
- plain/models/migrations/operations/base.py +1 -1
- plain/models/migrations/operations/fields.py +6 -6
- plain/models/migrations/operations/models.py +18 -16
- plain/models/migrations/recorder.py +9 -5
- plain/models/migrations/state.py +35 -46
- plain/models/migrations/utils.py +1 -1
- plain/models/options.py +182 -518
- plain/models/preflight.py +7 -5
- plain/models/query.py +119 -65
- plain/models/query_utils.py +18 -13
- plain/models/registry.py +6 -5
- plain/models/sql/compiler.py +51 -37
- plain/models/sql/query.py +77 -68
- plain/models/sql/subqueries.py +4 -4
- plain/models/utils.py +4 -1
- {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/METADATA +27 -43
- {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/RECORD +49 -48
- {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/WHEEL +0 -0
- {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/entry_points.txt +0 -0
- {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/licenses/LICENSE +0 -0
plain/models/base.py
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import copy
|
4
|
-
import inspect
|
5
4
|
import warnings
|
6
5
|
from collections.abc import Iterable, Iterator, Sequence
|
7
6
|
from itertools import chain
|
8
|
-
from typing import Any
|
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
|
9
12
|
|
10
13
|
import plain.runtime
|
11
14
|
from plain.exceptions import NON_FIELD_ERRORS, ValidationError
|
@@ -26,9 +29,9 @@ from plain.models.exceptions import (
|
|
26
29
|
from plain.models.expressions import RawSQL, Value
|
27
30
|
from plain.models.fields import NOT_PROVIDED, PrimaryKeyField
|
28
31
|
from plain.models.fields.reverse_related import ForeignObjectRel
|
32
|
+
from plain.models.meta import Meta
|
29
33
|
from plain.models.options import Options
|
30
34
|
from plain.models.query import F, Q, QuerySet
|
31
|
-
from plain.packages import packages_registry
|
32
35
|
from plain.preflight import PreflightResult
|
33
36
|
from plain.utils.encoding import force_str
|
34
37
|
from plain.utils.hashable import make_hashable
|
@@ -61,71 +64,8 @@ class ModelBase(type):
|
|
61
64
|
raise TypeError(
|
62
65
|
f"A model can't extend another model: {name} extends {base}"
|
63
66
|
)
|
64
|
-
# Meta has to be defined on the model itself.
|
65
|
-
if hasattr(base, "Meta"):
|
66
|
-
raise TypeError(
|
67
|
-
"Meta can only be defined on a model itself, not a parent class: "
|
68
|
-
f"{name} extends {base}"
|
69
|
-
)
|
70
|
-
|
71
|
-
new_class = super().__new__(cls, name, bases, attrs, **kwargs)
|
72
|
-
|
73
|
-
new_class._setup_meta()
|
74
|
-
|
75
|
-
# Now go back over all the attrs on this class see if they have a contribute_to_class() method.
|
76
|
-
# Attributes with contribute_to_class are fields and meta options.
|
77
|
-
for attr_name, attr_value in inspect.getmembers(new_class):
|
78
|
-
if attr_name.startswith("_"):
|
79
|
-
continue
|
80
|
-
|
81
|
-
if not inspect.isclass(attr_value) and hasattr(
|
82
|
-
attr_value, "contribute_to_class"
|
83
|
-
):
|
84
|
-
if attr_name not in attrs:
|
85
|
-
# If the field came from an inherited class/mixin,
|
86
|
-
# we need to make a copy of it to avoid altering the
|
87
|
-
# original class and other classes that inherit from it.
|
88
|
-
field = copy.deepcopy(attr_value)
|
89
|
-
else:
|
90
|
-
field = attr_value
|
91
|
-
field.contribute_to_class(new_class, attr_name)
|
92
|
-
|
93
|
-
# Set the name of _meta.indexes. This can't be done in
|
94
|
-
# Options.contribute_to_class() because fields haven't been added to
|
95
|
-
# the model at that point.
|
96
|
-
for index in new_class._meta.indexes:
|
97
|
-
if not index.name:
|
98
|
-
index.set_name_with_model(new_class)
|
99
|
-
|
100
|
-
return new_class
|
101
|
-
|
102
|
-
def _setup_meta(cls) -> None:
|
103
|
-
name = cls.__name__
|
104
|
-
module = cls.__module__
|
105
|
-
|
106
|
-
# The model's Meta class, if it has one.
|
107
|
-
meta = getattr(cls, "Meta", None)
|
108
|
-
|
109
|
-
# Look for an application configuration to attach the model to.
|
110
|
-
package_config = packages_registry.get_containing_package_config(module)
|
111
|
-
|
112
|
-
package_label = getattr(meta, "package_label", None)
|
113
|
-
if package_label is None:
|
114
|
-
if package_config is None:
|
115
|
-
raise RuntimeError(
|
116
|
-
f"Model class {module}.{name} doesn't declare an explicit "
|
117
|
-
"package_label and isn't in an application in "
|
118
|
-
"INSTALLED_PACKAGES."
|
119
|
-
)
|
120
|
-
else:
|
121
|
-
package_label = package_config.package_label
|
122
67
|
|
123
|
-
|
124
|
-
|
125
|
-
@property
|
126
|
-
def query(cls) -> QuerySet:
|
127
|
-
"""Create a new QuerySet for this model."""
|
128
|
-
return cls._meta.queryset
|
68
|
+
return super().__new__(cls, name, bases, attrs, **kwargs)
|
129
69
|
|
130
70
|
|
131
71
|
class ModelStateFieldsCacheDescriptor:
|
@@ -150,19 +90,20 @@ class ModelState:
|
|
150
90
|
|
151
91
|
|
152
92
|
class Model(metaclass=ModelBase):
|
153
|
-
|
93
|
+
# Every model gets an automatic id field
|
94
|
+
id = PrimaryKeyField()
|
154
95
|
|
155
|
-
#
|
96
|
+
# Descriptors for other model behavior
|
97
|
+
query = QuerySet()
|
98
|
+
model_options = Options()
|
99
|
+
_model_meta = Meta()
|
156
100
|
DoesNotExist = DoesNotExistDescriptor()
|
157
101
|
MultipleObjectsReturned = MultipleObjectsReturnedDescriptor()
|
158
102
|
|
159
|
-
# Every model gets an automatic id field
|
160
|
-
id = PrimaryKeyField()
|
161
|
-
|
162
103
|
def __init__(self, *args: Any, **kwargs: Any):
|
163
104
|
# Alias some things as locals to avoid repeat global lookups
|
164
105
|
cls = self.__class__
|
165
|
-
|
106
|
+
meta = cls._model_meta
|
166
107
|
_setattr = setattr
|
167
108
|
_DEFERRED = DEFERRED
|
168
109
|
|
@@ -173,12 +114,12 @@ class Model(metaclass=ModelBase):
|
|
173
114
|
# overrides it. It should be one or the other; don't duplicate the work
|
174
115
|
# The reason for the kwargs check is that standard iterator passes in by
|
175
116
|
# args, and instantiation for iteration is 33% faster.
|
176
|
-
if len(args) > len(
|
117
|
+
if len(args) > len(meta.concrete_fields):
|
177
118
|
# Daft, but matches old exception sans the err msg.
|
178
119
|
raise IndexError("Number of args exceeds number of fields")
|
179
120
|
|
180
121
|
if not kwargs:
|
181
|
-
fields_iter = iter(
|
122
|
+
fields_iter = iter(meta.concrete_fields)
|
182
123
|
# The ordering of the zip calls matter - zip throws StopIteration
|
183
124
|
# when an iter throws it. So if the first iter throws it, the second
|
184
125
|
# is *not* consumed. We rely on this, so don't change the order
|
@@ -189,7 +130,7 @@ class Model(metaclass=ModelBase):
|
|
189
130
|
_setattr(self, field.attname, val)
|
190
131
|
else:
|
191
132
|
# Slower, kwargs-ready version.
|
192
|
-
fields_iter = iter(
|
133
|
+
fields_iter = iter(meta.fields)
|
193
134
|
for val, field in zip(args, fields_iter):
|
194
135
|
if val is _DEFERRED:
|
195
136
|
continue
|
@@ -244,7 +185,7 @@ class Model(metaclass=ModelBase):
|
|
244
185
|
_setattr(self, field.attname, val)
|
245
186
|
|
246
187
|
if kwargs:
|
247
|
-
property_names =
|
188
|
+
property_names = meta._property_names
|
248
189
|
unexpected = ()
|
249
190
|
for prop, value in kwargs.items():
|
250
191
|
# Any remaining kwargs must correspond to properties or virtual
|
@@ -254,7 +195,7 @@ class Model(metaclass=ModelBase):
|
|
254
195
|
_setattr(self, prop, value)
|
255
196
|
else:
|
256
197
|
try:
|
257
|
-
|
198
|
+
meta.get_field(prop)
|
258
199
|
except FieldDoesNotExist:
|
259
200
|
unexpected += (prop,)
|
260
201
|
else:
|
@@ -270,11 +211,11 @@ class Model(metaclass=ModelBase):
|
|
270
211
|
|
271
212
|
@classmethod
|
272
213
|
def from_db(cls, field_names: Iterable[str], values: Sequence[Any]) -> Model:
|
273
|
-
if len(values) != len(cls.
|
214
|
+
if len(values) != len(cls._model_meta.concrete_fields):
|
274
215
|
values_iter = iter(values)
|
275
216
|
values = [
|
276
217
|
next(values_iter) if f.attname in field_names else DEFERRED
|
277
|
-
for f in cls.
|
218
|
+
for f in cls._model_meta.concrete_fields
|
278
219
|
]
|
279
220
|
new = cls(*values)
|
280
221
|
new._state.adding = False
|
@@ -304,7 +245,10 @@ class Model(metaclass=ModelBase):
|
|
304
245
|
def __reduce__(self) -> tuple[Any, tuple[Any, ...], dict[str, Any]]:
|
305
246
|
data = self.__getstate__()
|
306
247
|
data[PLAIN_VERSION_PICKLE_KEY] = plain.runtime.__version__
|
307
|
-
class_id =
|
248
|
+
class_id = (
|
249
|
+
self.model_options.package_label,
|
250
|
+
self.model_options.object_name,
|
251
|
+
)
|
308
252
|
return model_unpickle, (class_id,), data
|
309
253
|
|
310
254
|
def __getstate__(self) -> dict[str, Any]:
|
@@ -351,7 +295,7 @@ class Model(metaclass=ModelBase):
|
|
351
295
|
"""
|
352
296
|
return {
|
353
297
|
f.attname
|
354
|
-
for f in self.
|
298
|
+
for f in self._model_meta.concrete_fields
|
355
299
|
if f.attname not in self.__dict__
|
356
300
|
}
|
357
301
|
|
@@ -386,7 +330,7 @@ class Model(metaclass=ModelBase):
|
|
386
330
|
"are not allowed in fields."
|
387
331
|
)
|
388
332
|
|
389
|
-
db_instance_qs = self.
|
333
|
+
db_instance_qs = self._model_meta.base_queryset.filter(id=self.id)
|
390
334
|
|
391
335
|
# Use provided fields, if not set then reload all non-deferred fields.
|
392
336
|
deferred_fields = self.get_deferred_fields()
|
@@ -396,14 +340,14 @@ class Model(metaclass=ModelBase):
|
|
396
340
|
elif deferred_fields:
|
397
341
|
fields = [
|
398
342
|
f.attname
|
399
|
-
for f in self.
|
343
|
+
for f in self._model_meta.concrete_fields
|
400
344
|
if f.attname not in deferred_fields
|
401
345
|
]
|
402
346
|
db_instance_qs = db_instance_qs.only(*fields)
|
403
347
|
|
404
348
|
db_instance = db_instance_qs.get()
|
405
349
|
non_loaded_fields = db_instance.get_deferred_fields()
|
406
|
-
for field in self.
|
350
|
+
for field in self._model_meta.concrete_fields:
|
407
351
|
if field.attname in non_loaded_fields:
|
408
352
|
# This field wasn't refreshed - skip ahead.
|
409
353
|
continue
|
@@ -413,7 +357,7 @@ class Model(metaclass=ModelBase):
|
|
413
357
|
field.delete_cached_value(self)
|
414
358
|
|
415
359
|
# Clear cached relations.
|
416
|
-
for field in self.
|
360
|
+
for field in self._model_meta.related_objects:
|
417
361
|
if field.is_cached(self):
|
418
362
|
field.delete_cached_value(self)
|
419
363
|
|
@@ -429,7 +373,7 @@ class Model(metaclass=ModelBase):
|
|
429
373
|
and not use this method.
|
430
374
|
"""
|
431
375
|
try:
|
432
|
-
field = self.
|
376
|
+
field = self._model_meta.get_field(field_name)
|
433
377
|
except FieldDoesNotExist:
|
434
378
|
return getattr(self, field_name)
|
435
379
|
return getattr(self, field.attname)
|
@@ -464,7 +408,7 @@ class Model(metaclass=ModelBase):
|
|
464
408
|
return
|
465
409
|
|
466
410
|
update_fields = frozenset(update_fields)
|
467
|
-
field_names = self.
|
411
|
+
field_names = self._model_meta._non_pk_concrete_field_names
|
468
412
|
non_model_fields = update_fields.difference(field_names)
|
469
413
|
|
470
414
|
if non_model_fields:
|
@@ -479,7 +423,7 @@ class Model(metaclass=ModelBase):
|
|
479
423
|
# on the loaded fields.
|
480
424
|
elif not force_insert and deferred_fields:
|
481
425
|
field_names = set()
|
482
|
-
for field in self.
|
426
|
+
for field in self._model_meta.concrete_fields:
|
483
427
|
if not field.primary_key and not hasattr(field, "through"):
|
484
428
|
field_names.add(field.attname)
|
485
429
|
loaded_fields = field_names.difference(deferred_fields)
|
@@ -518,19 +462,20 @@ class Model(metaclass=ModelBase):
|
|
518
462
|
|
519
463
|
with transaction.mark_for_rollback_on_error():
|
520
464
|
self._save_table(
|
521
|
-
raw,
|
522
|
-
cls,
|
523
|
-
force_insert,
|
524
|
-
force_update,
|
525
|
-
update_fields,
|
465
|
+
raw=raw,
|
466
|
+
cls=cls,
|
467
|
+
force_insert=force_insert,
|
468
|
+
force_update=force_update,
|
469
|
+
update_fields=update_fields,
|
526
470
|
)
|
527
471
|
# Once saved, this is no longer a to-be-added instance.
|
528
472
|
self._state.adding = False
|
529
473
|
|
530
474
|
def _save_table(
|
531
475
|
self,
|
532
|
-
|
533
|
-
|
476
|
+
*,
|
477
|
+
raw: bool,
|
478
|
+
cls: type[Model],
|
534
479
|
force_insert: bool = False,
|
535
480
|
force_update: bool = False,
|
536
481
|
update_fields: Iterable[str] | None = None,
|
@@ -539,7 +484,7 @@ class Model(metaclass=ModelBase):
|
|
539
484
|
Do the heavy-lifting involved in saving. Update or insert the data
|
540
485
|
for a single table.
|
541
486
|
"""
|
542
|
-
meta = cls.
|
487
|
+
meta = cls._model_meta
|
543
488
|
non_pks = [f for f in meta.local_concrete_fields if not f.primary_key]
|
544
489
|
|
545
490
|
if update_fields:
|
@@ -644,7 +589,7 @@ class Model(metaclass=ModelBase):
|
|
644
589
|
) -> None:
|
645
590
|
# Ensure that a model instance without a PK hasn't been assigned to
|
646
591
|
# a ForeignKey on this model. If the field is nullable, allowing the save would result in silent data loss.
|
647
|
-
for field in self.
|
592
|
+
for field in self._model_meta.concrete_fields:
|
648
593
|
if fields and field not in fields:
|
649
594
|
continue
|
650
595
|
# If the related field isn't cached, then an instance hasn't been
|
@@ -681,7 +626,7 @@ class Model(metaclass=ModelBase):
|
|
681
626
|
def delete(self) -> tuple[int, dict[str, int]]:
|
682
627
|
if self.id is None:
|
683
628
|
raise ValueError(
|
684
|
-
f"{self.
|
629
|
+
f"{self.model_options.object_name} object can't be deleted because its id attribute is set "
|
685
630
|
"to None."
|
686
631
|
)
|
687
632
|
collector = Collector(origin=self)
|
@@ -691,7 +636,7 @@ class Model(metaclass=ModelBase):
|
|
691
636
|
def get_field_display(self, field_name: str) -> str:
|
692
637
|
"""Get the display value for a field, especially useful for fields with choices."""
|
693
638
|
# Get the field object from the field name
|
694
|
-
field = self.
|
639
|
+
field = self._model_meta.get_field(field_name)
|
695
640
|
value = getattr(self, field.attname)
|
696
641
|
|
697
642
|
# If field has no choices, just return the value as string
|
@@ -705,11 +650,11 @@ class Model(metaclass=ModelBase):
|
|
705
650
|
)
|
706
651
|
|
707
652
|
def _get_field_value_map(
|
708
|
-
self, meta:
|
653
|
+
self, meta: Meta | None, exclude: set[str] | None = None
|
709
654
|
) -> dict[str, Value]:
|
710
655
|
if exclude is None:
|
711
656
|
exclude = set()
|
712
|
-
meta = meta or self.
|
657
|
+
meta = meta or self._model_meta
|
713
658
|
return {
|
714
659
|
field.name: Value(getattr(self, field.attname), field)
|
715
660
|
for field in meta.local_concrete_fields
|
@@ -759,7 +704,7 @@ class Model(metaclass=ModelBase):
|
|
759
704
|
# Gather a list of checks for fields declared as unique and add them to
|
760
705
|
# the list of checks.
|
761
706
|
|
762
|
-
fields_with_class = [(self.__class__, self.
|
707
|
+
fields_with_class = [(self.__class__, self._model_meta.local_fields)]
|
763
708
|
|
764
709
|
for model_class, fields in fields_with_class:
|
765
710
|
for f in fields:
|
@@ -782,7 +727,7 @@ class Model(metaclass=ModelBase):
|
|
782
727
|
|
783
728
|
lookup_kwargs = {}
|
784
729
|
for field_name in unique_check:
|
785
|
-
f = self.
|
730
|
+
f = self._model_meta.get_field(field_name)
|
786
731
|
lookup_value = getattr(self, f.attname)
|
787
732
|
# TODO: Handle multiple backends with different feature flags.
|
788
733
|
if lookup_value is None:
|
@@ -818,19 +763,19 @@ class Model(metaclass=ModelBase):
|
|
818
763
|
return errors
|
819
764
|
|
820
765
|
def unique_error_message(
|
821
|
-
self, model_class: type, unique_check: tuple[str, ...]
|
766
|
+
self, model_class: type[Model], unique_check: tuple[str, ...]
|
822
767
|
) -> ValidationError:
|
823
|
-
|
768
|
+
meta = model_class._model_meta
|
824
769
|
|
825
770
|
params = {
|
826
771
|
"model": self,
|
827
772
|
"model_class": model_class,
|
828
|
-
"model_name":
|
773
|
+
"model_name": model_class.model_options.model_name,
|
829
774
|
"unique_check": unique_check,
|
830
775
|
}
|
831
776
|
|
832
777
|
if len(unique_check) == 1:
|
833
|
-
field =
|
778
|
+
field = meta.get_field(unique_check[0])
|
834
779
|
params["field_label"] = field.name
|
835
780
|
return ValidationError(
|
836
781
|
message=field.error_messages["unique"],
|
@@ -838,7 +783,7 @@ class Model(metaclass=ModelBase):
|
|
838
783
|
params=params,
|
839
784
|
)
|
840
785
|
else:
|
841
|
-
field_names = [
|
786
|
+
field_names = [meta.get_field(f).name for f in unique_check]
|
842
787
|
|
843
788
|
# Put an "and" before the last one
|
844
789
|
field_names[-1] = f"and {field_names[-1]}"
|
@@ -851,7 +796,7 @@ class Model(metaclass=ModelBase):
|
|
851
796
|
params["field_label"] = " ".join(field_names)
|
852
797
|
|
853
798
|
# Use the first field as the message format...
|
854
|
-
message =
|
799
|
+
message = meta.get_field(unique_check[0]).error_messages["unique"]
|
855
800
|
|
856
801
|
return ValidationError(
|
857
802
|
message=message,
|
@@ -860,7 +805,7 @@ class Model(metaclass=ModelBase):
|
|
860
805
|
)
|
861
806
|
|
862
807
|
def get_constraints(self) -> list[tuple[type, list[Any]]]:
|
863
|
-
constraints = [(self.__class__, self.
|
808
|
+
constraints = [(self.__class__, self.model_options.constraints)]
|
864
809
|
return constraints
|
865
810
|
|
866
811
|
def validate_constraints(self, exclude: set[str] | None = None) -> None:
|
@@ -944,7 +889,7 @@ class Model(metaclass=ModelBase):
|
|
944
889
|
exclude = set()
|
945
890
|
|
946
891
|
errors = {}
|
947
|
-
for f in self.
|
892
|
+
for f in self._model_meta.fields:
|
948
893
|
if f.name in exclude:
|
949
894
|
continue
|
950
895
|
# Skip validation for empty fields with required=False. The developer
|
@@ -992,12 +937,12 @@ class Model(metaclass=ModelBase):
|
|
992
937
|
|
993
938
|
@classmethod
|
994
939
|
def _check_db_table_comment(cls) -> list[PreflightResult]:
|
995
|
-
if not cls.
|
940
|
+
if not cls.model_options.db_table_comment:
|
996
941
|
return []
|
997
942
|
errors = []
|
998
943
|
if not (
|
999
944
|
db_connection.features.supports_comments
|
1000
|
-
or "supports_comments" in cls.
|
945
|
+
or "supports_comments" in cls.model_options.required_db_features
|
1001
946
|
):
|
1002
947
|
errors.append(
|
1003
948
|
PreflightResult(
|
@@ -1014,9 +959,9 @@ class Model(metaclass=ModelBase):
|
|
1014
959
|
def _check_fields(cls) -> list[PreflightResult]:
|
1015
960
|
"""Perform all field checks."""
|
1016
961
|
errors = []
|
1017
|
-
for field in cls.
|
962
|
+
for field in cls._model_meta.local_fields:
|
1018
963
|
errors.extend(field.preflight(from_model=cls))
|
1019
|
-
for field in cls.
|
964
|
+
for field in cls._model_meta.local_many_to_many:
|
1020
965
|
errors.extend(field.preflight(from_model=cls))
|
1021
966
|
return errors
|
1022
967
|
|
@@ -1027,7 +972,7 @@ class Model(metaclass=ModelBase):
|
|
1027
972
|
errors = []
|
1028
973
|
seen_intermediary_signatures = []
|
1029
974
|
|
1030
|
-
fields = cls.
|
975
|
+
fields = cls._model_meta.local_many_to_many
|
1031
976
|
|
1032
977
|
# Skip when the target model wasn't found.
|
1033
978
|
fields = (f for f in fields if isinstance(f.remote_field.model, ModelBase))
|
@@ -1046,7 +991,7 @@ class Model(metaclass=ModelBase):
|
|
1046
991
|
errors.append(
|
1047
992
|
PreflightResult(
|
1048
993
|
fix="The model has two identical many-to-many relations "
|
1049
|
-
f"through the intermediate model '{f.remote_field.through.
|
994
|
+
f"through the intermediate model '{f.remote_field.through.model_options.label}'.",
|
1050
995
|
obj=cls,
|
1051
996
|
id="models.duplicate_many_to_many_relations",
|
1052
997
|
)
|
@@ -1059,7 +1004,9 @@ class Model(metaclass=ModelBase):
|
|
1059
1004
|
def _check_id_field(cls) -> list[PreflightResult]:
|
1060
1005
|
"""Disallow user-defined fields named ``id``."""
|
1061
1006
|
if any(
|
1062
|
-
f
|
1007
|
+
f
|
1008
|
+
for f in cls._model_meta.local_fields
|
1009
|
+
if f.name == "id" and not f.auto_created
|
1063
1010
|
):
|
1064
1011
|
return [
|
1065
1012
|
PreflightResult(
|
@@ -1076,7 +1023,7 @@ class Model(metaclass=ModelBase):
|
|
1076
1023
|
errors = []
|
1077
1024
|
used_fields = {} # name or attname -> field
|
1078
1025
|
|
1079
|
-
for f in cls.
|
1026
|
+
for f in cls._model_meta.local_fields:
|
1080
1027
|
clash = used_fields.get(f.name) or used_fields.get(f.attname) or None
|
1081
1028
|
# Note that we may detect clash between user-defined non-unique
|
1082
1029
|
# field "id" and automatically added unique field "id", both
|
@@ -1089,7 +1036,7 @@ class Model(metaclass=ModelBase):
|
|
1089
1036
|
errors.append(
|
1090
1037
|
PreflightResult(
|
1091
1038
|
fix=f"The field '{f.name}' clashes with the field '{clash.name}' "
|
1092
|
-
f"from model '{clash.model.
|
1039
|
+
f"from model '{clash.model.model_options}'.",
|
1093
1040
|
obj=f,
|
1094
1041
|
id="models.field_name_clash",
|
1095
1042
|
)
|
@@ -1105,7 +1052,7 @@ class Model(metaclass=ModelBase):
|
|
1105
1052
|
used_column_names = []
|
1106
1053
|
errors = []
|
1107
1054
|
|
1108
|
-
for f in cls.
|
1055
|
+
for f in cls._model_meta.local_fields:
|
1109
1056
|
_, column_name = f.get_attname_column()
|
1110
1057
|
|
1111
1058
|
# Ensure the column name is not already in use.
|
@@ -1152,10 +1099,10 @@ class Model(metaclass=ModelBase):
|
|
1152
1099
|
cls,
|
1153
1100
|
) -> list[PreflightResult]:
|
1154
1101
|
errors = []
|
1155
|
-
property_names = cls.
|
1102
|
+
property_names = cls._model_meta._property_names
|
1156
1103
|
related_field_accessors = (
|
1157
1104
|
f.get_attname()
|
1158
|
-
for f in cls.
|
1105
|
+
for f in cls._model_meta._get_fields(reverse=False)
|
1159
1106
|
if f.is_relation and f.related_model is not None
|
1160
1107
|
)
|
1161
1108
|
for accessor in related_field_accessors:
|
@@ -1173,7 +1120,7 @@ class Model(metaclass=ModelBase):
|
|
1173
1120
|
@classmethod
|
1174
1121
|
def _check_single_primary_key(cls) -> list[PreflightResult]:
|
1175
1122
|
errors = []
|
1176
|
-
if sum(1 for f in cls.
|
1123
|
+
if sum(1 for f in cls._model_meta.local_fields if f.primary_key) > 1:
|
1177
1124
|
errors.append(
|
1178
1125
|
PreflightResult(
|
1179
1126
|
fix="The model cannot have more than one field with "
|
@@ -1189,7 +1136,7 @@ class Model(metaclass=ModelBase):
|
|
1189
1136
|
"""Check fields, names, and conditions of indexes."""
|
1190
1137
|
errors = []
|
1191
1138
|
references = set()
|
1192
|
-
for index in cls.
|
1139
|
+
for index in cls.model_options.indexes:
|
1193
1140
|
# Index name can't start with an underscore or a number, restricted
|
1194
1141
|
# for cross-database compatibility with Oracle.
|
1195
1142
|
if index.name[0] == "_" or index.name[0].isdigit():
|
@@ -1217,8 +1164,8 @@ class Model(metaclass=ModelBase):
|
|
1217
1164
|
)
|
1218
1165
|
if not (
|
1219
1166
|
db_connection.features.supports_partial_indexes
|
1220
|
-
or "supports_partial_indexes" in cls.
|
1221
|
-
) 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):
|
1222
1169
|
errors.append(
|
1223
1170
|
PreflightResult(
|
1224
1171
|
fix=f"{db_connection.display_name} does not support indexes with conditions. "
|
@@ -1231,8 +1178,8 @@ class Model(metaclass=ModelBase):
|
|
1231
1178
|
)
|
1232
1179
|
if not (
|
1233
1180
|
db_connection.features.supports_covering_indexes
|
1234
|
-
or "supports_covering_indexes" in cls.
|
1235
|
-
) 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):
|
1236
1183
|
errors.append(
|
1237
1184
|
PreflightResult(
|
1238
1185
|
fix=f"{db_connection.display_name} does not support indexes with non-key columns. "
|
@@ -1245,8 +1192,8 @@ class Model(metaclass=ModelBase):
|
|
1245
1192
|
)
|
1246
1193
|
if not (
|
1247
1194
|
db_connection.features.supports_expression_indexes
|
1248
|
-
or "supports_expression_indexes" in cls.
|
1249
|
-
) 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):
|
1250
1197
|
errors.append(
|
1251
1198
|
PreflightResult(
|
1252
1199
|
fix=f"{db_connection.display_name} does not support indexes on expressions. "
|
@@ -1258,9 +1205,13 @@ class Model(metaclass=ModelBase):
|
|
1258
1205
|
)
|
1259
1206
|
)
|
1260
1207
|
fields = [
|
1261
|
-
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
|
1262
1214
|
]
|
1263
|
-
fields += [include for index in cls._meta.indexes for include in index.include]
|
1264
1215
|
fields += references
|
1265
1216
|
errors.extend(cls._check_local_fields(fields, "indexes"))
|
1266
1217
|
return errors
|
@@ -1274,7 +1225,7 @@ class Model(metaclass=ModelBase):
|
|
1274
1225
|
# In order to avoid hitting the relation tree prematurely, we use our
|
1275
1226
|
# own fields_map instead of using get_field()
|
1276
1227
|
forward_fields_map = {}
|
1277
|
-
for field in cls.
|
1228
|
+
for field in cls._model_meta._get_fields(reverse=False):
|
1278
1229
|
forward_fields_map[field.name] = field
|
1279
1230
|
if hasattr(field, "attname"):
|
1280
1231
|
forward_fields_map[field.attname] = field
|
@@ -1301,11 +1252,11 @@ class Model(metaclass=ModelBase):
|
|
1301
1252
|
id="models.m2m_field_in_meta_option",
|
1302
1253
|
)
|
1303
1254
|
)
|
1304
|
-
elif field not in cls.
|
1255
|
+
elif field not in cls._model_meta.local_fields:
|
1305
1256
|
errors.append(
|
1306
1257
|
PreflightResult(
|
1307
1258
|
fix=f"'{option}' refers to field '{field_name}' which is not local to model "
|
1308
|
-
f"'{cls.
|
1259
|
+
f"'{cls.model_options.object_name}'. This issue may be caused by multi-table inheritance.",
|
1309
1260
|
obj=cls,
|
1310
1261
|
id="models.non_local_field_reference",
|
1311
1262
|
)
|
@@ -1319,10 +1270,10 @@ class Model(metaclass=ModelBase):
|
|
1319
1270
|
exist?
|
1320
1271
|
"""
|
1321
1272
|
|
1322
|
-
if not cls.
|
1273
|
+
if not cls.model_options.ordering:
|
1323
1274
|
return []
|
1324
1275
|
|
1325
|
-
if not isinstance(cls.
|
1276
|
+
if not isinstance(cls.model_options.ordering, list | tuple):
|
1326
1277
|
return [
|
1327
1278
|
PreflightResult(
|
1328
1279
|
fix="'ordering' must be a tuple or list (even if you want to order by "
|
@@ -1333,7 +1284,7 @@ class Model(metaclass=ModelBase):
|
|
1333
1284
|
]
|
1334
1285
|
|
1335
1286
|
errors = []
|
1336
|
-
fields = cls.
|
1287
|
+
fields = cls.model_options.ordering
|
1337
1288
|
|
1338
1289
|
# Skip expressions and '?' fields.
|
1339
1290
|
fields = (f for f in fields if isinstance(f, str) and f != "?")
|
@@ -1357,9 +1308,9 @@ class Model(metaclass=ModelBase):
|
|
1357
1308
|
fld = None
|
1358
1309
|
for part in field.split(LOOKUP_SEP):
|
1359
1310
|
try:
|
1360
|
-
fld = _cls.
|
1311
|
+
fld = _cls._model_meta.get_field(part)
|
1361
1312
|
if fld.is_relation:
|
1362
|
-
_cls = fld.path_infos[-1].
|
1313
|
+
_cls = fld.path_infos[-1].to_meta.model
|
1363
1314
|
else:
|
1364
1315
|
_cls = None
|
1365
1316
|
except (FieldDoesNotExist, AttributeError):
|
@@ -1380,13 +1331,13 @@ class Model(metaclass=ModelBase):
|
|
1380
1331
|
|
1381
1332
|
# Any field name that is not present in field_names does not exist.
|
1382
1333
|
# Also, ordering by m2m fields is not allowed.
|
1383
|
-
|
1334
|
+
meta = cls._model_meta
|
1384
1335
|
valid_fields = set(
|
1385
1336
|
chain.from_iterable(
|
1386
1337
|
(f.name, f.attname)
|
1387
1338
|
if not (f.auto_created and not f.concrete)
|
1388
1339
|
else (f.field.related_query_name(),)
|
1389
|
-
for f in chain(
|
1340
|
+
for f in chain(meta.fields, meta.related_objects)
|
1390
1341
|
)
|
1391
1342
|
)
|
1392
1343
|
|
@@ -1419,7 +1370,7 @@ class Model(metaclass=ModelBase):
|
|
1419
1370
|
if allowed_len is None:
|
1420
1371
|
return errors
|
1421
1372
|
|
1422
|
-
for f in cls.
|
1373
|
+
for f in cls._model_meta.local_fields:
|
1423
1374
|
_, column_name = f.get_attname_column()
|
1424
1375
|
|
1425
1376
|
# Check if auto-generated name for the field is too long
|
@@ -1439,14 +1390,14 @@ class Model(metaclass=ModelBase):
|
|
1439
1390
|
)
|
1440
1391
|
)
|
1441
1392
|
|
1442
|
-
for f in cls.
|
1393
|
+
for f in cls._model_meta.local_many_to_many:
|
1443
1394
|
# Skip nonexistent models.
|
1444
1395
|
if isinstance(f.remote_field.through, str):
|
1445
1396
|
continue
|
1446
1397
|
|
1447
1398
|
# Check if auto-generated name for the M2M field is too long
|
1448
1399
|
# for the database.
|
1449
|
-
for m2m in f.remote_field.through.
|
1400
|
+
for m2m in f.remote_field.through._model_meta.local_fields:
|
1450
1401
|
_, rel_name = m2m.get_attname_column()
|
1451
1402
|
if (
|
1452
1403
|
m2m.db_column is None
|
@@ -1487,10 +1438,11 @@ class Model(metaclass=ModelBase):
|
|
1487
1438
|
errors = []
|
1488
1439
|
if not (
|
1489
1440
|
db_connection.features.supports_table_check_constraints
|
1490
|
-
or "supports_table_check_constraints"
|
1441
|
+
or "supports_table_check_constraints"
|
1442
|
+
in cls.model_options.required_db_features
|
1491
1443
|
) and any(
|
1492
1444
|
isinstance(constraint, CheckConstraint)
|
1493
|
-
for constraint in cls.
|
1445
|
+
for constraint in cls.model_options.constraints
|
1494
1446
|
):
|
1495
1447
|
errors.append(
|
1496
1448
|
PreflightResult(
|
@@ -1505,11 +1457,11 @@ class Model(metaclass=ModelBase):
|
|
1505
1457
|
|
1506
1458
|
if not (
|
1507
1459
|
db_connection.features.supports_partial_indexes
|
1508
|
-
or "supports_partial_indexes" in cls.
|
1460
|
+
or "supports_partial_indexes" in cls.model_options.required_db_features
|
1509
1461
|
) and any(
|
1510
1462
|
isinstance(constraint, UniqueConstraint)
|
1511
1463
|
and constraint.condition is not None
|
1512
|
-
for constraint in cls.
|
1464
|
+
for constraint in cls.model_options.constraints
|
1513
1465
|
):
|
1514
1466
|
errors.append(
|
1515
1467
|
PreflightResult(
|
@@ -1525,11 +1477,11 @@ class Model(metaclass=ModelBase):
|
|
1525
1477
|
if not (
|
1526
1478
|
db_connection.features.supports_deferrable_unique_constraints
|
1527
1479
|
or "supports_deferrable_unique_constraints"
|
1528
|
-
in cls.
|
1480
|
+
in cls.model_options.required_db_features
|
1529
1481
|
) and any(
|
1530
1482
|
isinstance(constraint, UniqueConstraint)
|
1531
1483
|
and constraint.deferrable is not None
|
1532
|
-
for constraint in cls.
|
1484
|
+
for constraint in cls.model_options.constraints
|
1533
1485
|
):
|
1534
1486
|
errors.append(
|
1535
1487
|
PreflightResult(
|
@@ -1544,10 +1496,10 @@ class Model(metaclass=ModelBase):
|
|
1544
1496
|
|
1545
1497
|
if not (
|
1546
1498
|
db_connection.features.supports_covering_indexes
|
1547
|
-
or "supports_covering_indexes" in cls.
|
1499
|
+
or "supports_covering_indexes" in cls.model_options.required_db_features
|
1548
1500
|
) and any(
|
1549
1501
|
isinstance(constraint, UniqueConstraint) and constraint.include
|
1550
|
-
for constraint in cls.
|
1502
|
+
for constraint in cls.model_options.constraints
|
1551
1503
|
):
|
1552
1504
|
errors.append(
|
1553
1505
|
PreflightResult(
|
@@ -1562,10 +1514,10 @@ class Model(metaclass=ModelBase):
|
|
1562
1514
|
|
1563
1515
|
if not (
|
1564
1516
|
db_connection.features.supports_expression_indexes
|
1565
|
-
or "supports_expression_indexes" in cls.
|
1517
|
+
or "supports_expression_indexes" in cls.model_options.required_db_features
|
1566
1518
|
) and any(
|
1567
1519
|
isinstance(constraint, UniqueConstraint) and constraint.contains_expressions
|
1568
|
-
for constraint in cls.
|
1520
|
+
for constraint in cls.model_options.constraints
|
1569
1521
|
):
|
1570
1522
|
errors.append(
|
1571
1523
|
PreflightResult(
|
@@ -1580,22 +1532,23 @@ class Model(metaclass=ModelBase):
|
|
1580
1532
|
fields = set(
|
1581
1533
|
chain.from_iterable(
|
1582
1534
|
(*constraint.fields, *constraint.include)
|
1583
|
-
for constraint in cls.
|
1535
|
+
for constraint in cls.model_options.constraints
|
1584
1536
|
if isinstance(constraint, UniqueConstraint)
|
1585
1537
|
)
|
1586
1538
|
)
|
1587
1539
|
references = set()
|
1588
|
-
for constraint in cls.
|
1540
|
+
for constraint in cls.model_options.constraints:
|
1589
1541
|
if isinstance(constraint, UniqueConstraint):
|
1590
1542
|
if (
|
1591
1543
|
db_connection.features.supports_partial_indexes
|
1592
|
-
or "supports_partial_indexes"
|
1544
|
+
or "supports_partial_indexes"
|
1545
|
+
not in cls.model_options.required_db_features
|
1593
1546
|
) and isinstance(constraint.condition, Q):
|
1594
1547
|
references.update(cls._get_expr_references(constraint.condition))
|
1595
1548
|
if (
|
1596
1549
|
db_connection.features.supports_expression_indexes
|
1597
1550
|
or "supports_expression_indexes"
|
1598
|
-
not in cls.
|
1551
|
+
not in cls.model_options.required_db_features
|
1599
1552
|
) and constraint.contains_expressions:
|
1600
1553
|
for expression in constraint.expressions:
|
1601
1554
|
references.update(cls._get_expr_references(expression))
|
@@ -1603,7 +1556,7 @@ class Model(metaclass=ModelBase):
|
|
1603
1556
|
if (
|
1604
1557
|
db_connection.features.supports_table_check_constraints
|
1605
1558
|
or "supports_table_check_constraints"
|
1606
|
-
not in cls.
|
1559
|
+
not in cls.model_options.required_db_features
|
1607
1560
|
):
|
1608
1561
|
if isinstance(constraint.check, Q):
|
1609
1562
|
references.update(cls._get_expr_references(constraint.check))
|
@@ -1627,7 +1580,7 @@ class Model(metaclass=ModelBase):
|
|
1627
1580
|
# If it has no lookups it cannot result in a JOIN.
|
1628
1581
|
continue
|
1629
1582
|
try:
|
1630
|
-
field = cls.
|
1583
|
+
field = cls._model_meta.get_field(field_name)
|
1631
1584
|
if not field.is_relation or field.many_to_many or field.one_to_many:
|
1632
1585
|
continue
|
1633
1586
|
except FieldDoesNotExist:
|