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
@@ -46,7 +46,10 @@ reverse many-to-one relation.
|
|
46
46
|
``ReverseManyToManyDescriptor``, use ``ManyToManyDescriptor`` instead.
|
47
47
|
"""
|
48
48
|
|
49
|
+
from __future__ import annotations
|
50
|
+
|
49
51
|
from functools import cached_property
|
52
|
+
from typing import Any
|
50
53
|
|
51
54
|
from plain.models.query import QuerySet
|
52
55
|
from plain.models.query_utils import DeferredAttribute
|
@@ -60,7 +63,7 @@ from .related_managers import (
|
|
60
63
|
|
61
64
|
|
62
65
|
class ForeignKeyDeferredAttribute(DeferredAttribute):
|
63
|
-
def __set__(self, instance, value):
|
66
|
+
def __set__(self, instance: Any, value: Any) -> None:
|
64
67
|
if instance.__dict__.get(self.field.attname) != value and self.field.is_cached(
|
65
68
|
instance
|
66
69
|
):
|
@@ -80,11 +83,11 @@ class ForwardManyToOneDescriptor:
|
|
80
83
|
``Child.parent`` is a ``ForwardManyToOneDescriptor`` instance.
|
81
84
|
"""
|
82
85
|
|
83
|
-
def __init__(self, field_with_rel):
|
86
|
+
def __init__(self, field_with_rel: Any) -> None:
|
84
87
|
self.field = field_with_rel
|
85
88
|
|
86
89
|
@cached_property
|
87
|
-
def RelatedObjectDoesNotExist(self):
|
90
|
+
def RelatedObjectDoesNotExist(self) -> type:
|
88
91
|
# The exception can't be created at initialization time since the
|
89
92
|
# related model might not be resolved yet; `self.field.model` might
|
90
93
|
# still be a string model reference.
|
@@ -97,14 +100,16 @@ class ForwardManyToOneDescriptor:
|
|
97
100
|
},
|
98
101
|
)
|
99
102
|
|
100
|
-
def is_cached(self, instance):
|
103
|
+
def is_cached(self, instance: Any) -> bool:
|
101
104
|
return self.field.is_cached(instance)
|
102
105
|
|
103
106
|
def get_queryset(self) -> QuerySet:
|
104
107
|
qs = self.field.remote_field.model._meta.base_queryset
|
105
108
|
return qs.all()
|
106
109
|
|
107
|
-
def get_prefetch_queryset(
|
110
|
+
def get_prefetch_queryset(
|
111
|
+
self, instances: list[Any], queryset: QuerySet | None = None
|
112
|
+
) -> tuple[QuerySet, Any, Any, bool, str, bool]:
|
108
113
|
if queryset is None:
|
109
114
|
queryset = self.get_queryset()
|
110
115
|
|
@@ -145,12 +150,14 @@ class ForwardManyToOneDescriptor:
|
|
145
150
|
False,
|
146
151
|
)
|
147
152
|
|
148
|
-
def get_object(self, instance):
|
153
|
+
def get_object(self, instance: Any) -> Any:
|
149
154
|
qs = self.get_queryset()
|
150
155
|
# Assuming the database enforces foreign keys, this won't fail.
|
151
156
|
return qs.get(self.field.get_reverse_related_filter(instance))
|
152
157
|
|
153
|
-
def __get__(
|
158
|
+
def __get__(
|
159
|
+
self, instance: Any | None, cls: type | None = None
|
160
|
+
) -> ForwardManyToOneDescriptor | Any | None:
|
154
161
|
"""
|
155
162
|
Get the related instance through the forward relation.
|
156
163
|
|
@@ -189,7 +196,7 @@ class ForwardManyToOneDescriptor:
|
|
189
196
|
else:
|
190
197
|
return rel_obj
|
191
198
|
|
192
|
-
def __set__(self, instance, value):
|
199
|
+
def __set__(self, instance: Any, value: Any) -> None:
|
193
200
|
"""
|
194
201
|
Set the related instance through the forward relation.
|
195
202
|
|
@@ -250,7 +257,7 @@ class ForwardManyToOneDescriptor:
|
|
250
257
|
if value is not None and not remote_field.multiple:
|
251
258
|
remote_field.set_cached_value(value, instance)
|
252
259
|
|
253
|
-
def __reduce__(self):
|
260
|
+
def __reduce__(self) -> tuple[Any, tuple[Any, str]]:
|
254
261
|
"""
|
255
262
|
Pickling should return the instance attached by self.field on the
|
256
263
|
model, not a new copy of that descriptor. Use getattr() to retrieve
|
@@ -268,11 +275,13 @@ class RelationDescriptorBase:
|
|
268
275
|
this because they allow direct assignment.
|
269
276
|
"""
|
270
277
|
|
271
|
-
def __init__(self, rel):
|
278
|
+
def __init__(self, rel: Any) -> None:
|
272
279
|
self.rel = rel
|
273
280
|
self.field = rel.field
|
274
281
|
|
275
|
-
def __get__(
|
282
|
+
def __get__(
|
283
|
+
self, instance: Any | None, cls: type | None = None
|
284
|
+
) -> RelationDescriptorBase | Any:
|
276
285
|
"""
|
277
286
|
Get the related manager when the descriptor is accessed.
|
278
287
|
|
@@ -282,19 +291,19 @@ class RelationDescriptorBase:
|
|
282
291
|
return self
|
283
292
|
return self.get_related_manager(instance)
|
284
293
|
|
285
|
-
def get_related_manager(self, instance):
|
294
|
+
def get_related_manager(self, instance: Any) -> Any:
|
286
295
|
"""Return the appropriate manager for this relation."""
|
287
296
|
raise NotImplementedError(
|
288
297
|
f"{self.__class__.__name__} must implement get_related_manager()"
|
289
298
|
)
|
290
299
|
|
291
|
-
def _get_set_deprecation_msg_params(self):
|
300
|
+
def _get_set_deprecation_msg_params(self) -> tuple[str, str]:
|
292
301
|
"""Return parameters for the error message when direct assignment is attempted."""
|
293
302
|
raise NotImplementedError(
|
294
303
|
f"{self.__class__.__name__} must implement _get_set_deprecation_msg_params()"
|
295
304
|
)
|
296
305
|
|
297
|
-
def __set__(self, instance, value):
|
306
|
+
def __set__(self, instance: Any, value: Any) -> None:
|
298
307
|
"""Prevent direct assignment to the relation."""
|
299
308
|
raise TypeError(
|
300
309
|
"Direct assignment to the {} is prohibited. Use {}.set() instead.".format(
|
@@ -318,11 +327,11 @@ class ReverseManyToOneDescriptor(RelationDescriptorBase):
|
|
318
327
|
Most of the implementation is delegated to the ReverseManyToOneManager class.
|
319
328
|
"""
|
320
329
|
|
321
|
-
def get_related_manager(self, instance):
|
330
|
+
def get_related_manager(self, instance: Any) -> ReverseManyToOneManager:
|
322
331
|
"""Return the ReverseManyToOneManager for this relation."""
|
323
332
|
return ReverseManyToOneManager(instance, self.rel)
|
324
333
|
|
325
|
-
def _get_set_deprecation_msg_params(self):
|
334
|
+
def _get_set_deprecation_msg_params(self) -> tuple[str, str]:
|
326
335
|
return (
|
327
336
|
"reverse side of a related set",
|
328
337
|
self.rel.get_accessor_name(),
|
@@ -343,17 +352,17 @@ class ForwardManyToManyDescriptor(RelationDescriptorBase):
|
|
343
352
|
"""
|
344
353
|
|
345
354
|
@property
|
346
|
-
def through(self):
|
355
|
+
def through(self) -> Any:
|
347
356
|
# through is provided so that you have easy access to the through
|
348
357
|
# model (Book.authors.through) for inlines, etc. This is done as
|
349
358
|
# a property to ensure that the fully resolved value is returned.
|
350
359
|
return self.rel.through
|
351
360
|
|
352
|
-
def get_related_manager(self, instance):
|
361
|
+
def get_related_manager(self, instance: Any) -> ForwardManyToManyManager:
|
353
362
|
"""Return the ForwardManyToManyManager for this relation."""
|
354
363
|
return ForwardManyToManyManager(instance, self.rel)
|
355
364
|
|
356
|
-
def _get_set_deprecation_msg_params(self):
|
365
|
+
def _get_set_deprecation_msg_params(self) -> tuple[str, str]:
|
357
366
|
return (
|
358
367
|
"forward side of a many-to-many set",
|
359
368
|
self.field.name,
|
@@ -374,17 +383,17 @@ class ReverseManyToManyDescriptor(RelationDescriptorBase):
|
|
374
383
|
"""
|
375
384
|
|
376
385
|
@property
|
377
|
-
def through(self):
|
386
|
+
def through(self) -> Any:
|
378
387
|
# through is provided so that you have easy access to the through
|
379
388
|
# model (Book.authors.through) for inlines, etc. This is done as
|
380
389
|
# a property to ensure that the fully resolved value is returned.
|
381
390
|
return self.rel.through
|
382
391
|
|
383
|
-
def get_related_manager(self, instance):
|
392
|
+
def get_related_manager(self, instance: Any) -> ReverseManyToManyManager:
|
384
393
|
"""Return the ReverseManyToManyManager for this relation."""
|
385
394
|
return ReverseManyToManyManager(instance, self.rel)
|
386
395
|
|
387
|
-
def _get_set_deprecation_msg_params(self):
|
396
|
+
def _get_set_deprecation_msg_params(self) -> tuple[str, str]:
|
388
397
|
return (
|
389
398
|
"reverse side of a many-to-many set",
|
390
399
|
self.rel.get_accessor_name(),
|
@@ -1,3 +1,7 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING, Any
|
4
|
+
|
1
5
|
from plain.models.lookups import (
|
2
6
|
Exact,
|
3
7
|
GreaterThan,
|
@@ -8,12 +12,16 @@ from plain.models.lookups import (
|
|
8
12
|
LessThanOrEqual,
|
9
13
|
)
|
10
14
|
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
from plain.models.backends.base.base import BaseDatabaseWrapper
|
17
|
+
from plain.models.sql.compiler import SQLCompiler
|
18
|
+
|
11
19
|
|
12
20
|
class MultiColSource:
|
13
21
|
contains_aggregate = False
|
14
22
|
contains_over_clause = False
|
15
23
|
|
16
|
-
def __init__(self, alias, targets, sources, field):
|
24
|
+
def __init__(self, alias: str, targets: Any, sources: Any, field: Any) -> None:
|
17
25
|
self.targets, self.sources, self.field, self.alias = (
|
18
26
|
targets,
|
19
27
|
sources,
|
@@ -22,22 +30,22 @@ class MultiColSource:
|
|
22
30
|
)
|
23
31
|
self.output_field = self.field
|
24
32
|
|
25
|
-
def __repr__(self):
|
33
|
+
def __repr__(self) -> str:
|
26
34
|
return f"{self.__class__.__name__}({self.alias}, {self.field})"
|
27
35
|
|
28
|
-
def relabeled_clone(self, relabels):
|
36
|
+
def relabeled_clone(self, relabels: dict[str, str]) -> MultiColSource:
|
29
37
|
return self.__class__(
|
30
38
|
relabels.get(self.alias, self.alias), self.targets, self.sources, self.field
|
31
39
|
)
|
32
40
|
|
33
|
-
def get_lookup(self, lookup):
|
41
|
+
def get_lookup(self, lookup: str) -> Any:
|
34
42
|
return self.output_field.get_lookup(lookup)
|
35
43
|
|
36
|
-
def resolve_expression(self, *args, **kwargs):
|
44
|
+
def resolve_expression(self, *args: Any, **kwargs: Any) -> MultiColSource:
|
37
45
|
return self
|
38
46
|
|
39
47
|
|
40
|
-
def get_normalized_value(value, lhs):
|
48
|
+
def get_normalized_value(value: Any, lhs: Any) -> tuple[Any, ...]:
|
41
49
|
from plain.models import Model
|
42
50
|
|
43
51
|
if isinstance(value, Model):
|
@@ -63,7 +71,7 @@ def get_normalized_value(value, lhs):
|
|
63
71
|
|
64
72
|
|
65
73
|
class RelatedIn(In):
|
66
|
-
def get_prep_lookup(self):
|
74
|
+
def get_prep_lookup(self) -> list[Any]: # type: ignore[misc]
|
67
75
|
if not isinstance(self.lhs, MultiColSource):
|
68
76
|
if self.rhs_is_direct_value():
|
69
77
|
# If we get here, we are dealing with single-column relations.
|
@@ -97,7 +105,9 @@ class RelatedIn(In):
|
|
97
105
|
self.rhs.set_values([target_field])
|
98
106
|
return super().get_prep_lookup()
|
99
107
|
|
100
|
-
def as_sql(
|
108
|
+
def as_sql(
|
109
|
+
self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
|
110
|
+
) -> tuple[str, list[Any]]: # type: ignore[misc]
|
101
111
|
if isinstance(self.lhs, MultiColSource):
|
102
112
|
# For multicolumn lookups we need to build a multicolumn where clause.
|
103
113
|
# This clause is either a SubqueryConstraint (for values that need
|
@@ -139,7 +149,7 @@ class RelatedIn(In):
|
|
139
149
|
|
140
150
|
|
141
151
|
class RelatedLookupMixin:
|
142
|
-
def get_prep_lookup(self):
|
152
|
+
def get_prep_lookup(self) -> Any: # type: ignore[misc]
|
143
153
|
if not isinstance(self.lhs, MultiColSource) and not hasattr(
|
144
154
|
self.rhs, "resolve_expression"
|
145
155
|
):
|
@@ -155,9 +165,11 @@ class RelatedLookupMixin:
|
|
155
165
|
target_field = self.lhs.output_field.path_infos[-1].target_fields[-1]
|
156
166
|
self.rhs = target_field.get_prep_value(self.rhs)
|
157
167
|
|
158
|
-
return super().get_prep_lookup()
|
168
|
+
return super().get_prep_lookup() # type: ignore[misc]
|
159
169
|
|
160
|
-
def as_sql(
|
170
|
+
def as_sql(
|
171
|
+
self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
|
172
|
+
) -> tuple[str, list[Any]]: # type: ignore[misc]
|
161
173
|
if isinstance(self.lhs, MultiColSource):
|
162
174
|
assert self.rhs_is_direct_value()
|
163
175
|
self.rhs = get_normalized_value(self.rhs, self.lhs)
|
@@ -5,6 +5,10 @@ These managers provide the API for working with collections of related objects
|
|
5
5
|
through foreign key and many-to-many relationships.
|
6
6
|
"""
|
7
7
|
|
8
|
+
from __future__ import annotations
|
9
|
+
|
10
|
+
from typing import Any
|
11
|
+
|
8
12
|
from plain.models import transaction
|
9
13
|
from plain.models.db import NotSupportedError, db_connection
|
10
14
|
from plain.models.expressions import Window
|
@@ -15,7 +19,9 @@ from plain.models.query_utils import Q
|
|
15
19
|
from plain.models.utils import resolve_callables
|
16
20
|
|
17
21
|
|
18
|
-
def _filter_prefetch_queryset(
|
22
|
+
def _filter_prefetch_queryset(
|
23
|
+
queryset: QuerySet, field_name: str, instances: Any
|
24
|
+
) -> QuerySet:
|
19
25
|
predicate = Q(**{f"{field_name}__in": instances})
|
20
26
|
if queryset.sql_query.is_sliced:
|
21
27
|
if not db_connection.features.supports_over_clause:
|
@@ -28,9 +34,9 @@ def _filter_prefetch_queryset(queryset, field_name, instances):
|
|
28
34
|
expr for expr, _ in queryset.sql_query.get_compiler().get_order_by()
|
29
35
|
]
|
30
36
|
window = Window(RowNumber(), partition_by=field_name, order_by=order_by)
|
31
|
-
predicate &= GreaterThan(window, low_mark)
|
37
|
+
predicate &= GreaterThan(window, low_mark) # type: ignore[unsupported-operator]
|
32
38
|
if high_mark is not None:
|
33
|
-
predicate &= LessThanOrEqual(window, high_mark)
|
39
|
+
predicate &= LessThanOrEqual(window, high_mark) # type: ignore[unsupported-operator]
|
34
40
|
queryset.sql_query.clear_limits()
|
35
41
|
return queryset.filter(predicate)
|
36
42
|
|
@@ -59,7 +65,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
59
65
|
This manager adds behaviors specific to many-to-one relations.
|
60
66
|
"""
|
61
67
|
|
62
|
-
def __init__(self, instance, rel):
|
68
|
+
def __init__(self, instance: Any, rel: Any):
|
63
69
|
self.model = rel.related_model
|
64
70
|
self.instance = instance
|
65
71
|
self.field = rel.field
|
@@ -68,7 +74,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
68
74
|
self.base_queryset_class = rel.related_model._meta.queryset.__class__
|
69
75
|
self.allow_null = rel.field.allow_null
|
70
76
|
|
71
|
-
def _check_fk_val(self):
|
77
|
+
def _check_fk_val(self) -> None:
|
72
78
|
for field in self.field.foreign_related_fields:
|
73
79
|
if getattr(self.instance, field.attname) is None:
|
74
80
|
raise ValueError(
|
@@ -76,7 +82,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
76
82
|
f'"{field.attname}" before this relationship can be used.'
|
77
83
|
)
|
78
84
|
|
79
|
-
def _apply_rel_filters(self, queryset):
|
85
|
+
def _apply_rel_filters(self, queryset: QuerySet) -> QuerySet:
|
80
86
|
"""
|
81
87
|
Filter the queryset for the instance this manager is bound to.
|
82
88
|
"""
|
@@ -109,7 +115,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
109
115
|
queryset._known_related_objects = {self.field: {rel_obj_id: self.instance}}
|
110
116
|
return queryset
|
111
117
|
|
112
|
-
def _remove_prefetched_objects(self):
|
118
|
+
def _remove_prefetched_objects(self) -> None:
|
113
119
|
try:
|
114
120
|
self.instance._prefetched_objects_cache.pop(
|
115
121
|
self.field.remote_field.get_cache_name()
|
@@ -117,7 +123,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
117
123
|
except (AttributeError, KeyError):
|
118
124
|
pass # nothing to clear from cache
|
119
125
|
|
120
|
-
def get_queryset(self):
|
126
|
+
def get_queryset(self) -> QuerySet:
|
121
127
|
# Even if this relation is not to primary key, we require still primary key value.
|
122
128
|
# The wish is that the instance has been already saved to DB,
|
123
129
|
# although having a primary key value isn't a guarantee of that.
|
@@ -135,7 +141,9 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
135
141
|
queryset = self.base_queryset_class(model=self.model)
|
136
142
|
return self._apply_rel_filters(queryset)
|
137
143
|
|
138
|
-
def get_prefetch_queryset(
|
144
|
+
def get_prefetch_queryset(
|
145
|
+
self, instances: Any, queryset: QuerySet | None = None
|
146
|
+
) -> tuple[QuerySet, Any, Any, bool, str, bool]:
|
139
147
|
if queryset is None:
|
140
148
|
queryset = self.base_queryset_class(model=self.model)
|
141
149
|
|
@@ -153,11 +161,11 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
153
161
|
cache_name = self.field.remote_field.get_cache_name()
|
154
162
|
return queryset, rel_obj_attr, instance_attr, False, cache_name, False
|
155
163
|
|
156
|
-
def add(self, *objs, bulk=True):
|
164
|
+
def add(self, *objs: Any, bulk: bool = True) -> None:
|
157
165
|
self._check_fk_val()
|
158
166
|
self._remove_prefetched_objects()
|
159
167
|
|
160
|
-
def check_and_update_obj(obj):
|
168
|
+
def check_and_update_obj(obj: Any) -> None:
|
161
169
|
if not isinstance(obj, self.model):
|
162
170
|
raise TypeError(
|
163
171
|
f"'{self.model._meta.object_name}' instance expected, got {obj!r}"
|
@@ -185,22 +193,22 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
185
193
|
check_and_update_obj(obj)
|
186
194
|
obj.save()
|
187
195
|
|
188
|
-
def create(self, **kwargs):
|
196
|
+
def create(self, **kwargs: Any) -> Any:
|
189
197
|
self._check_fk_val()
|
190
198
|
kwargs[self.field.name] = self.instance
|
191
199
|
return self.base_queryset_class(model=self.model).create(**kwargs)
|
192
200
|
|
193
|
-
def get_or_create(self, **kwargs):
|
201
|
+
def get_or_create(self, **kwargs: Any) -> tuple[Any, bool]:
|
194
202
|
self._check_fk_val()
|
195
203
|
kwargs[self.field.name] = self.instance
|
196
204
|
return self.base_queryset_class(model=self.model).get_or_create(**kwargs)
|
197
205
|
|
198
|
-
def update_or_create(self, **kwargs):
|
206
|
+
def update_or_create(self, **kwargs: Any) -> tuple[Any, bool]:
|
199
207
|
self._check_fk_val()
|
200
208
|
kwargs[self.field.name] = self.instance
|
201
209
|
return self.base_queryset_class(model=self.model).update_or_create(**kwargs)
|
202
210
|
|
203
|
-
def remove(self, *objs, bulk=True):
|
211
|
+
def remove(self, *objs: Any, bulk: bool = True) -> None:
|
204
212
|
# remove() is only provided if the ForeignKey can have a value of null
|
205
213
|
if not self.allow_null:
|
206
214
|
raise AttributeError(
|
@@ -226,7 +234,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
226
234
|
)
|
227
235
|
self._clear(self.query.filter(id__in=old_ids), bulk)
|
228
236
|
|
229
|
-
def clear(self, *, bulk=True):
|
237
|
+
def clear(self, *, bulk: bool = True) -> None:
|
230
238
|
# clear() is only provided if the ForeignKey can have a value of null
|
231
239
|
if not self.allow_null:
|
232
240
|
raise AttributeError(
|
@@ -236,7 +244,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
236
244
|
self._check_fk_val()
|
237
245
|
self._clear(self.query, bulk)
|
238
246
|
|
239
|
-
def _clear(self, queryset, bulk):
|
247
|
+
def _clear(self, queryset: QuerySet, bulk: bool) -> None:
|
240
248
|
self._remove_prefetched_objects()
|
241
249
|
if bulk:
|
242
250
|
# `QuerySet.update()` is intrinsically atomic.
|
@@ -247,7 +255,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
247
255
|
setattr(obj, self.field.name, None)
|
248
256
|
obj.save(update_fields=[self.field.name])
|
249
257
|
|
250
|
-
def set(self, objs, *, bulk=True, clear=False):
|
258
|
+
def set(self, objs: Any, *, bulk: bool = True, clear: bool = False) -> None:
|
251
259
|
self._check_fk_val()
|
252
260
|
# Force evaluation of `objs` in case it's a queryset whose value
|
253
261
|
# could be affected by `manager.clear()`. Refs #19816.
|
@@ -286,7 +294,7 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
286
294
|
- symmetrical (for forward relations)
|
287
295
|
"""
|
288
296
|
|
289
|
-
def __init__(self, instance, rel):
|
297
|
+
def __init__(self, instance: Any, rel: Any):
|
290
298
|
self.instance = instance
|
291
299
|
self.through = rel.through
|
292
300
|
# Subclasses must set model before calling super().__init__
|
@@ -315,12 +323,12 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
315
323
|
"a many-to-many relationship can be used."
|
316
324
|
)
|
317
325
|
|
318
|
-
def _apply_rel_filters(self, queryset):
|
326
|
+
def _apply_rel_filters(self, queryset: QuerySet) -> QuerySet:
|
319
327
|
"""Filter the queryset for the instance this manager is bound to."""
|
320
328
|
queryset._defer_next_filter = True
|
321
329
|
return queryset._next_is_sticky().filter(**self.core_filters)
|
322
330
|
|
323
|
-
def _remove_prefetched_objects(self):
|
331
|
+
def _remove_prefetched_objects(self) -> None:
|
324
332
|
try:
|
325
333
|
self.instance._prefetched_objects_cache.pop(self.prefetch_cache_name)
|
326
334
|
except (AttributeError, KeyError):
|
@@ -333,7 +341,9 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
333
341
|
queryset = self.base_queryset_class(model=self.model)
|
334
342
|
return self._apply_rel_filters(queryset)
|
335
343
|
|
336
|
-
def get_prefetch_queryset(
|
344
|
+
def get_prefetch_queryset(
|
345
|
+
self, instances: Any, queryset: QuerySet | None = None
|
346
|
+
) -> tuple[QuerySet, Any, Any, bool, str, bool]:
|
337
347
|
if queryset is None:
|
338
348
|
queryset = self.base_queryset_class(model=self.model)
|
339
349
|
|
@@ -367,7 +377,7 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
367
377
|
False,
|
368
378
|
)
|
369
379
|
|
370
|
-
def clear(self):
|
380
|
+
def clear(self) -> None:
|
371
381
|
with transaction.atomic(savepoint=False):
|
372
382
|
self._remove_prefetched_objects()
|
373
383
|
filters = self._build_remove_filters(
|
@@ -375,7 +385,13 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
375
385
|
)
|
376
386
|
self.through.query.filter(filters).delete()
|
377
387
|
|
378
|
-
def set(
|
388
|
+
def set(
|
389
|
+
self,
|
390
|
+
objs: Any,
|
391
|
+
*,
|
392
|
+
clear: bool = False,
|
393
|
+
through_defaults: dict[str, Any] | None = None,
|
394
|
+
) -> None:
|
379
395
|
# Force evaluation of `objs` in case it's a queryset whose value
|
380
396
|
# could be affected by `manager.clear()`. Refs #19816.
|
381
397
|
objs = tuple(objs)
|
@@ -406,12 +422,16 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
406
422
|
self.remove(*old_ids)
|
407
423
|
self.add(*new_objs, through_defaults=through_defaults)
|
408
424
|
|
409
|
-
def create(
|
425
|
+
def create(
|
426
|
+
self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
|
427
|
+
) -> Any:
|
410
428
|
new_obj = self.base_queryset_class(model=self.model).create(**kwargs)
|
411
429
|
self.add(new_obj, through_defaults=through_defaults)
|
412
430
|
return new_obj
|
413
431
|
|
414
|
-
def get_or_create(
|
432
|
+
def get_or_create(
|
433
|
+
self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
|
434
|
+
) -> tuple[Any, bool]:
|
415
435
|
obj, created = self.base_queryset_class(model=self.model).get_or_create(
|
416
436
|
**kwargs
|
417
437
|
)
|
@@ -421,7 +441,9 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
421
441
|
self.add(obj, through_defaults=through_defaults)
|
422
442
|
return obj, created
|
423
443
|
|
424
|
-
def update_or_create(
|
444
|
+
def update_or_create(
|
445
|
+
self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
|
446
|
+
) -> tuple[Any, bool]:
|
425
447
|
obj, created = self.base_queryset_class(model=self.model).update_or_create(
|
426
448
|
**kwargs
|
427
449
|
)
|
@@ -431,7 +453,7 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
431
453
|
self.add(obj, through_defaults=through_defaults)
|
432
454
|
return obj, created
|
433
455
|
|
434
|
-
def _get_target_ids(self, target_field_name, objs):
|
456
|
+
def _get_target_ids(self, target_field_name: str, objs: Any) -> set[Any]:
|
435
457
|
"""Return the set of ids of `objs` that the target field references."""
|
436
458
|
from plain.models import Model
|
437
459
|
|
@@ -453,7 +475,9 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
453
475
|
target_ids.add(target_field.get_prep_value(obj))
|
454
476
|
return target_ids
|
455
477
|
|
456
|
-
def _get_missing_target_ids(
|
478
|
+
def _get_missing_target_ids(
|
479
|
+
self, source_field_name: str, target_field_name: str, target_ids: set[Any]
|
480
|
+
) -> set[Any]:
|
457
481
|
"""Return the subset of ids of `objs` that aren't already assigned to this relationship."""
|
458
482
|
vals = self.through.query.values_list(target_field_name, flat=True).filter(
|
459
483
|
**{
|
@@ -464,8 +488,12 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
464
488
|
return target_ids.difference(vals)
|
465
489
|
|
466
490
|
def _add_items(
|
467
|
-
self,
|
468
|
-
|
491
|
+
self,
|
492
|
+
source_field_name: str,
|
493
|
+
target_field_name: str,
|
494
|
+
*objs: Any,
|
495
|
+
through_defaults: dict[str, Any] | None = None,
|
496
|
+
) -> None:
|
469
497
|
if not objs:
|
470
498
|
return
|
471
499
|
|
@@ -490,7 +518,9 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
490
518
|
],
|
491
519
|
)
|
492
520
|
|
493
|
-
def _remove_items(
|
521
|
+
def _remove_items(
|
522
|
+
self, source_field_name: str, target_field_name: str, *objs: Any
|
523
|
+
) -> None:
|
494
524
|
if not objs:
|
495
525
|
return
|
496
526
|
|
@@ -515,13 +545,13 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
515
545
|
self.through.query.filter(filters).delete()
|
516
546
|
|
517
547
|
# Subclasses must implement these methods:
|
518
|
-
def _build_remove_filters(self, removed_vals):
|
548
|
+
def _build_remove_filters(self, removed_vals: Any) -> Any:
|
519
549
|
raise NotImplementedError
|
520
550
|
|
521
|
-
def add(self, *objs, through_defaults=None):
|
551
|
+
def add(self, *objs: Any, through_defaults: dict[str, Any] | None = None) -> None:
|
522
552
|
raise NotImplementedError
|
523
553
|
|
524
|
-
def remove(self, *objs):
|
554
|
+
def remove(self, *objs: Any) -> None:
|
525
555
|
raise NotImplementedError
|
526
556
|
|
527
557
|
|
@@ -532,7 +562,7 @@ class ForwardManyToManyManager(BaseManyToManyManager):
|
|
532
562
|
This manager adds behaviors specific to many-to-many relations.
|
533
563
|
"""
|
534
564
|
|
535
|
-
def __init__(self, instance, rel):
|
565
|
+
def __init__(self, instance: Any, rel: Any):
|
536
566
|
# Set required attributes before calling super().__init__
|
537
567
|
self.model = rel.model
|
538
568
|
self.query_field_name = rel.field.related_query_name()
|
@@ -543,7 +573,7 @@ class ForwardManyToManyManager(BaseManyToManyManager):
|
|
543
573
|
|
544
574
|
super().__init__(instance, rel)
|
545
575
|
|
546
|
-
def _build_remove_filters(self, removed_vals):
|
576
|
+
def _build_remove_filters(self, removed_vals: Any) -> Any:
|
547
577
|
filters = Q.create([(self.source_field_name, self.related_val)])
|
548
578
|
# No need to add a subquery condition if removed_vals is a QuerySet without
|
549
579
|
# filters.
|
@@ -551,17 +581,19 @@ class ForwardManyToManyManager(BaseManyToManyManager):
|
|
551
581
|
not isinstance(removed_vals, QuerySet) or removed_vals._has_filters()
|
552
582
|
)
|
553
583
|
if removed_vals_filters:
|
554
|
-
filters
|
584
|
+
filters = filters & Q.create( # type: ignore[unsupported-operator]
|
585
|
+
[(f"{self.target_field_name}__in", removed_vals)]
|
586
|
+
)
|
555
587
|
if self.symmetrical:
|
556
588
|
symmetrical_filters = Q.create([(self.target_field_name, self.related_val)])
|
557
589
|
if removed_vals_filters:
|
558
|
-
symmetrical_filters
|
590
|
+
symmetrical_filters = symmetrical_filters & Q.create( # type: ignore[unsupported-operator]
|
559
591
|
[(f"{self.source_field_name}__in", removed_vals)]
|
560
592
|
)
|
561
|
-
filters
|
593
|
+
filters = filters | symmetrical_filters # type: ignore[unsupported-operator]
|
562
594
|
return filters
|
563
595
|
|
564
|
-
def add(self, *objs, through_defaults=None):
|
596
|
+
def add(self, *objs: Any, through_defaults: dict[str, Any] | None = None) -> None:
|
565
597
|
self._remove_prefetched_objects()
|
566
598
|
with transaction.atomic(savepoint=False):
|
567
599
|
self._add_items(
|
@@ -580,7 +612,7 @@ class ForwardManyToManyManager(BaseManyToManyManager):
|
|
580
612
|
through_defaults=through_defaults,
|
581
613
|
)
|
582
614
|
|
583
|
-
def remove(self, *objs):
|
615
|
+
def remove(self, *objs: Any) -> None:
|
584
616
|
self._remove_prefetched_objects()
|
585
617
|
self._remove_items(self.source_field_name, self.target_field_name, *objs)
|
586
618
|
|
@@ -592,7 +624,7 @@ class ReverseManyToManyManager(BaseManyToManyManager):
|
|
592
624
|
This manager adds behaviors specific to many-to-many relations.
|
593
625
|
"""
|
594
626
|
|
595
|
-
def __init__(self, instance, rel):
|
627
|
+
def __init__(self, instance: Any, rel: Any):
|
596
628
|
# Set required attributes before calling super().__init__
|
597
629
|
self.model = rel.related_model
|
598
630
|
self.query_field_name = rel.field.name
|
@@ -603,7 +635,7 @@ class ReverseManyToManyManager(BaseManyToManyManager):
|
|
603
635
|
|
604
636
|
super().__init__(instance, rel)
|
605
637
|
|
606
|
-
def _build_remove_filters(self, removed_vals):
|
638
|
+
def _build_remove_filters(self, removed_vals: Any) -> Any:
|
607
639
|
filters = Q.create([(self.source_field_name, self.related_val)])
|
608
640
|
# No need to add a subquery condition if removed_vals is a QuerySet without
|
609
641
|
# filters.
|
@@ -611,11 +643,13 @@ class ReverseManyToManyManager(BaseManyToManyManager):
|
|
611
643
|
not isinstance(removed_vals, QuerySet) or removed_vals._has_filters()
|
612
644
|
)
|
613
645
|
if removed_vals_filters:
|
614
|
-
filters
|
646
|
+
filters = filters & Q.create( # type: ignore[unsupported-operator]
|
647
|
+
[(f"{self.target_field_name}__in", removed_vals)]
|
648
|
+
)
|
615
649
|
# Note: reverse relations are never symmetrical, so no symmetrical logic here
|
616
650
|
return filters
|
617
651
|
|
618
|
-
def add(self, *objs, through_defaults=None):
|
652
|
+
def add(self, *objs: Any, through_defaults: dict[str, Any] | None = None) -> None:
|
619
653
|
self._remove_prefetched_objects()
|
620
654
|
with transaction.atomic(savepoint=False):
|
621
655
|
self._add_items(
|
@@ -626,6 +660,6 @@ class ReverseManyToManyManager(BaseManyToManyManager):
|
|
626
660
|
)
|
627
661
|
# Reverse relations are never symmetrical, so no mirror entry logic
|
628
662
|
|
629
|
-
def remove(self, *objs):
|
663
|
+
def remove(self, *objs: Any) -> None:
|
630
664
|
self._remove_prefetched_objects()
|
631
665
|
self._remove_items(self.source_field_name, self.target_field_name, *objs)
|