plain.models 0.49.2__py3-none-any.whl → 0.50.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 +13 -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 +22 -12
- plain/models/backends/base/features.py +10 -4
- plain/models/backends/base/introspection.py +29 -16
- plain/models/backends/base/operations.py +187 -91
- plain/models/backends/base/schema.py +267 -165
- plain/models/backends/base/validation.py +12 -3
- 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 +12 -3
- 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 +106 -39
- plain/models/backends/mysql/schema.py +48 -24
- 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 +109 -42
- plain/models/backends/postgresql/schema.py +85 -46
- 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 +125 -42
- plain/models/backends/sqlite3/schema.py +82 -58
- 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 +113 -74
- plain/models/cli.py +94 -63
- plain/models/config.py +1 -1
- plain/models/connections.py +23 -7
- plain/models/constraints.py +65 -47
- plain/models/database_url.py +1 -1
- plain/models/db.py +6 -2
- plain/models/deletion.py +66 -43
- plain/models/entrypoints.py +1 -1
- plain/models/enums.py +22 -11
- plain/models/exceptions.py +23 -8
- plain/models/expressions.py +440 -257
- plain/models/fields/__init__.py +253 -202
- plain/models/fields/json.py +120 -54
- plain/models/fields/mixins.py +12 -8
- plain/models/fields/related.py +284 -252
- plain/models/fields/related_descriptors.py +31 -22
- plain/models/fields/related_lookups.py +23 -11
- plain/models/fields/related_managers.py +81 -47
- plain/models/fields/reverse_related.py +58 -55
- plain/models/forms.py +89 -63
- 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 +52 -28
- plain/models/lookups.py +228 -153
- 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 +37 -19
- plain/models/migrations/operations/fields.py +89 -42
- plain/models/migrations/operations/models.py +245 -143
- 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 +18 -11
- plain/models/migrations/serializer.py +50 -39
- plain/models/migrations/state.py +220 -133
- plain/models/migrations/utils.py +29 -13
- plain/models/migrations/writer.py +17 -14
- plain/models/options.py +63 -56
- plain/models/otel.py +16 -6
- plain/models/preflight.py +35 -12
- plain/models/query.py +323 -228
- plain/models/query_utils.py +93 -58
- plain/models/registry.py +34 -16
- plain/models/sql/compiler.py +146 -97
- plain/models/sql/datastructures.py +38 -25
- plain/models/sql/query.py +255 -169
- plain/models/sql/subqueries.py +32 -21
- 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 +13 -5
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/METADATA +1 -1
- plain_models-0.50.0.dist-info/RECORD +122 -0
- plain_models-0.49.2.dist-info/RECORD +0 -122
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/WHEEL +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/licenses/LICENSE +0 -0
plain/models/fields/related.py
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from functools import cached_property, partial
|
4
|
+
from typing import TYPE_CHECKING, Any
|
2
5
|
|
3
6
|
from plain import exceptions
|
4
7
|
from plain.models.constants import LOOKUP_SEP
|
@@ -30,10 +33,16 @@ from .related_lookups import (
|
|
30
33
|
)
|
31
34
|
from .reverse_related import ManyToManyRel, ManyToOneRel
|
32
35
|
|
36
|
+
if TYPE_CHECKING:
|
37
|
+
from plain.models.backends.base.base import BaseDatabaseWrapper
|
38
|
+
from plain.models.base import Model
|
39
|
+
|
33
40
|
RECURSIVE_RELATIONSHIP_CONSTANT = "self"
|
34
41
|
|
35
42
|
|
36
|
-
def resolve_relation(
|
43
|
+
def resolve_relation(
|
44
|
+
scope_model: type[Model], relation: type[Model] | str
|
45
|
+
) -> type[Model] | str:
|
37
46
|
"""
|
38
47
|
Transform relation into a model or fully-qualified model string of the form
|
39
48
|
"package_label.ModelName", relative to scope_model.
|
@@ -53,12 +62,14 @@ def resolve_relation(scope_model, relation):
|
|
53
62
|
# Look for an "app.Model" relation
|
54
63
|
if isinstance(relation, str):
|
55
64
|
if "." not in relation:
|
56
|
-
relation = f"{scope_model._meta.package_label}.{relation}"
|
65
|
+
relation = f"{scope_model._meta.package_label}.{relation}" # type: ignore[attr-defined]
|
57
66
|
|
58
67
|
return relation
|
59
68
|
|
60
69
|
|
61
|
-
def lazy_related_operation(
|
70
|
+
def lazy_related_operation(
|
71
|
+
function: Any, model: type[Model], *related_models: type[Model] | str, **kwargs: Any
|
72
|
+
) -> None:
|
62
73
|
"""
|
63
74
|
Schedule `function` to be called once `model` and all `related_models`
|
64
75
|
have been imported and registered with the app registry. `function` will
|
@@ -75,7 +86,7 @@ def lazy_related_operation(function, model, *related_models, **kwargs):
|
|
75
86
|
"""
|
76
87
|
models = [model] + [resolve_relation(model, rel) for rel in related_models]
|
77
88
|
model_keys = (make_model_tuple(m) for m in models)
|
78
|
-
models_registry = model._meta.models_registry
|
89
|
+
models_registry = model._meta.models_registry # type: ignore[attr-defined]
|
79
90
|
return models_registry.lazy_model_operation(
|
80
91
|
partial(function, **kwargs), *model_keys
|
81
92
|
)
|
@@ -92,35 +103,35 @@ class RelatedField(FieldCacheMixin, Field):
|
|
92
103
|
def __init__(
|
93
104
|
self,
|
94
105
|
*,
|
95
|
-
related_name=None,
|
96
|
-
related_query_name=None,
|
97
|
-
limit_choices_to=None,
|
98
|
-
**kwargs,
|
106
|
+
related_name: str | None = None,
|
107
|
+
related_query_name: str | None = None,
|
108
|
+
limit_choices_to: Any = None,
|
109
|
+
**kwargs: Any,
|
99
110
|
):
|
100
111
|
self._related_name = related_name
|
101
112
|
self._related_query_name = related_query_name
|
102
113
|
self._limit_choices_to = limit_choices_to
|
103
|
-
super().__init__(**kwargs)
|
114
|
+
super().__init__(**kwargs) # type: ignore[misc]
|
104
115
|
|
105
116
|
@cached_property
|
106
|
-
def related_model(self):
|
117
|
+
def related_model(self) -> type[Model]:
|
107
118
|
# Can't cache this property until all the models are loaded.
|
108
119
|
models_registry.check_ready()
|
109
|
-
return self.remote_field.model
|
120
|
+
return self.remote_field.model # type: ignore[attr-defined]
|
110
121
|
|
111
|
-
def preflight(self, **kwargs):
|
122
|
+
def preflight(self, **kwargs: Any) -> list[PreflightResult]: # type: ignore[misc]
|
112
123
|
return [
|
113
|
-
*super().preflight(**kwargs),
|
124
|
+
*super().preflight(**kwargs), # type: ignore[misc]
|
114
125
|
*self._check_related_name_is_valid(),
|
115
126
|
*self._check_related_query_name_is_valid(),
|
116
127
|
*self._check_relation_model_exists(),
|
117
128
|
*self._check_clashes(),
|
118
129
|
]
|
119
130
|
|
120
|
-
def _check_related_name_is_valid(self):
|
131
|
+
def _check_related_name_is_valid(self) -> list[PreflightResult]:
|
121
132
|
import keyword
|
122
133
|
|
123
|
-
related_name = self.remote_field.related_name
|
134
|
+
related_name = self.remote_field.related_name # type: ignore[attr-defined]
|
124
135
|
if related_name is None:
|
125
136
|
return []
|
126
137
|
is_valid_id = (
|
@@ -129,18 +140,18 @@ class RelatedField(FieldCacheMixin, Field):
|
|
129
140
|
if not is_valid_id:
|
130
141
|
return [
|
131
142
|
PreflightResult(
|
132
|
-
fix=f"The name '{self.remote_field.related_name}' is invalid related_name for field {self.model._meta.object_name}.{self.name}. Related name must be a valid Python identifier.",
|
143
|
+
fix=f"The name '{self.remote_field.related_name}' is invalid related_name for field {self.model._meta.object_name}.{self.name}. Related name must be a valid Python identifier.", # type: ignore[attr-defined]
|
133
144
|
obj=self,
|
134
145
|
id="fields.invalid_related_name",
|
135
146
|
)
|
136
147
|
]
|
137
148
|
return []
|
138
149
|
|
139
|
-
def _check_related_query_name_is_valid(self):
|
140
|
-
if self.remote_field.is_hidden():
|
150
|
+
def _check_related_query_name_is_valid(self) -> list[PreflightResult]:
|
151
|
+
if self.remote_field.is_hidden(): # type: ignore[attr-defined]
|
141
152
|
return []
|
142
153
|
rel_query_name = self.related_query_name()
|
143
|
-
errors = []
|
154
|
+
errors: list[PreflightResult] = []
|
144
155
|
if rel_query_name.endswith("_"):
|
145
156
|
errors.append(
|
146
157
|
PreflightResult(
|
@@ -167,15 +178,15 @@ class RelatedField(FieldCacheMixin, Field):
|
|
167
178
|
)
|
168
179
|
return errors
|
169
180
|
|
170
|
-
def _check_relation_model_exists(self):
|
181
|
+
def _check_relation_model_exists(self) -> list[PreflightResult]:
|
171
182
|
rel_is_missing = (
|
172
|
-
self.remote_field.model not in self.opts.models_registry.get_models()
|
183
|
+
self.remote_field.model not in self.opts.models_registry.get_models() # type: ignore[attr-defined]
|
173
184
|
)
|
174
|
-
rel_is_string = isinstance(self.remote_field.model, str)
|
185
|
+
rel_is_string = isinstance(self.remote_field.model, str) # type: ignore[attr-defined]
|
175
186
|
model_name = (
|
176
|
-
self.remote_field.model
|
187
|
+
self.remote_field.model # type: ignore[attr-defined]
|
177
188
|
if rel_is_string
|
178
|
-
else self.remote_field.model._meta.object_name
|
189
|
+
else self.remote_field.model._meta.object_name # type: ignore[attr-defined]
|
179
190
|
)
|
180
191
|
if rel_is_missing and rel_is_string:
|
181
192
|
return [
|
@@ -190,16 +201,16 @@ class RelatedField(FieldCacheMixin, Field):
|
|
190
201
|
]
|
191
202
|
return []
|
192
203
|
|
193
|
-
def _check_clashes(self):
|
204
|
+
def _check_clashes(self) -> list[PreflightResult]:
|
194
205
|
"""Check accessor and reverse query name clashes."""
|
195
206
|
from plain.models.base import ModelBase
|
196
207
|
|
197
|
-
errors = []
|
198
|
-
opts = self.model._meta
|
208
|
+
errors: list[PreflightResult] = []
|
209
|
+
opts = self.model._meta # type: ignore[attr-defined]
|
199
210
|
|
200
211
|
# f.remote_field.model may be a string instead of a model. Skip if
|
201
212
|
# model name is not resolved.
|
202
|
-
if not isinstance(self.remote_field.model, ModelBase):
|
213
|
+
if not isinstance(self.remote_field.model, ModelBase): # type: ignore[attr-defined]
|
203
214
|
return []
|
204
215
|
|
205
216
|
# Consider that we are checking field `Model.foreign` and the models
|
@@ -214,12 +225,14 @@ class RelatedField(FieldCacheMixin, Field):
|
|
214
225
|
# m2m = models.ManyToManyField(Target)
|
215
226
|
|
216
227
|
# rel_opts.object_name == "Target"
|
217
|
-
rel_opts = self.remote_field.model._meta
|
228
|
+
rel_opts = self.remote_field.model._meta # type: ignore[attr-defined]
|
218
229
|
# If the field doesn't install a backward relation on the target model
|
219
230
|
# (so `is_hidden` returns True), then there are no clashes to check
|
220
231
|
# and we can skip these fields.
|
221
|
-
rel_is_hidden = self.remote_field.is_hidden()
|
222
|
-
rel_name =
|
232
|
+
rel_is_hidden = self.remote_field.is_hidden() # type: ignore[attr-defined]
|
233
|
+
rel_name = (
|
234
|
+
self.remote_field.get_accessor_name()
|
235
|
+
) # i. e. "model_set" # type: ignore[attr-defined]
|
223
236
|
rel_query_name = self.related_query_name() # i. e. "model"
|
224
237
|
# i.e. "package_label.Model.field".
|
225
238
|
field_name = f"{opts.label}.{self.name}"
|
@@ -299,42 +312,47 @@ class RelatedField(FieldCacheMixin, Field):
|
|
299
312
|
|
300
313
|
return errors
|
301
314
|
|
302
|
-
def db_type(self, connection):
|
315
|
+
def db_type(self, connection: BaseDatabaseWrapper) -> None:
|
303
316
|
# By default related field will not have a column as it relates to
|
304
317
|
# columns from another table.
|
305
318
|
return None
|
306
319
|
|
307
|
-
def contribute_to_class(self, cls, name):
|
308
|
-
super().contribute_to_class(cls, name)
|
320
|
+
def contribute_to_class(self, cls: type[Model], name: str) -> None:
|
321
|
+
super().contribute_to_class(cls, name) # type: ignore[misc]
|
309
322
|
|
310
|
-
self.opts = cls._meta
|
323
|
+
self.opts = cls._meta # type: ignore[attr-defined]
|
311
324
|
|
312
|
-
if self.remote_field.related_name:
|
313
|
-
related_name = self.remote_field.related_name
|
325
|
+
if self.remote_field.related_name: # type: ignore[attr-defined]
|
326
|
+
related_name = self.remote_field.related_name # type: ignore[attr-defined]
|
314
327
|
related_name %= {
|
315
328
|
"class": cls.__name__.lower(),
|
316
|
-
"model_name": cls._meta.model_name.lower(),
|
317
|
-
"package_label": cls._meta.package_label.lower(),
|
329
|
+
"model_name": cls._meta.model_name.lower(), # type: ignore[union-attr]
|
330
|
+
"package_label": cls._meta.package_label.lower(), # type: ignore[union-attr]
|
318
331
|
}
|
319
|
-
self.remote_field.related_name = related_name
|
332
|
+
self.remote_field.related_name = related_name # type: ignore[attr-defined]
|
320
333
|
|
321
|
-
if self.remote_field.related_query_name:
|
322
|
-
related_query_name = self.remote_field.related_query_name % {
|
334
|
+
if self.remote_field.related_query_name: # type: ignore[attr-defined]
|
335
|
+
related_query_name = self.remote_field.related_query_name % { # type: ignore[attr-defined]
|
323
336
|
"class": cls.__name__.lower(),
|
324
|
-
"package_label": cls._meta.package_label.lower(),
|
337
|
+
"package_label": cls._meta.package_label.lower(), # type: ignore[union-attr]
|
325
338
|
}
|
326
|
-
self.remote_field.related_query_name = related_query_name
|
339
|
+
self.remote_field.related_query_name = related_query_name # type: ignore[attr-defined]
|
327
340
|
|
328
|
-
def resolve_related_class(
|
329
|
-
|
341
|
+
def resolve_related_class(
|
342
|
+
model: type[Model], related: type[Model], field: RelatedField
|
343
|
+
) -> None:
|
344
|
+
field.remote_field.model = related # type: ignore[attr-defined]
|
330
345
|
field.do_related_class(related, model)
|
331
346
|
|
332
347
|
lazy_related_operation(
|
333
|
-
resolve_related_class,
|
348
|
+
resolve_related_class,
|
349
|
+
cls,
|
350
|
+
self.remote_field.model,
|
351
|
+
field=self, # type: ignore[attr-defined]
|
334
352
|
)
|
335
353
|
|
336
|
-
def deconstruct(self):
|
337
|
-
name, path, args, kwargs = super().deconstruct()
|
354
|
+
def deconstruct(self) -> tuple[str, str, list[Any], dict[str, Any]]:
|
355
|
+
name, path, args, kwargs = super().deconstruct() # type: ignore[misc]
|
338
356
|
if self._limit_choices_to:
|
339
357
|
kwargs["limit_choices_to"] = self._limit_choices_to
|
340
358
|
if self._related_name is not None:
|
@@ -343,7 +361,7 @@ class RelatedField(FieldCacheMixin, Field):
|
|
343
361
|
kwargs["related_query_name"] = self._related_query_name
|
344
362
|
return name, path, args, kwargs
|
345
363
|
|
346
|
-
def get_forward_related_filter(self, obj):
|
364
|
+
def get_forward_related_filter(self, obj: Model) -> dict[str, Any]:
|
347
365
|
"""
|
348
366
|
Return the keyword arguments that when supplied to
|
349
367
|
self.model.object.filter(), would select all instances related through
|
@@ -353,10 +371,10 @@ class RelatedField(FieldCacheMixin, Field):
|
|
353
371
|
"""
|
354
372
|
return {
|
355
373
|
f"{self.name}__{rh_field.name}": getattr(obj, rh_field.attname)
|
356
|
-
for _, rh_field in self.related_fields
|
374
|
+
for _, rh_field in self.related_fields # type: ignore[attr-defined]
|
357
375
|
}
|
358
376
|
|
359
|
-
def get_reverse_related_filter(self, obj):
|
377
|
+
def get_reverse_related_filter(self, obj: Model) -> Any:
|
360
378
|
"""
|
361
379
|
Complement to get_forward_related_filter(). Return the keyword
|
362
380
|
arguments that when passed to self.related_field.model.object.filter()
|
@@ -366,47 +384,47 @@ class RelatedField(FieldCacheMixin, Field):
|
|
366
384
|
return Q.create(
|
367
385
|
[
|
368
386
|
(rh_field.attname, getattr(obj, lh_field.attname))
|
369
|
-
for lh_field, rh_field in self.related_fields
|
387
|
+
for lh_field, rh_field in self.related_fields # type: ignore[attr-defined]
|
370
388
|
]
|
371
389
|
)
|
372
390
|
|
373
|
-
def set_attributes_from_rel(self):
|
374
|
-
self.name = self.name or (self.remote_field.model._meta.model_name + "_" + "id")
|
375
|
-
self.remote_field.set_field_name()
|
391
|
+
def set_attributes_from_rel(self) -> None:
|
392
|
+
self.name = self.name or (self.remote_field.model._meta.model_name + "_" + "id") # type: ignore[attr-defined]
|
393
|
+
self.remote_field.set_field_name() # type: ignore[attr-defined]
|
376
394
|
|
377
|
-
def do_related_class(self, other, cls):
|
395
|
+
def do_related_class(self, other: type[Model], cls: type[Model]) -> None:
|
378
396
|
self.set_attributes_from_rel()
|
379
|
-
self.contribute_to_related_class(other, self.remote_field)
|
397
|
+
self.contribute_to_related_class(other, self.remote_field) # type: ignore[attr-defined]
|
380
398
|
|
381
|
-
def get_limit_choices_to(self):
|
399
|
+
def get_limit_choices_to(self) -> Any:
|
382
400
|
"""
|
383
401
|
Return ``limit_choices_to`` for this model field.
|
384
402
|
|
385
403
|
If it is a callable, it will be invoked and the result will be
|
386
404
|
returned.
|
387
405
|
"""
|
388
|
-
if callable(self.remote_field.limit_choices_to):
|
389
|
-
return self.remote_field.limit_choices_to()
|
390
|
-
return self.remote_field.limit_choices_to
|
406
|
+
if callable(self.remote_field.limit_choices_to): # type: ignore[attr-defined]
|
407
|
+
return self.remote_field.limit_choices_to() # type: ignore[attr-defined]
|
408
|
+
return self.remote_field.limit_choices_to # type: ignore[attr-defined]
|
391
409
|
|
392
|
-
def related_query_name(self):
|
410
|
+
def related_query_name(self) -> str:
|
393
411
|
"""
|
394
412
|
Define the name that can be used to identify this related object in a
|
395
413
|
table-spanning query.
|
396
414
|
"""
|
397
415
|
return (
|
398
|
-
self.remote_field.related_query_name
|
399
|
-
or self.remote_field.related_name
|
400
|
-
or self.opts.model_name
|
416
|
+
self.remote_field.related_query_name # type: ignore[attr-defined]
|
417
|
+
or self.remote_field.related_name # type: ignore[attr-defined]
|
418
|
+
or self.opts.model_name # type: ignore[attr-defined]
|
401
419
|
)
|
402
420
|
|
403
421
|
@property
|
404
|
-
def target_field(self):
|
422
|
+
def target_field(self) -> Field:
|
405
423
|
"""
|
406
424
|
When filtering against this relation, return the field on the remote
|
407
425
|
model against which the filtering should happen.
|
408
426
|
"""
|
409
|
-
target_fields = self.path_infos[-1].target_fields
|
427
|
+
target_fields = self.path_infos[-1].target_fields # type: ignore[attr-defined]
|
410
428
|
if len(target_fields) > 1:
|
411
429
|
raise FieldError(
|
412
430
|
"The relation has multiple target fields, but only single target field "
|
@@ -414,8 +432,8 @@ class RelatedField(FieldCacheMixin, Field):
|
|
414
432
|
)
|
415
433
|
return target_fields[0]
|
416
434
|
|
417
|
-
def get_cache_name(self):
|
418
|
-
return self.name
|
435
|
+
def get_cache_name(self) -> str:
|
436
|
+
return self.name # type: ignore[return-value]
|
419
437
|
|
420
438
|
|
421
439
|
class ForeignKey(RelatedField):
|
@@ -442,17 +460,17 @@ class ForeignKey(RelatedField):
|
|
442
460
|
|
443
461
|
def __init__(
|
444
462
|
self,
|
445
|
-
to,
|
446
|
-
on_delete,
|
447
|
-
related_name=None,
|
448
|
-
related_query_name=None,
|
449
|
-
limit_choices_to=None,
|
450
|
-
db_index=True,
|
451
|
-
db_constraint=True,
|
452
|
-
**kwargs,
|
463
|
+
to: type[Model] | str,
|
464
|
+
on_delete: Any,
|
465
|
+
related_name: str | None = None,
|
466
|
+
related_query_name: str | None = None,
|
467
|
+
limit_choices_to: Any = None,
|
468
|
+
db_index: bool = True,
|
469
|
+
db_constraint: bool = True,
|
470
|
+
**kwargs: Any,
|
453
471
|
):
|
454
472
|
try:
|
455
|
-
to._meta.model_name
|
473
|
+
to._meta.model_name # type: ignore[attr-defined]
|
456
474
|
except AttributeError:
|
457
475
|
if not isinstance(to, str):
|
458
476
|
raise TypeError(
|
@@ -481,55 +499,57 @@ class ForeignKey(RelatedField):
|
|
481
499
|
self.db_index = db_index
|
482
500
|
self.db_constraint = db_constraint
|
483
501
|
|
484
|
-
def __copy__(self):
|
485
|
-
obj = super().__copy__()
|
502
|
+
def __copy__(self) -> ForeignKey:
|
503
|
+
obj = super().__copy__() # type: ignore[misc]
|
486
504
|
# Remove any cached PathInfo values.
|
487
505
|
obj.__dict__.pop("path_infos", None)
|
488
506
|
obj.__dict__.pop("reverse_path_infos", None)
|
489
507
|
return obj
|
490
508
|
|
491
509
|
@cached_property
|
492
|
-
def related_fields(self):
|
510
|
+
def related_fields(self) -> list[tuple[Field, Field]]:
|
493
511
|
return self.resolve_related_fields()
|
494
512
|
|
495
513
|
@cached_property
|
496
|
-
def reverse_related_fields(self):
|
514
|
+
def reverse_related_fields(self) -> list[tuple[Field, Field]]:
|
497
515
|
return [(rhs_field, lhs_field) for lhs_field, rhs_field in self.related_fields]
|
498
516
|
|
499
517
|
@cached_property
|
500
|
-
def local_related_fields(self):
|
518
|
+
def local_related_fields(self) -> tuple[Field, ...]:
|
501
519
|
return tuple(lhs_field for lhs_field, rhs_field in self.related_fields)
|
502
520
|
|
503
521
|
@cached_property
|
504
|
-
def foreign_related_fields(self):
|
522
|
+
def foreign_related_fields(self) -> tuple[Field, ...]:
|
505
523
|
return tuple(
|
506
524
|
rhs_field for lhs_field, rhs_field in self.related_fields if rhs_field
|
507
525
|
)
|
508
526
|
|
509
|
-
def get_local_related_value(self, instance):
|
527
|
+
def get_local_related_value(self, instance: Model) -> tuple[Any, ...]:
|
510
528
|
# Always returns the value of the single local field
|
511
529
|
field = self.local_related_fields[0]
|
512
|
-
if field.primary_key:
|
530
|
+
if field.primary_key: # type: ignore[attr-defined]
|
513
531
|
return (instance.id,)
|
514
|
-
return (getattr(instance, field.attname),)
|
532
|
+
return (getattr(instance, field.attname),) # type: ignore[attr-defined]
|
515
533
|
|
516
|
-
def get_foreign_related_value(self, instance):
|
534
|
+
def get_foreign_related_value(self, instance: Model) -> tuple[Any, ...]:
|
517
535
|
# Always returns the id of the foreign instance
|
518
536
|
return (instance.id,)
|
519
537
|
|
520
|
-
def get_joining_columns(
|
538
|
+
def get_joining_columns(
|
539
|
+
self, reverse_join: bool = False
|
540
|
+
) -> tuple[tuple[str, str], ...]:
|
521
541
|
# Always returns a single column pair
|
522
542
|
if reverse_join:
|
523
543
|
from_field, to_field = self.related_fields[0]
|
524
|
-
return ((to_field.column, from_field.column),)
|
544
|
+
return ((to_field.column, from_field.column),) # type: ignore[attr-defined]
|
525
545
|
else:
|
526
546
|
from_field, to_field = self.related_fields[0]
|
527
|
-
return ((from_field.column, to_field.column),)
|
547
|
+
return ((from_field.column, to_field.column),) # type: ignore[attr-defined]
|
528
548
|
|
529
|
-
def get_reverse_joining_columns(self):
|
549
|
+
def get_reverse_joining_columns(self) -> tuple[tuple[str, str], ...]:
|
530
550
|
return self.get_joining_columns(reverse_join=True)
|
531
551
|
|
532
|
-
def get_extra_restriction(self, alias, related_alias):
|
552
|
+
def get_extra_restriction(self, alias: str, related_alias: str) -> None:
|
533
553
|
"""
|
534
554
|
Return a pair condition used for joining and subquery pushdown. The
|
535
555
|
condition is something that responds to as_sql(compiler, connection)
|
@@ -543,10 +563,10 @@ class ForeignKey(RelatedField):
|
|
543
563
|
"""
|
544
564
|
return None
|
545
565
|
|
546
|
-
def get_path_info(self, filtered_relation=None):
|
566
|
+
def get_path_info(self, filtered_relation: Any = None) -> list[PathInfo]:
|
547
567
|
"""Get path from this field to the related model."""
|
548
|
-
opts = self.remote_field.model._meta
|
549
|
-
from_opts = self.model._meta
|
568
|
+
opts = self.remote_field.model._meta # type: ignore[attr-defined]
|
569
|
+
from_opts = self.model._meta # type: ignore[attr-defined]
|
550
570
|
return [
|
551
571
|
PathInfo(
|
552
572
|
from_opts=from_opts,
|
@@ -560,58 +580,58 @@ class ForeignKey(RelatedField):
|
|
560
580
|
]
|
561
581
|
|
562
582
|
@cached_property
|
563
|
-
def path_infos(self):
|
583
|
+
def path_infos(self) -> list[PathInfo]:
|
564
584
|
return self.get_path_info()
|
565
585
|
|
566
|
-
def get_reverse_path_info(self, filtered_relation=None):
|
586
|
+
def get_reverse_path_info(self, filtered_relation: Any = None) -> list[PathInfo]:
|
567
587
|
"""Get path from the related model to this field's model."""
|
568
|
-
opts = self.model._meta
|
569
|
-
from_opts = self.remote_field.model._meta
|
588
|
+
opts = self.model._meta # type: ignore[attr-defined]
|
589
|
+
from_opts = self.remote_field.model._meta # type: ignore[attr-defined]
|
570
590
|
return [
|
571
591
|
PathInfo(
|
572
592
|
from_opts=from_opts,
|
573
593
|
to_opts=opts,
|
574
594
|
target_fields=(opts.get_field("id"),),
|
575
|
-
join_field=self.remote_field,
|
576
|
-
m2m=not self.primary_key,
|
595
|
+
join_field=self.remote_field, # type: ignore[attr-defined]
|
596
|
+
m2m=not self.primary_key, # type: ignore[attr-defined]
|
577
597
|
direct=False,
|
578
598
|
filtered_relation=filtered_relation,
|
579
599
|
)
|
580
600
|
]
|
581
601
|
|
582
602
|
@cached_property
|
583
|
-
def reverse_path_infos(self):
|
603
|
+
def reverse_path_infos(self) -> list[PathInfo]:
|
584
604
|
return self.get_reverse_path_info()
|
585
605
|
|
586
|
-
def contribute_to_class(self, cls, name):
|
606
|
+
def contribute_to_class(self, cls: type[Model], name: str) -> None:
|
587
607
|
super().contribute_to_class(cls, name)
|
588
|
-
setattr(cls, self.name, self.forward_related_accessor_class(self))
|
608
|
+
setattr(cls, self.name, self.forward_related_accessor_class(self)) # type: ignore[attr-defined]
|
589
609
|
|
590
|
-
def contribute_to_related_class(self, cls, related):
|
610
|
+
def contribute_to_related_class(self, cls: type[Model], related: Any) -> None:
|
591
611
|
# Internal FK's - i.e., those with a related name ending with '+'
|
592
|
-
if not self.remote_field.is_hidden():
|
612
|
+
if not self.remote_field.is_hidden(): # type: ignore[attr-defined]
|
593
613
|
setattr(
|
594
614
|
cls,
|
595
615
|
related.get_accessor_name(),
|
596
|
-
self.related_accessor_class(related),
|
616
|
+
self.related_accessor_class(related), # type: ignore[attr-defined]
|
597
617
|
)
|
598
618
|
# While 'limit_choices_to' might be a callable, simply pass
|
599
619
|
# it along for later - this is too early because it's still
|
600
620
|
# model load time.
|
601
|
-
if self.remote_field.limit_choices_to:
|
621
|
+
if self.remote_field.limit_choices_to: # type: ignore[attr-defined]
|
602
622
|
cls._meta.related_fkey_lookups.append(
|
603
|
-
self.remote_field.limit_choices_to
|
623
|
+
self.remote_field.limit_choices_to # type: ignore[attr-defined]
|
604
624
|
)
|
605
625
|
|
606
|
-
def preflight(self, **kwargs):
|
626
|
+
def preflight(self, **kwargs: Any) -> list[PreflightResult]: # type: ignore[misc]
|
607
627
|
return [
|
608
|
-
*super().preflight(**kwargs),
|
628
|
+
*super().preflight(**kwargs), # type: ignore[misc]
|
609
629
|
*self._check_on_delete(),
|
610
630
|
]
|
611
631
|
|
612
|
-
def _check_on_delete(self):
|
613
|
-
on_delete = getattr(self.remote_field, "on_delete", None)
|
614
|
-
if on_delete == SET_NULL and not self.allow_null:
|
632
|
+
def _check_on_delete(self) -> list[PreflightResult]:
|
633
|
+
on_delete = getattr(self.remote_field, "on_delete", None) # type: ignore[attr-defined]
|
634
|
+
if on_delete == SET_NULL and not self.allow_null: # type: ignore[attr-defined]
|
615
635
|
return [
|
616
636
|
PreflightResult(
|
617
637
|
fix=(
|
@@ -622,7 +642,7 @@ class ForeignKey(RelatedField):
|
|
622
642
|
id="fields.foreign_key_null_constraint_violation",
|
623
643
|
)
|
624
644
|
]
|
625
|
-
elif on_delete == SET_DEFAULT and not self.has_default():
|
645
|
+
elif on_delete == SET_DEFAULT and not self.has_default(): # type: ignore[attr-defined]
|
626
646
|
return [
|
627
647
|
PreflightResult(
|
628
648
|
fix=(
|
@@ -636,20 +656,20 @@ class ForeignKey(RelatedField):
|
|
636
656
|
else:
|
637
657
|
return []
|
638
658
|
|
639
|
-
def deconstruct(self):
|
659
|
+
def deconstruct(self) -> tuple[str, str, list[Any], dict[str, Any]]:
|
640
660
|
name, path, args, kwargs = super().deconstruct()
|
641
|
-
kwargs["on_delete"] = self.remote_field.on_delete
|
661
|
+
kwargs["on_delete"] = self.remote_field.on_delete # type: ignore[attr-defined]
|
642
662
|
|
643
|
-
if isinstance(self.remote_field.model, SettingsReference):
|
644
|
-
kwargs["to"] = self.remote_field.model
|
645
|
-
elif isinstance(self.remote_field.model, str):
|
646
|
-
if "." in self.remote_field.model:
|
647
|
-
package_label, model_name = self.remote_field.model.split(".")
|
663
|
+
if isinstance(self.remote_field.model, SettingsReference): # type: ignore[attr-defined]
|
664
|
+
kwargs["to"] = self.remote_field.model # type: ignore[attr-defined]
|
665
|
+
elif isinstance(self.remote_field.model, str): # type: ignore[attr-defined]
|
666
|
+
if "." in self.remote_field.model: # type: ignore[attr-defined]
|
667
|
+
package_label, model_name = self.remote_field.model.split(".") # type: ignore[attr-defined]
|
648
668
|
kwargs["to"] = f"{package_label}.{model_name.lower()}"
|
649
669
|
else:
|
650
|
-
kwargs["to"] = self.remote_field.model.lower()
|
670
|
+
kwargs["to"] = self.remote_field.model.lower() # type: ignore[attr-defined]
|
651
671
|
else:
|
652
|
-
kwargs["to"] = self.remote_field.model._meta.label_lower
|
672
|
+
kwargs["to"] = self.remote_field.model._meta.label_lower # type: ignore[attr-defined]
|
653
673
|
|
654
674
|
if self.db_index is not True:
|
655
675
|
kwargs["db_index"] = self.db_index
|
@@ -659,105 +679,107 @@ class ForeignKey(RelatedField):
|
|
659
679
|
|
660
680
|
return name, path, args, kwargs
|
661
681
|
|
662
|
-
def to_python(self, value):
|
663
|
-
return self.target_field.to_python(value)
|
682
|
+
def to_python(self, value: Any) -> Any:
|
683
|
+
return self.target_field.to_python(value) # type: ignore[attr-defined]
|
664
684
|
|
665
685
|
@property
|
666
|
-
def target_field(self):
|
686
|
+
def target_field(self) -> Field:
|
667
687
|
return self.foreign_related_fields[0]
|
668
688
|
|
669
|
-
def validate(self, value, model_instance):
|
670
|
-
super().validate(value, model_instance)
|
689
|
+
def validate(self, value: Any, model_instance: Model) -> None:
|
690
|
+
super().validate(value, model_instance) # type: ignore[misc]
|
671
691
|
if value is None:
|
672
|
-
return
|
692
|
+
return None
|
673
693
|
|
674
|
-
qs = self.remote_field.model._meta.base_queryset.filter(
|
675
|
-
**{self.remote_field.field_name: value}
|
694
|
+
qs = self.remote_field.model._meta.base_queryset.filter( # type: ignore[attr-defined]
|
695
|
+
**{self.remote_field.field_name: value} # type: ignore[attr-defined]
|
676
696
|
)
|
677
697
|
qs = qs.complex_filter(self.get_limit_choices_to())
|
678
698
|
if not qs.exists():
|
679
699
|
raise exceptions.ValidationError(
|
680
|
-
self.error_messages["invalid"],
|
700
|
+
self.error_messages["invalid"], # type: ignore[attr-defined]
|
681
701
|
code="invalid",
|
682
702
|
params={
|
683
|
-
"model": self.remote_field.model._meta.model_name,
|
703
|
+
"model": self.remote_field.model._meta.model_name, # type: ignore[attr-defined]
|
684
704
|
"id": value,
|
685
|
-
"field": self.remote_field.field_name,
|
705
|
+
"field": self.remote_field.field_name, # type: ignore[attr-defined]
|
686
706
|
"value": value,
|
687
707
|
},
|
688
708
|
)
|
689
709
|
|
690
|
-
def resolve_related_fields(self):
|
691
|
-
if isinstance(self.remote_field.model, str):
|
710
|
+
def resolve_related_fields(self) -> list[tuple[ForeignKey, Field]]:
|
711
|
+
if isinstance(self.remote_field.model, str): # type: ignore[attr-defined]
|
692
712
|
raise ValueError(
|
693
|
-
f"Related model {self.remote_field.model!r} cannot be resolved"
|
713
|
+
f"Related model {self.remote_field.model!r} cannot be resolved" # type: ignore[attr-defined]
|
694
714
|
)
|
695
715
|
from_field = self
|
696
|
-
to_field = self.remote_field.model._meta.get_field("id")
|
697
|
-
related_fields = [(from_field, to_field)]
|
716
|
+
to_field = self.remote_field.model._meta.get_field("id") # type: ignore[attr-defined]
|
717
|
+
related_fields: list[tuple[ForeignKey, Field]] = [(from_field, to_field)]
|
698
718
|
|
699
719
|
for from_field, to_field in related_fields:
|
700
|
-
if to_field and to_field.model != self.remote_field.model:
|
720
|
+
if to_field and to_field.model != self.remote_field.model: # type: ignore[attr-defined]
|
701
721
|
raise FieldError(
|
702
|
-
f"'{self.model._meta.label}.{self.name}' refers to field '{to_field.name}' which is not local to model "
|
703
|
-
f"'{self.remote_field.model._meta.label}'."
|
722
|
+
f"'{self.model._meta.label}.{self.name}' refers to field '{to_field.name}' which is not local to model " # type: ignore[attr-defined]
|
723
|
+
f"'{self.remote_field.model._meta.label}'." # type: ignore[attr-defined]
|
704
724
|
)
|
705
725
|
return related_fields
|
706
726
|
|
707
|
-
def get_attname(self):
|
727
|
+
def get_attname(self) -> str:
|
708
728
|
return f"{self.name}_id"
|
709
729
|
|
710
|
-
def get_attname_column(self):
|
730
|
+
def get_attname_column(self) -> tuple[str, str]:
|
711
731
|
attname = self.get_attname()
|
712
|
-
column = self.db_column or attname
|
732
|
+
column = self.db_column or attname # type: ignore[attr-defined]
|
713
733
|
return attname, column
|
714
734
|
|
715
|
-
def get_default(self):
|
735
|
+
def get_default(self) -> Any:
|
716
736
|
"""Return the to_field if the default value is an object."""
|
717
|
-
field_default = super().get_default()
|
718
|
-
if isinstance(field_default, self.remote_field.model):
|
719
|
-
return getattr(field_default, self.target_field.attname)
|
737
|
+
field_default = super().get_default() # type: ignore[misc]
|
738
|
+
if isinstance(field_default, self.remote_field.model): # type: ignore[attr-defined]
|
739
|
+
return getattr(field_default, self.target_field.attname) # type: ignore[attr-defined]
|
720
740
|
return field_default
|
721
741
|
|
722
|
-
def get_db_prep_save(self, value, connection):
|
742
|
+
def get_db_prep_save(self, value: Any, connection: BaseDatabaseWrapper) -> Any:
|
723
743
|
if value is None or (
|
724
|
-
value == "" and not self.target_field.empty_strings_allowed
|
744
|
+
value == "" and not self.target_field.empty_strings_allowed # type: ignore[attr-defined]
|
725
745
|
):
|
726
746
|
return None
|
727
747
|
else:
|
728
|
-
return self.target_field.get_db_prep_save(value, connection=connection)
|
748
|
+
return self.target_field.get_db_prep_save(value, connection=connection) # type: ignore[attr-defined]
|
729
749
|
|
730
|
-
def get_db_prep_value(
|
731
|
-
|
750
|
+
def get_db_prep_value(
|
751
|
+
self, value: Any, connection: BaseDatabaseWrapper, prepared: bool = False
|
752
|
+
) -> Any:
|
753
|
+
return self.target_field.get_db_prep_value(value, connection, prepared) # type: ignore[attr-defined]
|
732
754
|
|
733
|
-
def get_prep_value(self, value):
|
734
|
-
return self.target_field.get_prep_value(value)
|
755
|
+
def get_prep_value(self, value: Any) -> Any:
|
756
|
+
return self.target_field.get_prep_value(value) # type: ignore[attr-defined]
|
735
757
|
|
736
|
-
def db_check(self, connection):
|
758
|
+
def db_check(self, connection: BaseDatabaseWrapper) -> None:
|
737
759
|
return None
|
738
760
|
|
739
|
-
def db_type(self, connection):
|
740
|
-
return self.target_field.rel_db_type(connection=connection)
|
761
|
+
def db_type(self, connection: BaseDatabaseWrapper) -> str | None:
|
762
|
+
return self.target_field.rel_db_type(connection=connection) # type: ignore[attr-defined]
|
741
763
|
|
742
|
-
def cast_db_type(self, connection):
|
743
|
-
return self.target_field.cast_db_type(connection=connection)
|
764
|
+
def cast_db_type(self, connection: BaseDatabaseWrapper) -> str | None:
|
765
|
+
return self.target_field.cast_db_type(connection=connection) # type: ignore[attr-defined]
|
744
766
|
|
745
|
-
def db_parameters(self, connection):
|
746
|
-
target_db_parameters = self.target_field.db_parameters(connection)
|
767
|
+
def db_parameters(self, connection: BaseDatabaseWrapper) -> dict[str, Any]:
|
768
|
+
target_db_parameters = self.target_field.db_parameters(connection) # type: ignore[attr-defined]
|
747
769
|
return {
|
748
770
|
"type": self.db_type(connection),
|
749
771
|
"check": self.db_check(connection),
|
750
772
|
"collation": target_db_parameters.get("collation"),
|
751
773
|
}
|
752
774
|
|
753
|
-
def get_col(self, alias, output_field=None):
|
775
|
+
def get_col(self, alias: str, output_field: Field | None = None) -> Any:
|
754
776
|
if output_field is None:
|
755
777
|
output_field = self.target_field
|
756
778
|
while isinstance(output_field, ForeignKey):
|
757
779
|
output_field = output_field.target_field
|
758
780
|
if output_field is self:
|
759
781
|
raise ValueError("Cannot resolve output_field.")
|
760
|
-
return super().get_col(alias, output_field)
|
782
|
+
return super().get_col(alias, output_field) # type: ignore[misc]
|
761
783
|
|
762
784
|
|
763
785
|
# Register lookups for ForeignKey
|
@@ -791,18 +813,18 @@ class ManyToManyField(RelatedField):
|
|
791
813
|
|
792
814
|
def __init__(
|
793
815
|
self,
|
794
|
-
to,
|
816
|
+
to: type[Model] | str,
|
795
817
|
*,
|
796
|
-
through,
|
797
|
-
through_fields=None,
|
798
|
-
related_name=None,
|
799
|
-
related_query_name=None,
|
800
|
-
limit_choices_to=None,
|
801
|
-
symmetrical=None,
|
802
|
-
**kwargs,
|
818
|
+
through: type[Model] | str,
|
819
|
+
through_fields: tuple[str, str] | None = None,
|
820
|
+
related_name: str | None = None,
|
821
|
+
related_query_name: str | None = None,
|
822
|
+
limit_choices_to: Any = None,
|
823
|
+
symmetrical: bool | None = None,
|
824
|
+
**kwargs: Any,
|
803
825
|
):
|
804
826
|
try:
|
805
|
-
to._meta
|
827
|
+
to._meta # type: ignore[attr-defined]
|
806
828
|
except AttributeError:
|
807
829
|
if not isinstance(to, str):
|
808
830
|
raise TypeError(
|
@@ -835,18 +857,18 @@ class ManyToManyField(RelatedField):
|
|
835
857
|
**kwargs,
|
836
858
|
)
|
837
859
|
|
838
|
-
def preflight(self, **kwargs):
|
860
|
+
def preflight(self, **kwargs: Any) -> list[PreflightResult]: # type: ignore[misc]
|
839
861
|
return [
|
840
|
-
*super().preflight(**kwargs),
|
862
|
+
*super().preflight(**kwargs), # type: ignore[misc]
|
841
863
|
*self._check_relationship_model(**kwargs),
|
842
864
|
*self._check_ignored_options(**kwargs),
|
843
865
|
*self._check_table_uniqueness(**kwargs),
|
844
866
|
]
|
845
867
|
|
846
|
-
def _check_ignored_options(self, **kwargs):
|
847
|
-
warnings = []
|
868
|
+
def _check_ignored_options(self, **kwargs: Any) -> list[PreflightResult]:
|
869
|
+
warnings: list[PreflightResult] = []
|
848
870
|
|
849
|
-
if self.has_null_arg:
|
871
|
+
if self.has_null_arg: # type: ignore[attr-defined]
|
850
872
|
warnings.append(
|
851
873
|
PreflightResult(
|
852
874
|
fix="The 'null' option has no effect on ManyToManyField. Remove the 'null' argument.",
|
@@ -865,7 +887,7 @@ class ManyToManyField(RelatedField):
|
|
865
887
|
warning=True,
|
866
888
|
)
|
867
889
|
)
|
868
|
-
if self.remote_field.symmetrical and self._related_name:
|
890
|
+
if self.remote_field.symmetrical and self._related_name: # type: ignore[attr-defined]
|
869
891
|
warnings.append(
|
870
892
|
PreflightResult(
|
871
893
|
fix=(
|
@@ -878,7 +900,7 @@ class ManyToManyField(RelatedField):
|
|
878
900
|
warning=True,
|
879
901
|
)
|
880
902
|
)
|
881
|
-
if self.db_comment:
|
903
|
+
if self.db_comment: # type: ignore[attr-defined]
|
882
904
|
warnings.append(
|
883
905
|
PreflightResult(
|
884
906
|
fix="The 'db_comment' option has no effect on ManyToManyField. Remove the 'db_comment' argument.",
|
@@ -890,7 +912,9 @@ class ManyToManyField(RelatedField):
|
|
890
912
|
|
891
913
|
return warnings
|
892
914
|
|
893
|
-
def _check_relationship_model(
|
915
|
+
def _check_relationship_model(
|
916
|
+
self, from_model: type[Model] | None = None, **kwargs: Any
|
917
|
+
) -> list[PreflightResult]:
|
894
918
|
if hasattr(self.remote_field.through, "_meta"):
|
895
919
|
qualified_model_name = f"{self.remote_field.through._meta.package_label}.{self.remote_field.through.__name__}"
|
896
920
|
else:
|
@@ -1097,18 +1121,18 @@ class ManyToManyField(RelatedField):
|
|
1097
1121
|
|
1098
1122
|
return errors
|
1099
1123
|
|
1100
|
-
def _check_table_uniqueness(self, **kwargs):
|
1101
|
-
if isinstance(self.remote_field.through, str):
|
1124
|
+
def _check_table_uniqueness(self, **kwargs: Any) -> list[PreflightResult]:
|
1125
|
+
if isinstance(self.remote_field.through, str): # type: ignore[attr-defined]
|
1102
1126
|
return []
|
1103
1127
|
registered_tables = {
|
1104
1128
|
model._meta.db_table: model
|
1105
|
-
for model in self.opts.models_registry.get_models()
|
1106
|
-
if model != self.remote_field.through
|
1129
|
+
for model in self.opts.models_registry.get_models() # type: ignore[attr-defined]
|
1130
|
+
if model != self.remote_field.through # type: ignore[attr-defined]
|
1107
1131
|
}
|
1108
|
-
m2m_db_table = self.m2m_db_table()
|
1132
|
+
m2m_db_table = self.m2m_db_table() # type: ignore[attr-defined]
|
1109
1133
|
model = registered_tables.get(m2m_db_table)
|
1110
1134
|
# Check if there's already a m2m field using the same through model.
|
1111
|
-
if model and model != self.remote_field.through:
|
1135
|
+
if model and model != self.remote_field.through: # type: ignore[attr-defined]
|
1112
1136
|
clashing_obj = model._meta.label
|
1113
1137
|
return [
|
1114
1138
|
PreflightResult(
|
@@ -1123,71 +1147,73 @@ class ManyToManyField(RelatedField):
|
|
1123
1147
|
]
|
1124
1148
|
return []
|
1125
1149
|
|
1126
|
-
def deconstruct(self):
|
1150
|
+
def deconstruct(self) -> tuple[str, str, list[Any], dict[str, Any]]:
|
1127
1151
|
name, path, args, kwargs = super().deconstruct()
|
1128
1152
|
|
1129
|
-
if self.remote_field.db_constraint is not True:
|
1130
|
-
kwargs["db_constraint"] = self.remote_field.db_constraint
|
1153
|
+
if self.remote_field.db_constraint is not True: # type: ignore[attr-defined]
|
1154
|
+
kwargs["db_constraint"] = self.remote_field.db_constraint # type: ignore[attr-defined]
|
1131
1155
|
|
1132
1156
|
# Lowercase model names as they should be treated as case-insensitive.
|
1133
|
-
if isinstance(self.remote_field.model, str):
|
1134
|
-
if "." in self.remote_field.model:
|
1135
|
-
package_label, model_name = self.remote_field.model.split(".")
|
1157
|
+
if isinstance(self.remote_field.model, str): # type: ignore[attr-defined]
|
1158
|
+
if "." in self.remote_field.model: # type: ignore[attr-defined]
|
1159
|
+
package_label, model_name = self.remote_field.model.split(".") # type: ignore[attr-defined]
|
1136
1160
|
kwargs["to"] = f"{package_label}.{model_name.lower()}"
|
1137
1161
|
else:
|
1138
|
-
kwargs["to"] = self.remote_field.model.lower()
|
1162
|
+
kwargs["to"] = self.remote_field.model.lower() # type: ignore[attr-defined]
|
1139
1163
|
else:
|
1140
|
-
kwargs["to"] = self.remote_field.model._meta.label_lower
|
1164
|
+
kwargs["to"] = self.remote_field.model._meta.label_lower # type: ignore[attr-defined]
|
1141
1165
|
|
1142
|
-
if isinstance(self.remote_field.through, str):
|
1143
|
-
kwargs["through"] = self.remote_field.through
|
1166
|
+
if isinstance(self.remote_field.through, str): # type: ignore[attr-defined]
|
1167
|
+
kwargs["through"] = self.remote_field.through # type: ignore[attr-defined]
|
1144
1168
|
else:
|
1145
|
-
kwargs["through"] = self.remote_field.through._meta.label
|
1169
|
+
kwargs["through"] = self.remote_field.through._meta.label # type: ignore[attr-defined]
|
1146
1170
|
|
1147
1171
|
return name, path, args, kwargs
|
1148
1172
|
|
1149
|
-
def _get_path_info(
|
1173
|
+
def _get_path_info(
|
1174
|
+
self, direct: bool = False, filtered_relation: Any = None
|
1175
|
+
) -> list[PathInfo]:
|
1150
1176
|
"""Called by both direct and indirect m2m traversal."""
|
1151
|
-
int_model = self.remote_field.through
|
1152
|
-
linkfield1 = int_model._meta.get_field(self.m2m_field_name())
|
1153
|
-
linkfield2 = int_model._meta.get_field(self.m2m_reverse_field_name())
|
1177
|
+
int_model = self.remote_field.through # type: ignore[attr-defined]
|
1178
|
+
linkfield1 = int_model._meta.get_field(self.m2m_field_name()) # type: ignore[attr-defined]
|
1179
|
+
linkfield2 = int_model._meta.get_field(self.m2m_reverse_field_name()) # type: ignore[attr-defined]
|
1154
1180
|
if direct:
|
1155
|
-
join1infos = linkfield1.reverse_path_infos
|
1181
|
+
join1infos = linkfield1.reverse_path_infos # type: ignore[attr-defined]
|
1156
1182
|
if filtered_relation:
|
1157
|
-
join2infos = linkfield2.get_path_info(filtered_relation)
|
1183
|
+
join2infos = linkfield2.get_path_info(filtered_relation) # type: ignore[attr-defined]
|
1158
1184
|
else:
|
1159
|
-
join2infos = linkfield2.path_infos
|
1185
|
+
join2infos = linkfield2.path_infos # type: ignore[attr-defined]
|
1160
1186
|
else:
|
1161
|
-
join1infos = linkfield2.reverse_path_infos
|
1187
|
+
join1infos = linkfield2.reverse_path_infos # type: ignore[attr-defined]
|
1162
1188
|
if filtered_relation:
|
1163
|
-
join2infos = linkfield1.get_path_info(filtered_relation)
|
1189
|
+
join2infos = linkfield1.get_path_info(filtered_relation) # type: ignore[attr-defined]
|
1164
1190
|
else:
|
1165
|
-
join2infos = linkfield1.path_infos
|
1191
|
+
join2infos = linkfield1.path_infos # type: ignore[attr-defined]
|
1166
1192
|
|
1167
1193
|
return [*join1infos, *join2infos]
|
1168
1194
|
|
1169
|
-
def get_path_info(self, filtered_relation=None):
|
1195
|
+
def get_path_info(self, filtered_relation: Any = None) -> list[PathInfo]:
|
1170
1196
|
return self._get_path_info(direct=True, filtered_relation=filtered_relation)
|
1171
1197
|
|
1172
1198
|
@cached_property
|
1173
|
-
def path_infos(self):
|
1199
|
+
def path_infos(self) -> list[PathInfo]:
|
1174
1200
|
return self.get_path_info()
|
1175
1201
|
|
1176
|
-
def get_reverse_path_info(self, filtered_relation=None):
|
1202
|
+
def get_reverse_path_info(self, filtered_relation: Any = None) -> list[PathInfo]:
|
1177
1203
|
return self._get_path_info(direct=False, filtered_relation=filtered_relation)
|
1178
1204
|
|
1179
1205
|
@cached_property
|
1180
|
-
def reverse_path_infos(self):
|
1206
|
+
def reverse_path_infos(self) -> list[PathInfo]:
|
1181
1207
|
return self.get_reverse_path_info()
|
1182
1208
|
|
1183
|
-
def _get_m2m_db_table(self):
|
1209
|
+
def _get_m2m_db_table(self) -> str:
|
1184
1210
|
"""
|
1185
1211
|
Function that can be curried to provide the m2m table name for this
|
1186
1212
|
relation.
|
1187
1213
|
"""
|
1188
|
-
return self.remote_field.through._meta.db_table
|
1214
|
+
return self.remote_field.through._meta.db_table # type: ignore[attr-defined]
|
1189
1215
|
|
1190
|
-
def _get_m2m_attr(self, related, attr):
|
1216
|
+
def _get_m2m_attr(self, related: Any, attr: str) -> Any:
|
1191
1217
|
"""
|
1192
1218
|
Function that can be curried to provide the source accessor or DB
|
1193
1219
|
column name for the m2m table.
|
@@ -1195,20 +1221,21 @@ class ManyToManyField(RelatedField):
|
|
1195
1221
|
cache_attr = f"_m2m_{attr}_cache"
|
1196
1222
|
if hasattr(self, cache_attr):
|
1197
1223
|
return getattr(self, cache_attr)
|
1198
|
-
if self.remote_field.through_fields is not None:
|
1199
|
-
link_field_name = self.remote_field.through_fields[0]
|
1224
|
+
if self.remote_field.through_fields is not None: # type: ignore[attr-defined]
|
1225
|
+
link_field_name: str | None = self.remote_field.through_fields[0] # type: ignore[attr-defined]
|
1200
1226
|
else:
|
1201
1227
|
link_field_name = None
|
1202
|
-
for f in self.remote_field.through._meta.fields:
|
1228
|
+
for f in self.remote_field.through._meta.fields: # type: ignore[attr-defined]
|
1203
1229
|
if (
|
1204
|
-
f.is_relation
|
1205
|
-
and f.remote_field.model == related.related_model
|
1230
|
+
f.is_relation # type: ignore[attr-defined]
|
1231
|
+
and f.remote_field.model == related.related_model # type: ignore[attr-defined]
|
1206
1232
|
and (link_field_name is None or link_field_name == f.name)
|
1207
1233
|
):
|
1208
1234
|
setattr(self, cache_attr, getattr(f, attr))
|
1209
1235
|
return getattr(self, cache_attr)
|
1236
|
+
return None
|
1210
1237
|
|
1211
|
-
def _get_m2m_reverse_attr(self, related, attr):
|
1238
|
+
def _get_m2m_reverse_attr(self, related: Any, attr: str) -> Any:
|
1212
1239
|
"""
|
1213
1240
|
Function that can be curried to provide the related accessor or DB
|
1214
1241
|
column name for the m2m table.
|
@@ -1217,12 +1244,12 @@ class ManyToManyField(RelatedField):
|
|
1217
1244
|
if hasattr(self, cache_attr):
|
1218
1245
|
return getattr(self, cache_attr)
|
1219
1246
|
found = False
|
1220
|
-
if self.remote_field.through_fields is not None:
|
1221
|
-
link_field_name = self.remote_field.through_fields[1]
|
1247
|
+
if self.remote_field.through_fields is not None: # type: ignore[attr-defined]
|
1248
|
+
link_field_name: str | None = self.remote_field.through_fields[1] # type: ignore[attr-defined]
|
1222
1249
|
else:
|
1223
1250
|
link_field_name = None
|
1224
|
-
for f in self.remote_field.through._meta.fields:
|
1225
|
-
if f.is_relation and f.remote_field.model == related.model:
|
1251
|
+
for f in self.remote_field.through._meta.fields: # type: ignore[attr-defined]
|
1252
|
+
if f.is_relation and f.remote_field.model == related.model: # type: ignore[attr-defined]
|
1226
1253
|
if link_field_name is None and related.related_model == related.model:
|
1227
1254
|
# If this is an m2m-intermediate to self,
|
1228
1255
|
# the first foreign key you find will be
|
@@ -1238,64 +1265,69 @@ class ManyToManyField(RelatedField):
|
|
1238
1265
|
break
|
1239
1266
|
return getattr(self, cache_attr)
|
1240
1267
|
|
1241
|
-
def contribute_to_class(self, cls, name):
|
1268
|
+
def contribute_to_class(self, cls: type[Model], name: str) -> None:
|
1242
1269
|
super().contribute_to_class(cls, name)
|
1243
1270
|
|
1244
|
-
def resolve_through_model(
|
1245
|
-
|
1271
|
+
def resolve_through_model(
|
1272
|
+
_: Any, model: type[Model], field: ManyToManyField
|
1273
|
+
) -> None:
|
1274
|
+
field.remote_field.through = model # type: ignore[attr-defined]
|
1246
1275
|
|
1247
1276
|
lazy_related_operation(
|
1248
|
-
resolve_through_model,
|
1277
|
+
resolve_through_model,
|
1278
|
+
cls,
|
1279
|
+
self.remote_field.through,
|
1280
|
+
field=self, # type: ignore[attr-defined]
|
1249
1281
|
)
|
1250
1282
|
|
1251
1283
|
# Add the descriptor for the m2m relation.
|
1252
|
-
setattr(cls, self.name, ForwardManyToManyDescriptor(self.remote_field))
|
1284
|
+
setattr(cls, self.name, ForwardManyToManyDescriptor(self.remote_field)) # type: ignore[attr-defined]
|
1253
1285
|
|
1254
1286
|
# Set up the accessor for the m2m table name for the relation.
|
1255
|
-
self.m2m_db_table = self._get_m2m_db_table
|
1287
|
+
self.m2m_db_table = self._get_m2m_db_table # type: ignore[method-assign]
|
1256
1288
|
|
1257
|
-
def contribute_to_related_class(self, cls, related):
|
1289
|
+
def contribute_to_related_class(self, cls: type[Model], related: Any) -> None:
|
1258
1290
|
# Internal M2Ms (i.e., those with a related name ending with '+')
|
1259
1291
|
# don't get a related descriptor.
|
1260
|
-
if not self.remote_field.is_hidden():
|
1292
|
+
if not self.remote_field.is_hidden(): # type: ignore[attr-defined]
|
1261
1293
|
setattr(
|
1262
1294
|
cls,
|
1263
1295
|
related.get_accessor_name(),
|
1264
|
-
ReverseManyToManyDescriptor(self.remote_field),
|
1296
|
+
ReverseManyToManyDescriptor(self.remote_field), # type: ignore[attr-defined]
|
1265
1297
|
)
|
1266
1298
|
|
1267
1299
|
# Set up the accessors for the column names on the m2m table.
|
1268
|
-
self.m2m_column_name = partial(self._get_m2m_attr, related, "column")
|
1269
|
-
self.m2m_reverse_name = partial(self._get_m2m_reverse_attr, related, "column")
|
1300
|
+
self.m2m_column_name = partial(self._get_m2m_attr, related, "column") # type: ignore[method-assign]
|
1301
|
+
self.m2m_reverse_name = partial(self._get_m2m_reverse_attr, related, "column") # type: ignore[method-assign]
|
1270
1302
|
|
1271
|
-
self.m2m_field_name = partial(self._get_m2m_attr, related, "name")
|
1272
|
-
self.m2m_reverse_field_name = partial(
|
1303
|
+
self.m2m_field_name = partial(self._get_m2m_attr, related, "name") # type: ignore[method-assign]
|
1304
|
+
self.m2m_reverse_field_name = partial( # type: ignore[method-assign]
|
1273
1305
|
self._get_m2m_reverse_attr, related, "name"
|
1274
1306
|
)
|
1275
1307
|
|
1276
1308
|
get_m2m_rel = partial(self._get_m2m_attr, related, "remote_field")
|
1277
|
-
self.m2m_target_field_name = lambda: get_m2m_rel().field_name
|
1309
|
+
self.m2m_target_field_name = lambda: get_m2m_rel().field_name # type: ignore[method-assign,attr-defined]
|
1278
1310
|
get_m2m_reverse_rel = partial(
|
1279
1311
|
self._get_m2m_reverse_attr, related, "remote_field"
|
1280
1312
|
)
|
1281
|
-
self.m2m_reverse_target_field_name = lambda: get_m2m_reverse_rel().field_name
|
1313
|
+
self.m2m_reverse_target_field_name = lambda: get_m2m_reverse_rel().field_name # type: ignore[method-assign,attr-defined]
|
1282
1314
|
|
1283
|
-
def set_attributes_from_rel(self):
|
1315
|
+
def set_attributes_from_rel(self) -> None:
|
1284
1316
|
pass
|
1285
1317
|
|
1286
|
-
def value_from_object(self, obj):
|
1287
|
-
return [] if obj.id is None else list(getattr(obj, self.attname).all())
|
1318
|
+
def value_from_object(self, obj: Model) -> list[Any]:
|
1319
|
+
return [] if obj.id is None else list(getattr(obj, self.attname).all()) # type: ignore[attr-defined]
|
1288
1320
|
|
1289
|
-
def save_form_data(self, instance, data):
|
1290
|
-
getattr(instance, self.attname).set(data)
|
1321
|
+
def save_form_data(self, instance: Model, data: Any) -> None:
|
1322
|
+
getattr(instance, self.attname).set(data) # type: ignore[attr-defined]
|
1291
1323
|
|
1292
|
-
def db_check(self, connection):
|
1324
|
+
def db_check(self, connection: BaseDatabaseWrapper) -> None:
|
1293
1325
|
return None
|
1294
1326
|
|
1295
|
-
def db_type(self, connection):
|
1327
|
+
def db_type(self, connection: BaseDatabaseWrapper) -> None:
|
1296
1328
|
# A ManyToManyField is not represented by a single column,
|
1297
1329
|
# so return None.
|
1298
1330
|
return None
|
1299
1331
|
|
1300
|
-
def db_parameters(self, connection):
|
1332
|
+
def db_parameters(self, connection: BaseDatabaseWrapper) -> dict[str, None]:
|
1301
1333
|
return {"type": None, "check": None}
|