plain.models 0.50.0__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 +14 -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 +1 -1
- 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.0.dist-info}/METADATA +27 -43
- {plain_models-0.50.0.dist-info → plain_models-0.51.0.dist-info}/RECORD +49 -48
- {plain_models-0.50.0.dist-info → plain_models-0.51.0.dist-info}/WHEEL +0 -0
- {plain_models-0.50.0.dist-info → plain_models-0.51.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.50.0.dist-info → plain_models-0.51.0.dist-info}/licenses/LICENSE +0 -0
plain/models/fields/related.py
CHANGED
@@ -62,7 +62,7 @@ def resolve_relation(
|
|
62
62
|
# Look for an "app.Model" relation
|
63
63
|
if isinstance(relation, str):
|
64
64
|
if "." not in relation:
|
65
|
-
relation = f"{scope_model.
|
65
|
+
relation = f"{scope_model.model_options.package_label}.{relation}"
|
66
66
|
|
67
67
|
return relation
|
68
68
|
|
@@ -82,11 +82,11 @@ def lazy_related_operation(
|
|
82
82
|
references will be resolved relative to `model`.
|
83
83
|
|
84
84
|
This is a convenience wrapper for `Packages.lazy_model_operation` - the app
|
85
|
-
registry model used is the one found in `model.
|
85
|
+
registry model used is the one found in `model._model_meta.models_registry`.
|
86
86
|
"""
|
87
87
|
models = [model] + [resolve_relation(model, rel) for rel in related_models]
|
88
88
|
model_keys = (make_model_tuple(m) for m in models)
|
89
|
-
models_registry = model.
|
89
|
+
models_registry = model._model_meta.models_registry
|
90
90
|
return models_registry.lazy_model_operation(
|
91
91
|
partial(function, **kwargs), *model_keys
|
92
92
|
)
|
@@ -140,7 +140,7 @@ class RelatedField(FieldCacheMixin, Field):
|
|
140
140
|
if not is_valid_id:
|
141
141
|
return [
|
142
142
|
PreflightResult(
|
143
|
-
fix=f"The name '{self.remote_field.related_name}' is invalid related_name for field {self.model.
|
143
|
+
fix=f"The name '{self.remote_field.related_name}' is invalid related_name for field {self.model.model_options.object_name}.{self.name}. Related name must be a valid Python identifier.",
|
144
144
|
obj=self,
|
145
145
|
id="fields.invalid_related_name",
|
146
146
|
)
|
@@ -180,13 +180,13 @@ class RelatedField(FieldCacheMixin, Field):
|
|
180
180
|
|
181
181
|
def _check_relation_model_exists(self) -> list[PreflightResult]:
|
182
182
|
rel_is_missing = (
|
183
|
-
self.remote_field.model not in self.
|
183
|
+
self.remote_field.model not in self.meta.models_registry.get_models() # type: ignore[attr-defined]
|
184
184
|
)
|
185
185
|
rel_is_string = isinstance(self.remote_field.model, str) # type: ignore[attr-defined]
|
186
186
|
model_name = (
|
187
187
|
self.remote_field.model # type: ignore[attr-defined]
|
188
188
|
if rel_is_string
|
189
|
-
else self.remote_field.model.
|
189
|
+
else self.remote_field.model.model_options.object_name
|
190
190
|
)
|
191
191
|
if rel_is_missing and rel_is_string:
|
192
192
|
return [
|
@@ -206,7 +206,6 @@ class RelatedField(FieldCacheMixin, Field):
|
|
206
206
|
from plain.models.base import ModelBase
|
207
207
|
|
208
208
|
errors: list[PreflightResult] = []
|
209
|
-
opts = self.model._meta # type: ignore[attr-defined]
|
210
209
|
|
211
210
|
# f.remote_field.model may be a string instead of a model. Skip if
|
212
211
|
# model name is not resolved.
|
@@ -224,8 +223,9 @@ class RelatedField(FieldCacheMixin, Field):
|
|
224
223
|
# foreign = models.ForeignKey(Target)
|
225
224
|
# m2m = models.ManyToManyField(Target)
|
226
225
|
|
227
|
-
#
|
228
|
-
|
226
|
+
# rel_options.object_name == "Target"
|
227
|
+
rel_meta = self.remote_field.model._model_meta
|
228
|
+
rel_options = self.remote_field.model.model_options
|
229
229
|
# If the field doesn't install a backward relation on the target model
|
230
230
|
# (so `is_hidden` returns True), then there are no clashes to check
|
231
231
|
# and we can skip these fields.
|
@@ -235,20 +235,20 @@ class RelatedField(FieldCacheMixin, Field):
|
|
235
235
|
) # i. e. "model_set" # type: ignore[attr-defined]
|
236
236
|
rel_query_name = self.related_query_name() # i. e. "model"
|
237
237
|
# i.e. "package_label.Model.field".
|
238
|
-
field_name = f"{
|
238
|
+
field_name = f"{self.model.model_options.label}.{self.name}"
|
239
239
|
|
240
240
|
# Check clashes between accessor or reverse query name of `field`
|
241
241
|
# and any other field name -- i.e. accessor for Model.foreign is
|
242
242
|
# model_set and it clashes with Target.model_set.
|
243
|
-
potential_clashes =
|
243
|
+
potential_clashes = rel_meta.fields + rel_meta.many_to_many
|
244
244
|
for clash_field in potential_clashes:
|
245
245
|
# i.e. "package_label.Target.model_set".
|
246
|
-
clash_name = f"{
|
246
|
+
clash_name = f"{rel_options.label}.{clash_field.name}"
|
247
247
|
if not rel_is_hidden and clash_field.name == rel_name:
|
248
248
|
errors.append(
|
249
249
|
PreflightResult(
|
250
250
|
fix=(
|
251
|
-
f"Reverse accessor '{
|
251
|
+
f"Reverse accessor '{rel_options.object_name}.{rel_name}' "
|
252
252
|
f"for '{field_name}' clashes with field name "
|
253
253
|
f"'{clash_name}'. "
|
254
254
|
f"Rename field '{clash_name}', or add/change a related_name "
|
@@ -275,17 +275,15 @@ class RelatedField(FieldCacheMixin, Field):
|
|
275
275
|
# Check clashes between accessors/reverse query names of `field` and
|
276
276
|
# any other field accessor -- i. e. Model.foreign accessor clashes with
|
277
277
|
# Model.m2m accessor.
|
278
|
-
potential_clashes = (r for r in
|
278
|
+
potential_clashes = (r for r in rel_meta.related_objects if r.field is not self)
|
279
279
|
for clash_field in potential_clashes:
|
280
280
|
# i.e. "package_label.Model.m2m".
|
281
|
-
clash_name =
|
282
|
-
f"{clash_field.related_model._meta.label}.{clash_field.field.name}"
|
283
|
-
)
|
281
|
+
clash_name = f"{clash_field.related_model.model_options.label}.{clash_field.field.name}"
|
284
282
|
if not rel_is_hidden and clash_field.get_accessor_name() == rel_name:
|
285
283
|
errors.append(
|
286
284
|
PreflightResult(
|
287
285
|
fix=(
|
288
|
-
f"Reverse accessor '{
|
286
|
+
f"Reverse accessor '{rel_options.object_name}.{rel_name}' "
|
289
287
|
f"for '{field_name}' clashes with reverse accessor for "
|
290
288
|
f"'{clash_name}'. "
|
291
289
|
"Add or change a related_name argument "
|
@@ -320,21 +318,21 @@ class RelatedField(FieldCacheMixin, Field):
|
|
320
318
|
def contribute_to_class(self, cls: type[Model], name: str) -> None:
|
321
319
|
super().contribute_to_class(cls, name) # type: ignore[misc]
|
322
320
|
|
323
|
-
self.
|
321
|
+
self.meta = cls._model_meta
|
324
322
|
|
325
323
|
if self.remote_field.related_name: # type: ignore[attr-defined]
|
326
324
|
related_name = self.remote_field.related_name # type: ignore[attr-defined]
|
327
325
|
related_name %= {
|
328
326
|
"class": cls.__name__.lower(),
|
329
|
-
"model_name": cls.
|
330
|
-
"package_label": cls.
|
327
|
+
"model_name": cls.model_options.model_name.lower(),
|
328
|
+
"package_label": cls.model_options.package_label.lower(),
|
331
329
|
}
|
332
330
|
self.remote_field.related_name = related_name # type: ignore[attr-defined]
|
333
331
|
|
334
332
|
if self.remote_field.related_query_name: # type: ignore[attr-defined]
|
335
333
|
related_query_name = self.remote_field.related_query_name % { # type: ignore[attr-defined]
|
336
334
|
"class": cls.__name__.lower(),
|
337
|
-
"package_label": cls.
|
335
|
+
"package_label": cls.model_options.package_label.lower(),
|
338
336
|
}
|
339
337
|
self.remote_field.related_query_name = related_query_name # type: ignore[attr-defined]
|
340
338
|
|
@@ -389,7 +387,9 @@ class RelatedField(FieldCacheMixin, Field):
|
|
389
387
|
)
|
390
388
|
|
391
389
|
def set_attributes_from_rel(self) -> None:
|
392
|
-
self.name = self.name or (
|
390
|
+
self.name = self.name or (
|
391
|
+
self.remote_field.model.model_options.model_name + "_" + "id"
|
392
|
+
)
|
393
393
|
self.remote_field.set_field_name() # type: ignore[attr-defined]
|
394
394
|
|
395
395
|
def do_related_class(self, other: type[Model], cls: type[Model]) -> None:
|
@@ -415,7 +415,7 @@ class RelatedField(FieldCacheMixin, Field):
|
|
415
415
|
return (
|
416
416
|
self.remote_field.related_query_name # type: ignore[attr-defined]
|
417
417
|
or self.remote_field.related_name # type: ignore[attr-defined]
|
418
|
-
or self.
|
418
|
+
or self.model.model_options.model_name # type: ignore[attr-defined]
|
419
419
|
)
|
420
420
|
|
421
421
|
@property
|
@@ -469,10 +469,10 @@ class ForeignKey(RelatedField):
|
|
469
469
|
db_constraint: bool = True,
|
470
470
|
**kwargs: Any,
|
471
471
|
):
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
472
|
+
if not isinstance(to, str):
|
473
|
+
try:
|
474
|
+
to.model_options.model_name
|
475
|
+
except AttributeError:
|
476
476
|
raise TypeError(
|
477
477
|
f"{self.__class__.__name__}({to!r}) is invalid. First parameter to ForeignKey must be "
|
478
478
|
f"either a model, a model name, or the string {RECURSIVE_RELATIONSHIP_CONSTANT!r}"
|
@@ -565,12 +565,12 @@ class ForeignKey(RelatedField):
|
|
565
565
|
|
566
566
|
def get_path_info(self, filtered_relation: Any = None) -> list[PathInfo]:
|
567
567
|
"""Get path from this field to the related model."""
|
568
|
-
|
569
|
-
|
568
|
+
meta = self.remote_field.model._model_meta
|
569
|
+
from_meta = self.model._model_meta
|
570
570
|
return [
|
571
571
|
PathInfo(
|
572
|
-
|
573
|
-
|
572
|
+
from_meta=from_meta,
|
573
|
+
to_meta=meta,
|
574
574
|
target_fields=self.foreign_related_fields,
|
575
575
|
join_field=self,
|
576
576
|
m2m=False,
|
@@ -585,13 +585,13 @@ class ForeignKey(RelatedField):
|
|
585
585
|
|
586
586
|
def get_reverse_path_info(self, filtered_relation: Any = None) -> list[PathInfo]:
|
587
587
|
"""Get path from the related model to this field's model."""
|
588
|
-
|
589
|
-
|
588
|
+
meta = self.model._model_meta
|
589
|
+
from_meta = self.remote_field.model._model_meta
|
590
590
|
return [
|
591
591
|
PathInfo(
|
592
|
-
|
593
|
-
|
594
|
-
target_fields=(
|
592
|
+
from_meta=from_meta,
|
593
|
+
to_meta=meta,
|
594
|
+
target_fields=(meta.get_field("id"),),
|
595
595
|
join_field=self.remote_field, # type: ignore[attr-defined]
|
596
596
|
m2m=not self.primary_key, # type: ignore[attr-defined]
|
597
597
|
direct=False,
|
@@ -619,7 +619,7 @@ class ForeignKey(RelatedField):
|
|
619
619
|
# it along for later - this is too early because it's still
|
620
620
|
# model load time.
|
621
621
|
if self.remote_field.limit_choices_to: # type: ignore[attr-defined]
|
622
|
-
cls.
|
622
|
+
cls._model_meta.related_fkey_lookups.append(
|
623
623
|
self.remote_field.limit_choices_to # type: ignore[attr-defined]
|
624
624
|
)
|
625
625
|
|
@@ -669,7 +669,7 @@ class ForeignKey(RelatedField):
|
|
669
669
|
else:
|
670
670
|
kwargs["to"] = self.remote_field.model.lower() # type: ignore[attr-defined]
|
671
671
|
else:
|
672
|
-
kwargs["to"] = self.remote_field.model.
|
672
|
+
kwargs["to"] = self.remote_field.model.model_options.label_lower
|
673
673
|
|
674
674
|
if self.db_index is not True:
|
675
675
|
kwargs["db_index"] = self.db_index
|
@@ -691,7 +691,7 @@ class ForeignKey(RelatedField):
|
|
691
691
|
if value is None:
|
692
692
|
return None
|
693
693
|
|
694
|
-
qs = self.remote_field.model.
|
694
|
+
qs = self.remote_field.model._model_meta.base_queryset.filter(
|
695
695
|
**{self.remote_field.field_name: value} # type: ignore[attr-defined]
|
696
696
|
)
|
697
697
|
qs = qs.complex_filter(self.get_limit_choices_to())
|
@@ -700,7 +700,7 @@ class ForeignKey(RelatedField):
|
|
700
700
|
self.error_messages["invalid"], # type: ignore[attr-defined]
|
701
701
|
code="invalid",
|
702
702
|
params={
|
703
|
-
"model": self.remote_field.model.
|
703
|
+
"model": self.remote_field.model.model_options.model_name,
|
704
704
|
"id": value,
|
705
705
|
"field": self.remote_field.field_name, # type: ignore[attr-defined]
|
706
706
|
"value": value,
|
@@ -713,14 +713,14 @@ class ForeignKey(RelatedField):
|
|
713
713
|
f"Related model {self.remote_field.model!r} cannot be resolved" # type: ignore[attr-defined]
|
714
714
|
)
|
715
715
|
from_field = self
|
716
|
-
to_field = self.remote_field.model.
|
716
|
+
to_field = self.remote_field.model._model_meta.get_field("id")
|
717
717
|
related_fields: list[tuple[ForeignKey, Field]] = [(from_field, to_field)]
|
718
718
|
|
719
719
|
for from_field, to_field in related_fields:
|
720
720
|
if to_field and to_field.model != self.remote_field.model: # type: ignore[attr-defined]
|
721
721
|
raise FieldError(
|
722
|
-
f"'{self.model.
|
723
|
-
f"'{self.remote_field.model.
|
722
|
+
f"'{self.model.model_options.label}.{self.name}' refers to field '{to_field.name}' which is not local to model "
|
723
|
+
f"'{self.remote_field.model.model_options.label}'."
|
724
724
|
)
|
725
725
|
return related_fields
|
726
726
|
|
@@ -823,10 +823,10 @@ class ManyToManyField(RelatedField):
|
|
823
823
|
symmetrical: bool | None = None,
|
824
824
|
**kwargs: Any,
|
825
825
|
):
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
826
|
+
if not isinstance(to, str):
|
827
|
+
try:
|
828
|
+
to._model_meta
|
829
|
+
except AttributeError:
|
830
830
|
raise TypeError(
|
831
831
|
f"{self.__class__.__name__}({to!r}) is invalid. First parameter to ManyToManyField "
|
832
832
|
f"must be either a model, a model name, or the string {RECURSIVE_RELATIONSHIP_CONSTANT!r}"
|
@@ -915,14 +915,14 @@ class ManyToManyField(RelatedField):
|
|
915
915
|
def _check_relationship_model(
|
916
916
|
self, from_model: type[Model] | None = None, **kwargs: Any
|
917
917
|
) -> list[PreflightResult]:
|
918
|
-
if hasattr(self.remote_field.through, "
|
919
|
-
qualified_model_name = f"{self.remote_field.through.
|
918
|
+
if hasattr(self.remote_field.through, "_model_meta"):
|
919
|
+
qualified_model_name = f"{self.remote_field.through.model_options.package_label}.{self.remote_field.through.__name__}"
|
920
920
|
else:
|
921
921
|
qualified_model_name = self.remote_field.through
|
922
922
|
|
923
923
|
errors = []
|
924
924
|
|
925
|
-
if self.remote_field.through not in self.
|
925
|
+
if self.remote_field.through not in self.meta.models_registry.get_models():
|
926
926
|
# The relationship model is not installed.
|
927
927
|
errors.append(
|
928
928
|
PreflightResult(
|
@@ -944,18 +944,20 @@ class ManyToManyField(RelatedField):
|
|
944
944
|
)
|
945
945
|
# Set some useful local variables
|
946
946
|
to_model = resolve_relation(from_model, self.remote_field.model)
|
947
|
-
from_model_name = from_model.
|
947
|
+
from_model_name = from_model.model_options.object_name
|
948
948
|
if isinstance(to_model, str):
|
949
949
|
to_model_name = to_model
|
950
950
|
else:
|
951
|
-
to_model_name = to_model.
|
952
|
-
relationship_model_name =
|
951
|
+
to_model_name = to_model.model_options.object_name
|
952
|
+
relationship_model_name = (
|
953
|
+
self.remote_field.through.model_options.object_name
|
954
|
+
)
|
953
955
|
self_referential = from_model == to_model
|
954
956
|
# Count foreign keys in intermediate model
|
955
957
|
if self_referential:
|
956
958
|
seen_self = sum(
|
957
959
|
from_model == getattr(field.remote_field, "model", None)
|
958
|
-
for field in self.remote_field.through.
|
960
|
+
for field in self.remote_field.through._model_meta.fields
|
959
961
|
)
|
960
962
|
|
961
963
|
if seen_self > 2 and not self.remote_field.through_fields:
|
@@ -977,11 +979,11 @@ class ManyToManyField(RelatedField):
|
|
977
979
|
# Count foreign keys in relationship model
|
978
980
|
seen_from = sum(
|
979
981
|
from_model == getattr(field.remote_field, "model", None)
|
980
|
-
for field in self.remote_field.through.
|
982
|
+
for field in self.remote_field.through._model_meta.fields
|
981
983
|
)
|
982
984
|
seen_to = sum(
|
983
985
|
to_model == getattr(field.remote_field, "model", None)
|
984
|
-
for field in self.remote_field.through.
|
986
|
+
for field in self.remote_field.through._model_meta.fields
|
985
987
|
)
|
986
988
|
|
987
989
|
if seen_from > 1 and not self.remote_field.through_fields:
|
@@ -1078,7 +1080,7 @@ class ManyToManyField(RelatedField):
|
|
1078
1080
|
(target_field_name, target),
|
1079
1081
|
):
|
1080
1082
|
possible_field_names = []
|
1081
|
-
for f in through.
|
1083
|
+
for f in through._model_meta.fields:
|
1082
1084
|
if (
|
1083
1085
|
hasattr(f, "remote_field")
|
1084
1086
|
and getattr(f.remote_field, "model", None) == related_model
|
@@ -1088,7 +1090,7 @@ class ManyToManyField(RelatedField):
|
|
1088
1090
|
fix = (
|
1089
1091
|
"Did you mean one of the following foreign keys to '{}': "
|
1090
1092
|
"{}?".format(
|
1091
|
-
related_model.
|
1093
|
+
related_model.model_options.object_name,
|
1092
1094
|
", ".join(possible_field_names),
|
1093
1095
|
)
|
1094
1096
|
)
|
@@ -1096,7 +1098,7 @@ class ManyToManyField(RelatedField):
|
|
1096
1098
|
fix = ""
|
1097
1099
|
|
1098
1100
|
try:
|
1099
|
-
field = through.
|
1101
|
+
field = through._model_meta.get_field(field_name)
|
1100
1102
|
except FieldDoesNotExist:
|
1101
1103
|
errors.append(
|
1102
1104
|
PreflightResult(
|
@@ -1113,7 +1115,7 @@ class ManyToManyField(RelatedField):
|
|
1113
1115
|
):
|
1114
1116
|
errors.append(
|
1115
1117
|
PreflightResult(
|
1116
|
-
fix=f"'{through.
|
1118
|
+
fix=f"'{through.model_options.object_name}.{field_name}' is not a foreign key to '{related_model.model_options.object_name}'. {fix}",
|
1117
1119
|
obj=self,
|
1118
1120
|
id="fields.m2m_through_field_not_fk_to_model",
|
1119
1121
|
)
|
@@ -1125,15 +1127,15 @@ class ManyToManyField(RelatedField):
|
|
1125
1127
|
if isinstance(self.remote_field.through, str): # type: ignore[attr-defined]
|
1126
1128
|
return []
|
1127
1129
|
registered_tables = {
|
1128
|
-
model.
|
1129
|
-
for model in self.
|
1130
|
+
model.model_options.db_table: model
|
1131
|
+
for model in self.meta.models_registry.get_models() # type: ignore[attr-defined]
|
1130
1132
|
if model != self.remote_field.through # type: ignore[attr-defined]
|
1131
1133
|
}
|
1132
1134
|
m2m_db_table = self.m2m_db_table() # type: ignore[attr-defined]
|
1133
1135
|
model = registered_tables.get(m2m_db_table)
|
1134
1136
|
# Check if there's already a m2m field using the same through model.
|
1135
1137
|
if model and model != self.remote_field.through: # type: ignore[attr-defined]
|
1136
|
-
clashing_obj = model.
|
1138
|
+
clashing_obj = model.model_options.label
|
1137
1139
|
return [
|
1138
1140
|
PreflightResult(
|
1139
1141
|
fix=(
|
@@ -1161,12 +1163,12 @@ class ManyToManyField(RelatedField):
|
|
1161
1163
|
else:
|
1162
1164
|
kwargs["to"] = self.remote_field.model.lower() # type: ignore[attr-defined]
|
1163
1165
|
else:
|
1164
|
-
kwargs["to"] = self.remote_field.model.
|
1166
|
+
kwargs["to"] = self.remote_field.model.model_options.label_lower
|
1165
1167
|
|
1166
1168
|
if isinstance(self.remote_field.through, str): # type: ignore[attr-defined]
|
1167
1169
|
kwargs["through"] = self.remote_field.through # type: ignore[attr-defined]
|
1168
1170
|
else:
|
1169
|
-
kwargs["through"] = self.remote_field.through.
|
1171
|
+
kwargs["through"] = self.remote_field.through.model_options.label
|
1170
1172
|
|
1171
1173
|
return name, path, args, kwargs
|
1172
1174
|
|
@@ -1175,8 +1177,8 @@ class ManyToManyField(RelatedField):
|
|
1175
1177
|
) -> list[PathInfo]:
|
1176
1178
|
"""Called by both direct and indirect m2m traversal."""
|
1177
1179
|
int_model = self.remote_field.through # type: ignore[attr-defined]
|
1178
|
-
linkfield1 = int_model.
|
1179
|
-
linkfield2 = int_model.
|
1180
|
+
linkfield1 = int_model._model_meta.get_field(self.m2m_field_name())
|
1181
|
+
linkfield2 = int_model._model_meta.get_field(self.m2m_reverse_field_name())
|
1180
1182
|
if direct:
|
1181
1183
|
join1infos = linkfield1.reverse_path_infos # type: ignore[attr-defined]
|
1182
1184
|
if filtered_relation:
|
@@ -1211,7 +1213,7 @@ class ManyToManyField(RelatedField):
|
|
1211
1213
|
Function that can be curried to provide the m2m table name for this
|
1212
1214
|
relation.
|
1213
1215
|
"""
|
1214
|
-
return self.remote_field.through.
|
1216
|
+
return self.remote_field.through.model_options.db_table
|
1215
1217
|
|
1216
1218
|
def _get_m2m_attr(self, related: Any, attr: str) -> Any:
|
1217
1219
|
"""
|
@@ -1225,7 +1227,7 @@ class ManyToManyField(RelatedField):
|
|
1225
1227
|
link_field_name: str | None = self.remote_field.through_fields[0] # type: ignore[attr-defined]
|
1226
1228
|
else:
|
1227
1229
|
link_field_name = None
|
1228
|
-
for f in self.remote_field.through.
|
1230
|
+
for f in self.remote_field.through._model_meta.fields:
|
1229
1231
|
if (
|
1230
1232
|
f.is_relation # type: ignore[attr-defined]
|
1231
1233
|
and f.remote_field.model == related.related_model # type: ignore[attr-defined]
|
@@ -1248,7 +1250,7 @@ class ManyToManyField(RelatedField):
|
|
1248
1250
|
link_field_name: str | None = self.remote_field.through_fields[1] # type: ignore[attr-defined]
|
1249
1251
|
else:
|
1250
1252
|
link_field_name = None
|
1251
|
-
for f in self.remote_field.through.
|
1253
|
+
for f in self.remote_field.through._model_meta.fields:
|
1252
1254
|
if f.is_relation and f.remote_field.model == related.model: # type: ignore[attr-defined]
|
1253
1255
|
if link_field_name is None and related.related_model == related.model:
|
1254
1256
|
# If this is an m2m-intermediate to self,
|
@@ -104,7 +104,7 @@ class ForwardManyToOneDescriptor:
|
|
104
104
|
return self.field.is_cached(instance)
|
105
105
|
|
106
106
|
def get_queryset(self) -> QuerySet:
|
107
|
-
qs = self.field.remote_field.model.
|
107
|
+
qs = self.field.remote_field.model._model_meta.base_queryset
|
108
108
|
return qs.all()
|
109
109
|
|
110
110
|
def get_prefetch_queryset(
|
@@ -217,7 +217,7 @@ class ForwardManyToOneDescriptor:
|
|
217
217
|
# An object must be an instance of the related class.
|
218
218
|
if value is not None and not isinstance(value, self.field.remote_field.model):
|
219
219
|
raise ValueError(
|
220
|
-
f'Cannot assign "{value!r}": "{instance.
|
220
|
+
f'Cannot assign "{value!r}": "{instance.model_options.object_name}.{self.field.name}" must be a "{self.field.remote_field.model.model_options.object_name}" instance.'
|
221
221
|
)
|
222
222
|
remote_field = self.field.remote_field
|
223
223
|
# If we're setting the value of a OneToOneField to None, we need to clear
|
@@ -55,7 +55,7 @@ def get_normalized_value(value: Any, lhs: Any) -> tuple[Any, ...]:
|
|
55
55
|
sources = lhs.output_field.path_infos[-1].target_fields
|
56
56
|
for source in sources:
|
57
57
|
while not isinstance(value, source.model) and source.remote_field:
|
58
|
-
source = source.remote_field.model.
|
58
|
+
source = source.remote_field.model._model_meta.get_field(
|
59
59
|
source.remote_field.field_name
|
60
60
|
)
|
61
61
|
try:
|
@@ -70,8 +70,6 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
70
70
|
self.instance = instance
|
71
71
|
self.field = rel.field
|
72
72
|
self.core_filters = {self.field.name: instance}
|
73
|
-
# Store the base queryset class for this model
|
74
|
-
self.base_queryset_class = rel.related_model._meta.queryset.__class__
|
75
73
|
self.allow_null = rel.field.allow_null
|
76
74
|
|
77
75
|
def _check_fk_val(self) -> None:
|
@@ -137,15 +135,14 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
137
135
|
self.field.remote_field.get_cache_name()
|
138
136
|
]
|
139
137
|
except (AttributeError, KeyError):
|
140
|
-
|
141
|
-
queryset = self.base_queryset_class(model=self.model)
|
138
|
+
queryset = self.model.query
|
142
139
|
return self._apply_rel_filters(queryset)
|
143
140
|
|
144
141
|
def get_prefetch_queryset(
|
145
142
|
self, instances: Any, queryset: QuerySet | None = None
|
146
143
|
) -> tuple[QuerySet, Any, Any, bool, str, bool]:
|
147
144
|
if queryset is None:
|
148
|
-
queryset = self.
|
145
|
+
queryset = self.model.query
|
149
146
|
|
150
147
|
rel_obj_attr = self.field.get_local_related_value
|
151
148
|
instance_attr = self.field.get_foreign_related_value
|
@@ -168,7 +165,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
168
165
|
def check_and_update_obj(obj: Any) -> None:
|
169
166
|
if not isinstance(obj, self.model):
|
170
167
|
raise TypeError(
|
171
|
-
f"'{self.model.
|
168
|
+
f"'{self.model.model_options.object_name}' instance expected, got {obj!r}"
|
172
169
|
)
|
173
170
|
setattr(obj, self.field.name, self.instance)
|
174
171
|
|
@@ -182,7 +179,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
182
179
|
"the object first."
|
183
180
|
)
|
184
181
|
ids.append(obj.id)
|
185
|
-
self.model.
|
182
|
+
self.model._model_meta.base_queryset.filter(id__in=ids).update(
|
186
183
|
**{
|
187
184
|
self.field.name: self.instance,
|
188
185
|
}
|
@@ -196,17 +193,17 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
196
193
|
def create(self, **kwargs: Any) -> Any:
|
197
194
|
self._check_fk_val()
|
198
195
|
kwargs[self.field.name] = self.instance
|
199
|
-
return self.
|
196
|
+
return self.model.query.create(**kwargs)
|
200
197
|
|
201
198
|
def get_or_create(self, **kwargs: Any) -> tuple[Any, bool]:
|
202
199
|
self._check_fk_val()
|
203
200
|
kwargs[self.field.name] = self.instance
|
204
|
-
return self.
|
201
|
+
return self.model.query.get_or_create(**kwargs)
|
205
202
|
|
206
203
|
def update_or_create(self, **kwargs: Any) -> tuple[Any, bool]:
|
207
204
|
self._check_fk_val()
|
208
205
|
kwargs[self.field.name] = self.instance
|
209
|
-
return self.
|
206
|
+
return self.model.query.update_or_create(**kwargs)
|
210
207
|
|
211
208
|
def remove(self, *objs: Any, bulk: bool = True) -> None:
|
212
209
|
# remove() is only provided if the ForeignKey can have a value of null
|
@@ -223,7 +220,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
223
220
|
for obj in objs:
|
224
221
|
if not isinstance(obj, self.model):
|
225
222
|
raise TypeError(
|
226
|
-
f"'{self.model.
|
223
|
+
f"'{self.model.model_options.object_name}' instance expected, got {obj!r}"
|
227
224
|
)
|
228
225
|
# Is obj actually part of this descriptor set?
|
229
226
|
if self.field.get_local_related_value(obj) == val:
|
@@ -297,11 +294,9 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
297
294
|
def __init__(self, instance: Any, rel: Any):
|
298
295
|
self.instance = instance
|
299
296
|
self.through = rel.through
|
300
|
-
# Subclasses must set model before calling super().__init__
|
301
|
-
self.base_queryset_class = self.model._meta.queryset.__class__
|
302
297
|
|
303
|
-
self.source_field = self.through.
|
304
|
-
self.target_field = self.through.
|
298
|
+
self.source_field = self.through._model_meta.get_field(self.source_field_name)
|
299
|
+
self.target_field = self.through._model_meta.get_field(self.target_field_name)
|
305
300
|
|
306
301
|
self.core_filters = {}
|
307
302
|
self.id_field_names = {}
|
@@ -338,14 +333,14 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
338
333
|
try:
|
339
334
|
return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
|
340
335
|
except (AttributeError, KeyError):
|
341
|
-
queryset = self.
|
336
|
+
queryset = self.model.query
|
342
337
|
return self._apply_rel_filters(queryset)
|
343
338
|
|
344
339
|
def get_prefetch_queryset(
|
345
340
|
self, instances: Any, queryset: QuerySet | None = None
|
346
341
|
) -> tuple[QuerySet, Any, Any, bool, str, bool]:
|
347
342
|
if queryset is None:
|
348
|
-
queryset = self.
|
343
|
+
queryset = self.model.query
|
349
344
|
|
350
345
|
queryset = _filter_prefetch_queryset(
|
351
346
|
queryset._next_is_sticky(), self.query_field_name, instances
|
@@ -353,8 +348,8 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
353
348
|
|
354
349
|
# M2M: need to annotate the query in order to get the primary model
|
355
350
|
# that the secondary model was actually related to.
|
356
|
-
fk = self.through.
|
357
|
-
join_table = fk.model.
|
351
|
+
fk = self.through._model_meta.get_field(self.source_field_name)
|
352
|
+
join_table = fk.model.model_options.db_table
|
358
353
|
qn = db_connection.ops.quote_name
|
359
354
|
queryset = queryset.extra(
|
360
355
|
select={
|
@@ -380,9 +375,7 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
380
375
|
def clear(self) -> None:
|
381
376
|
with transaction.atomic(savepoint=False):
|
382
377
|
self._remove_prefetched_objects()
|
383
|
-
filters = self._build_remove_filters(
|
384
|
-
self.base_queryset_class(model=self.model)
|
385
|
-
)
|
378
|
+
filters = self._build_remove_filters(self.model.query)
|
386
379
|
self.through.query.filter(filters).delete()
|
387
380
|
|
388
381
|
def set(
|
@@ -425,16 +418,14 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
425
418
|
def create(
|
426
419
|
self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
|
427
420
|
) -> Any:
|
428
|
-
new_obj = self.
|
421
|
+
new_obj = self.model.query.create(**kwargs)
|
429
422
|
self.add(new_obj, through_defaults=through_defaults)
|
430
423
|
return new_obj
|
431
424
|
|
432
425
|
def get_or_create(
|
433
426
|
self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
|
434
427
|
) -> tuple[Any, bool]:
|
435
|
-
obj, created = self.
|
436
|
-
**kwargs
|
437
|
-
)
|
428
|
+
obj, created = self.model.query.get_or_create(**kwargs)
|
438
429
|
# We only need to add() if created because if we got an object back
|
439
430
|
# from get() then the relationship already exists.
|
440
431
|
if created:
|
@@ -444,9 +435,7 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
444
435
|
def update_or_create(
|
445
436
|
self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
|
446
437
|
) -> tuple[Any, bool]:
|
447
|
-
obj, created = self.
|
448
|
-
**kwargs
|
449
|
-
)
|
438
|
+
obj, created = self.model.query.update_or_create(**kwargs)
|
450
439
|
# We only need to add() if created because if we got an object back
|
451
440
|
# from get() then the relationship already exists.
|
452
441
|
if created:
|
@@ -458,7 +447,7 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
458
447
|
from plain.models import Model
|
459
448
|
|
460
449
|
target_ids = set()
|
461
|
-
target_field = self.through.
|
450
|
+
target_field = self.through._model_meta.get_field(target_field_name)
|
462
451
|
for obj in objs:
|
463
452
|
if isinstance(obj, self.model):
|
464
453
|
target_id = target_field.get_foreign_related_value(obj)[0]
|
@@ -469,7 +458,7 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
469
458
|
target_ids.add(target_id)
|
470
459
|
elif isinstance(obj, Model):
|
471
460
|
raise TypeError(
|
472
|
-
f"'{self.model.
|
461
|
+
f"'{self.model.model_options.object_name}' instance expected, got {obj!r}"
|
473
462
|
)
|
474
463
|
else:
|
475
464
|
target_ids.add(target_field.get_prep_value(obj))
|
@@ -534,7 +523,7 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
534
523
|
old_ids.add(obj)
|
535
524
|
|
536
525
|
with transaction.atomic(savepoint=False):
|
537
|
-
target_model_qs = self.
|
526
|
+
target_model_qs = self.model.query
|
538
527
|
if target_model_qs._has_filters():
|
539
528
|
old_vals = target_model_qs.filter(
|
540
529
|
**{f"{self.target_field.target_field.attname}__in": old_ids}
|