plain.models 0.49.2__py3-none-any.whl → 0.51.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plain/models/CHANGELOG.md +27 -0
- plain/models/README.md +26 -42
- plain/models/__init__.py +2 -0
- plain/models/aggregates.py +42 -19
- plain/models/backends/base/base.py +125 -105
- plain/models/backends/base/client.py +11 -3
- plain/models/backends/base/creation.py +24 -14
- plain/models/backends/base/features.py +10 -4
- plain/models/backends/base/introspection.py +37 -20
- plain/models/backends/base/operations.py +187 -91
- plain/models/backends/base/schema.py +338 -218
- plain/models/backends/base/validation.py +13 -4
- plain/models/backends/ddl_references.py +85 -43
- plain/models/backends/mysql/base.py +29 -26
- plain/models/backends/mysql/client.py +7 -2
- plain/models/backends/mysql/compiler.py +13 -4
- plain/models/backends/mysql/creation.py +5 -2
- plain/models/backends/mysql/features.py +24 -22
- plain/models/backends/mysql/introspection.py +22 -13
- plain/models/backends/mysql/operations.py +107 -40
- plain/models/backends/mysql/schema.py +52 -28
- plain/models/backends/mysql/validation.py +13 -6
- plain/models/backends/postgresql/base.py +41 -34
- plain/models/backends/postgresql/client.py +7 -2
- plain/models/backends/postgresql/creation.py +10 -5
- plain/models/backends/postgresql/introspection.py +15 -8
- plain/models/backends/postgresql/operations.py +110 -43
- plain/models/backends/postgresql/schema.py +88 -49
- plain/models/backends/sqlite3/_functions.py +151 -115
- plain/models/backends/sqlite3/base.py +37 -23
- plain/models/backends/sqlite3/client.py +7 -1
- plain/models/backends/sqlite3/creation.py +9 -5
- plain/models/backends/sqlite3/features.py +5 -3
- plain/models/backends/sqlite3/introspection.py +32 -16
- plain/models/backends/sqlite3/operations.py +126 -43
- plain/models/backends/sqlite3/schema.py +127 -92
- plain/models/backends/utils.py +52 -29
- plain/models/backups/cli.py +8 -6
- plain/models/backups/clients.py +16 -7
- plain/models/backups/core.py +24 -13
- plain/models/base.py +221 -229
- plain/models/cli.py +98 -67
- plain/models/config.py +1 -1
- plain/models/connections.py +23 -7
- plain/models/constraints.py +79 -56
- plain/models/database_url.py +1 -1
- plain/models/db.py +6 -2
- plain/models/deletion.py +80 -56
- plain/models/entrypoints.py +1 -1
- plain/models/enums.py +22 -11
- plain/models/exceptions.py +23 -8
- plain/models/expressions.py +441 -258
- plain/models/fields/__init__.py +272 -217
- plain/models/fields/json.py +123 -57
- plain/models/fields/mixins.py +12 -8
- plain/models/fields/related.py +324 -290
- plain/models/fields/related_descriptors.py +33 -24
- plain/models/fields/related_lookups.py +24 -12
- plain/models/fields/related_managers.py +102 -79
- plain/models/fields/reverse_related.py +66 -63
- plain/models/forms.py +101 -75
- plain/models/functions/comparison.py +71 -18
- plain/models/functions/datetime.py +79 -29
- plain/models/functions/math.py +43 -10
- plain/models/functions/mixins.py +24 -7
- plain/models/functions/text.py +104 -25
- plain/models/functions/window.py +12 -6
- plain/models/indexes.py +57 -32
- plain/models/lookups.py +228 -153
- plain/models/meta.py +505 -0
- plain/models/migrations/autodetector.py +86 -43
- plain/models/migrations/exceptions.py +7 -3
- plain/models/migrations/executor.py +33 -7
- plain/models/migrations/graph.py +79 -50
- plain/models/migrations/loader.py +45 -22
- plain/models/migrations/migration.py +23 -18
- plain/models/migrations/operations/base.py +38 -20
- plain/models/migrations/operations/fields.py +95 -48
- plain/models/migrations/operations/models.py +246 -142
- plain/models/migrations/operations/special.py +82 -25
- plain/models/migrations/optimizer.py +7 -2
- plain/models/migrations/questioner.py +58 -31
- plain/models/migrations/recorder.py +27 -16
- plain/models/migrations/serializer.py +50 -39
- plain/models/migrations/state.py +232 -156
- plain/models/migrations/utils.py +30 -14
- plain/models/migrations/writer.py +17 -14
- plain/models/options.py +189 -518
- plain/models/otel.py +16 -6
- plain/models/preflight.py +42 -17
- plain/models/query.py +400 -251
- plain/models/query_utils.py +109 -69
- plain/models/registry.py +40 -21
- plain/models/sql/compiler.py +190 -127
- plain/models/sql/datastructures.py +38 -25
- plain/models/sql/query.py +320 -225
- plain/models/sql/subqueries.py +36 -25
- plain/models/sql/where.py +54 -29
- plain/models/test/pytest.py +15 -11
- plain/models/test/utils.py +4 -2
- plain/models/transaction.py +20 -7
- plain/models/utils.py +17 -6
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/METADATA +27 -43
- plain_models-0.51.0.dist-info/RECORD +123 -0
- plain_models-0.49.2.dist-info/RECORD +0 -122
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/WHEEL +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/licenses/LICENSE +0 -0
@@ -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,16 +65,14 @@ 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
|
66
72
|
self.core_filters = {self.field.name: instance}
|
67
|
-
# Store the base queryset class for this model
|
68
|
-
self.base_queryset_class = rel.related_model._meta.queryset.__class__
|
69
73
|
self.allow_null = rel.field.allow_null
|
70
74
|
|
71
|
-
def _check_fk_val(self):
|
75
|
+
def _check_fk_val(self) -> None:
|
72
76
|
for field in self.field.foreign_related_fields:
|
73
77
|
if getattr(self.instance, field.attname) is None:
|
74
78
|
raise ValueError(
|
@@ -76,7 +80,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
76
80
|
f'"{field.attname}" before this relationship can be used.'
|
77
81
|
)
|
78
82
|
|
79
|
-
def _apply_rel_filters(self, queryset):
|
83
|
+
def _apply_rel_filters(self, queryset: QuerySet) -> QuerySet:
|
80
84
|
"""
|
81
85
|
Filter the queryset for the instance this manager is bound to.
|
82
86
|
"""
|
@@ -109,7 +113,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
109
113
|
queryset._known_related_objects = {self.field: {rel_obj_id: self.instance}}
|
110
114
|
return queryset
|
111
115
|
|
112
|
-
def _remove_prefetched_objects(self):
|
116
|
+
def _remove_prefetched_objects(self) -> None:
|
113
117
|
try:
|
114
118
|
self.instance._prefetched_objects_cache.pop(
|
115
119
|
self.field.remote_field.get_cache_name()
|
@@ -117,7 +121,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
117
121
|
except (AttributeError, KeyError):
|
118
122
|
pass # nothing to clear from cache
|
119
123
|
|
120
|
-
def get_queryset(self):
|
124
|
+
def get_queryset(self) -> QuerySet:
|
121
125
|
# Even if this relation is not to primary key, we require still primary key value.
|
122
126
|
# The wish is that the instance has been already saved to DB,
|
123
127
|
# although having a primary key value isn't a guarantee of that.
|
@@ -131,13 +135,14 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
131
135
|
self.field.remote_field.get_cache_name()
|
132
136
|
]
|
133
137
|
except (AttributeError, KeyError):
|
134
|
-
|
135
|
-
queryset = self.base_queryset_class(model=self.model)
|
138
|
+
queryset = self.model.query
|
136
139
|
return self._apply_rel_filters(queryset)
|
137
140
|
|
138
|
-
def get_prefetch_queryset(
|
141
|
+
def get_prefetch_queryset(
|
142
|
+
self, instances: Any, queryset: QuerySet | None = None
|
143
|
+
) -> tuple[QuerySet, Any, Any, bool, str, bool]:
|
139
144
|
if queryset is None:
|
140
|
-
queryset = self.
|
145
|
+
queryset = self.model.query
|
141
146
|
|
142
147
|
rel_obj_attr = self.field.get_local_related_value
|
143
148
|
instance_attr = self.field.get_foreign_related_value
|
@@ -153,14 +158,14 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
153
158
|
cache_name = self.field.remote_field.get_cache_name()
|
154
159
|
return queryset, rel_obj_attr, instance_attr, False, cache_name, False
|
155
160
|
|
156
|
-
def add(self, *objs, bulk=True):
|
161
|
+
def add(self, *objs: Any, bulk: bool = True) -> None:
|
157
162
|
self._check_fk_val()
|
158
163
|
self._remove_prefetched_objects()
|
159
164
|
|
160
|
-
def check_and_update_obj(obj):
|
165
|
+
def check_and_update_obj(obj: Any) -> None:
|
161
166
|
if not isinstance(obj, self.model):
|
162
167
|
raise TypeError(
|
163
|
-
f"'{self.model.
|
168
|
+
f"'{self.model.model_options.object_name}' instance expected, got {obj!r}"
|
164
169
|
)
|
165
170
|
setattr(obj, self.field.name, self.instance)
|
166
171
|
|
@@ -174,7 +179,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
174
179
|
"the object first."
|
175
180
|
)
|
176
181
|
ids.append(obj.id)
|
177
|
-
self.model.
|
182
|
+
self.model._model_meta.base_queryset.filter(id__in=ids).update(
|
178
183
|
**{
|
179
184
|
self.field.name: self.instance,
|
180
185
|
}
|
@@ -185,22 +190,22 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
185
190
|
check_and_update_obj(obj)
|
186
191
|
obj.save()
|
187
192
|
|
188
|
-
def create(self, **kwargs):
|
193
|
+
def create(self, **kwargs: Any) -> Any:
|
189
194
|
self._check_fk_val()
|
190
195
|
kwargs[self.field.name] = self.instance
|
191
|
-
return self.
|
196
|
+
return self.model.query.create(**kwargs)
|
192
197
|
|
193
|
-
def get_or_create(self, **kwargs):
|
198
|
+
def get_or_create(self, **kwargs: Any) -> tuple[Any, bool]:
|
194
199
|
self._check_fk_val()
|
195
200
|
kwargs[self.field.name] = self.instance
|
196
|
-
return self.
|
201
|
+
return self.model.query.get_or_create(**kwargs)
|
197
202
|
|
198
|
-
def update_or_create(self, **kwargs):
|
203
|
+
def update_or_create(self, **kwargs: Any) -> tuple[Any, bool]:
|
199
204
|
self._check_fk_val()
|
200
205
|
kwargs[self.field.name] = self.instance
|
201
|
-
return self.
|
206
|
+
return self.model.query.update_or_create(**kwargs)
|
202
207
|
|
203
|
-
def remove(self, *objs, bulk=True):
|
208
|
+
def remove(self, *objs: Any, bulk: bool = True) -> None:
|
204
209
|
# remove() is only provided if the ForeignKey can have a value of null
|
205
210
|
if not self.allow_null:
|
206
211
|
raise AttributeError(
|
@@ -215,7 +220,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
215
220
|
for obj in objs:
|
216
221
|
if not isinstance(obj, self.model):
|
217
222
|
raise TypeError(
|
218
|
-
f"'{self.model.
|
223
|
+
f"'{self.model.model_options.object_name}' instance expected, got {obj!r}"
|
219
224
|
)
|
220
225
|
# Is obj actually part of this descriptor set?
|
221
226
|
if self.field.get_local_related_value(obj) == val:
|
@@ -226,7 +231,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
226
231
|
)
|
227
232
|
self._clear(self.query.filter(id__in=old_ids), bulk)
|
228
233
|
|
229
|
-
def clear(self, *, bulk=True):
|
234
|
+
def clear(self, *, bulk: bool = True) -> None:
|
230
235
|
# clear() is only provided if the ForeignKey can have a value of null
|
231
236
|
if not self.allow_null:
|
232
237
|
raise AttributeError(
|
@@ -236,7 +241,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
236
241
|
self._check_fk_val()
|
237
242
|
self._clear(self.query, bulk)
|
238
243
|
|
239
|
-
def _clear(self, queryset, bulk):
|
244
|
+
def _clear(self, queryset: QuerySet, bulk: bool) -> None:
|
240
245
|
self._remove_prefetched_objects()
|
241
246
|
if bulk:
|
242
247
|
# `QuerySet.update()` is intrinsically atomic.
|
@@ -247,7 +252,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
|
|
247
252
|
setattr(obj, self.field.name, None)
|
248
253
|
obj.save(update_fields=[self.field.name])
|
249
254
|
|
250
|
-
def set(self, objs, *, bulk=True, clear=False):
|
255
|
+
def set(self, objs: Any, *, bulk: bool = True, clear: bool = False) -> None:
|
251
256
|
self._check_fk_val()
|
252
257
|
# Force evaluation of `objs` in case it's a queryset whose value
|
253
258
|
# could be affected by `manager.clear()`. Refs #19816.
|
@@ -286,14 +291,12 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
286
291
|
- symmetrical (for forward relations)
|
287
292
|
"""
|
288
293
|
|
289
|
-
def __init__(self, instance, rel):
|
294
|
+
def __init__(self, instance: Any, rel: Any):
|
290
295
|
self.instance = instance
|
291
296
|
self.through = rel.through
|
292
|
-
# Subclasses must set model before calling super().__init__
|
293
|
-
self.base_queryset_class = self.model._meta.queryset.__class__
|
294
297
|
|
295
|
-
self.source_field = self.through.
|
296
|
-
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)
|
297
300
|
|
298
301
|
self.core_filters = {}
|
299
302
|
self.id_field_names = {}
|
@@ -315,12 +318,12 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
315
318
|
"a many-to-many relationship can be used."
|
316
319
|
)
|
317
320
|
|
318
|
-
def _apply_rel_filters(self, queryset):
|
321
|
+
def _apply_rel_filters(self, queryset: QuerySet) -> QuerySet:
|
319
322
|
"""Filter the queryset for the instance this manager is bound to."""
|
320
323
|
queryset._defer_next_filter = True
|
321
324
|
return queryset._next_is_sticky().filter(**self.core_filters)
|
322
325
|
|
323
|
-
def _remove_prefetched_objects(self):
|
326
|
+
def _remove_prefetched_objects(self) -> None:
|
324
327
|
try:
|
325
328
|
self.instance._prefetched_objects_cache.pop(self.prefetch_cache_name)
|
326
329
|
except (AttributeError, KeyError):
|
@@ -330,12 +333,14 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
330
333
|
try:
|
331
334
|
return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
|
332
335
|
except (AttributeError, KeyError):
|
333
|
-
queryset = self.
|
336
|
+
queryset = self.model.query
|
334
337
|
return self._apply_rel_filters(queryset)
|
335
338
|
|
336
|
-
def get_prefetch_queryset(
|
339
|
+
def get_prefetch_queryset(
|
340
|
+
self, instances: Any, queryset: QuerySet | None = None
|
341
|
+
) -> tuple[QuerySet, Any, Any, bool, str, bool]:
|
337
342
|
if queryset is None:
|
338
|
-
queryset = self.
|
343
|
+
queryset = self.model.query
|
339
344
|
|
340
345
|
queryset = _filter_prefetch_queryset(
|
341
346
|
queryset._next_is_sticky(), self.query_field_name, instances
|
@@ -343,8 +348,8 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
343
348
|
|
344
349
|
# M2M: need to annotate the query in order to get the primary model
|
345
350
|
# that the secondary model was actually related to.
|
346
|
-
fk = self.through.
|
347
|
-
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
|
348
353
|
qn = db_connection.ops.quote_name
|
349
354
|
queryset = queryset.extra(
|
350
355
|
select={
|
@@ -367,15 +372,19 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
367
372
|
False,
|
368
373
|
)
|
369
374
|
|
370
|
-
def clear(self):
|
375
|
+
def clear(self) -> None:
|
371
376
|
with transaction.atomic(savepoint=False):
|
372
377
|
self._remove_prefetched_objects()
|
373
|
-
filters = self._build_remove_filters(
|
374
|
-
self.base_queryset_class(model=self.model)
|
375
|
-
)
|
378
|
+
filters = self._build_remove_filters(self.model.query)
|
376
379
|
self.through.query.filter(filters).delete()
|
377
380
|
|
378
|
-
def set(
|
381
|
+
def set(
|
382
|
+
self,
|
383
|
+
objs: Any,
|
384
|
+
*,
|
385
|
+
clear: bool = False,
|
386
|
+
through_defaults: dict[str, Any] | None = None,
|
387
|
+
) -> None:
|
379
388
|
# Force evaluation of `objs` in case it's a queryset whose value
|
380
389
|
# could be affected by `manager.clear()`. Refs #19816.
|
381
390
|
objs = tuple(objs)
|
@@ -406,37 +415,39 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
406
415
|
self.remove(*old_ids)
|
407
416
|
self.add(*new_objs, through_defaults=through_defaults)
|
408
417
|
|
409
|
-
def create(
|
410
|
-
|
418
|
+
def create(
|
419
|
+
self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
|
420
|
+
) -> Any:
|
421
|
+
new_obj = self.model.query.create(**kwargs)
|
411
422
|
self.add(new_obj, through_defaults=through_defaults)
|
412
423
|
return new_obj
|
413
424
|
|
414
|
-
def get_or_create(
|
415
|
-
|
416
|
-
|
417
|
-
)
|
425
|
+
def get_or_create(
|
426
|
+
self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
|
427
|
+
) -> tuple[Any, bool]:
|
428
|
+
obj, created = self.model.query.get_or_create(**kwargs)
|
418
429
|
# We only need to add() if created because if we got an object back
|
419
430
|
# from get() then the relationship already exists.
|
420
431
|
if created:
|
421
432
|
self.add(obj, through_defaults=through_defaults)
|
422
433
|
return obj, created
|
423
434
|
|
424
|
-
def update_or_create(
|
425
|
-
|
426
|
-
|
427
|
-
)
|
435
|
+
def update_or_create(
|
436
|
+
self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
|
437
|
+
) -> tuple[Any, bool]:
|
438
|
+
obj, created = self.model.query.update_or_create(**kwargs)
|
428
439
|
# We only need to add() if created because if we got an object back
|
429
440
|
# from get() then the relationship already exists.
|
430
441
|
if created:
|
431
442
|
self.add(obj, through_defaults=through_defaults)
|
432
443
|
return obj, created
|
433
444
|
|
434
|
-
def _get_target_ids(self, target_field_name, objs):
|
445
|
+
def _get_target_ids(self, target_field_name: str, objs: Any) -> set[Any]:
|
435
446
|
"""Return the set of ids of `objs` that the target field references."""
|
436
447
|
from plain.models import Model
|
437
448
|
|
438
449
|
target_ids = set()
|
439
|
-
target_field = self.through.
|
450
|
+
target_field = self.through._model_meta.get_field(target_field_name)
|
440
451
|
for obj in objs:
|
441
452
|
if isinstance(obj, self.model):
|
442
453
|
target_id = target_field.get_foreign_related_value(obj)[0]
|
@@ -447,13 +458,15 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
447
458
|
target_ids.add(target_id)
|
448
459
|
elif isinstance(obj, Model):
|
449
460
|
raise TypeError(
|
450
|
-
f"'{self.model.
|
461
|
+
f"'{self.model.model_options.object_name}' instance expected, got {obj!r}"
|
451
462
|
)
|
452
463
|
else:
|
453
464
|
target_ids.add(target_field.get_prep_value(obj))
|
454
465
|
return target_ids
|
455
466
|
|
456
|
-
def _get_missing_target_ids(
|
467
|
+
def _get_missing_target_ids(
|
468
|
+
self, source_field_name: str, target_field_name: str, target_ids: set[Any]
|
469
|
+
) -> set[Any]:
|
457
470
|
"""Return the subset of ids of `objs` that aren't already assigned to this relationship."""
|
458
471
|
vals = self.through.query.values_list(target_field_name, flat=True).filter(
|
459
472
|
**{
|
@@ -464,8 +477,12 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
464
477
|
return target_ids.difference(vals)
|
465
478
|
|
466
479
|
def _add_items(
|
467
|
-
self,
|
468
|
-
|
480
|
+
self,
|
481
|
+
source_field_name: str,
|
482
|
+
target_field_name: str,
|
483
|
+
*objs: Any,
|
484
|
+
through_defaults: dict[str, Any] | None = None,
|
485
|
+
) -> None:
|
469
486
|
if not objs:
|
470
487
|
return
|
471
488
|
|
@@ -490,7 +507,9 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
490
507
|
],
|
491
508
|
)
|
492
509
|
|
493
|
-
def _remove_items(
|
510
|
+
def _remove_items(
|
511
|
+
self, source_field_name: str, target_field_name: str, *objs: Any
|
512
|
+
) -> None:
|
494
513
|
if not objs:
|
495
514
|
return
|
496
515
|
|
@@ -504,7 +523,7 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
504
523
|
old_ids.add(obj)
|
505
524
|
|
506
525
|
with transaction.atomic(savepoint=False):
|
507
|
-
target_model_qs = self.
|
526
|
+
target_model_qs = self.model.query
|
508
527
|
if target_model_qs._has_filters():
|
509
528
|
old_vals = target_model_qs.filter(
|
510
529
|
**{f"{self.target_field.target_field.attname}__in": old_ids}
|
@@ -515,13 +534,13 @@ class BaseManyToManyManager(BaseRelatedManager):
|
|
515
534
|
self.through.query.filter(filters).delete()
|
516
535
|
|
517
536
|
# Subclasses must implement these methods:
|
518
|
-
def _build_remove_filters(self, removed_vals):
|
537
|
+
def _build_remove_filters(self, removed_vals: Any) -> Any:
|
519
538
|
raise NotImplementedError
|
520
539
|
|
521
|
-
def add(self, *objs, through_defaults=None):
|
540
|
+
def add(self, *objs: Any, through_defaults: dict[str, Any] | None = None) -> None:
|
522
541
|
raise NotImplementedError
|
523
542
|
|
524
|
-
def remove(self, *objs):
|
543
|
+
def remove(self, *objs: Any) -> None:
|
525
544
|
raise NotImplementedError
|
526
545
|
|
527
546
|
|
@@ -532,7 +551,7 @@ class ForwardManyToManyManager(BaseManyToManyManager):
|
|
532
551
|
This manager adds behaviors specific to many-to-many relations.
|
533
552
|
"""
|
534
553
|
|
535
|
-
def __init__(self, instance, rel):
|
554
|
+
def __init__(self, instance: Any, rel: Any):
|
536
555
|
# Set required attributes before calling super().__init__
|
537
556
|
self.model = rel.model
|
538
557
|
self.query_field_name = rel.field.related_query_name()
|
@@ -543,7 +562,7 @@ class ForwardManyToManyManager(BaseManyToManyManager):
|
|
543
562
|
|
544
563
|
super().__init__(instance, rel)
|
545
564
|
|
546
|
-
def _build_remove_filters(self, removed_vals):
|
565
|
+
def _build_remove_filters(self, removed_vals: Any) -> Any:
|
547
566
|
filters = Q.create([(self.source_field_name, self.related_val)])
|
548
567
|
# No need to add a subquery condition if removed_vals is a QuerySet without
|
549
568
|
# filters.
|
@@ -551,17 +570,19 @@ class ForwardManyToManyManager(BaseManyToManyManager):
|
|
551
570
|
not isinstance(removed_vals, QuerySet) or removed_vals._has_filters()
|
552
571
|
)
|
553
572
|
if removed_vals_filters:
|
554
|
-
filters
|
573
|
+
filters = filters & Q.create( # type: ignore[unsupported-operator]
|
574
|
+
[(f"{self.target_field_name}__in", removed_vals)]
|
575
|
+
)
|
555
576
|
if self.symmetrical:
|
556
577
|
symmetrical_filters = Q.create([(self.target_field_name, self.related_val)])
|
557
578
|
if removed_vals_filters:
|
558
|
-
symmetrical_filters
|
579
|
+
symmetrical_filters = symmetrical_filters & Q.create( # type: ignore[unsupported-operator]
|
559
580
|
[(f"{self.source_field_name}__in", removed_vals)]
|
560
581
|
)
|
561
|
-
filters
|
582
|
+
filters = filters | symmetrical_filters # type: ignore[unsupported-operator]
|
562
583
|
return filters
|
563
584
|
|
564
|
-
def add(self, *objs, through_defaults=None):
|
585
|
+
def add(self, *objs: Any, through_defaults: dict[str, Any] | None = None) -> None:
|
565
586
|
self._remove_prefetched_objects()
|
566
587
|
with transaction.atomic(savepoint=False):
|
567
588
|
self._add_items(
|
@@ -580,7 +601,7 @@ class ForwardManyToManyManager(BaseManyToManyManager):
|
|
580
601
|
through_defaults=through_defaults,
|
581
602
|
)
|
582
603
|
|
583
|
-
def remove(self, *objs):
|
604
|
+
def remove(self, *objs: Any) -> None:
|
584
605
|
self._remove_prefetched_objects()
|
585
606
|
self._remove_items(self.source_field_name, self.target_field_name, *objs)
|
586
607
|
|
@@ -592,7 +613,7 @@ class ReverseManyToManyManager(BaseManyToManyManager):
|
|
592
613
|
This manager adds behaviors specific to many-to-many relations.
|
593
614
|
"""
|
594
615
|
|
595
|
-
def __init__(self, instance, rel):
|
616
|
+
def __init__(self, instance: Any, rel: Any):
|
596
617
|
# Set required attributes before calling super().__init__
|
597
618
|
self.model = rel.related_model
|
598
619
|
self.query_field_name = rel.field.name
|
@@ -603,7 +624,7 @@ class ReverseManyToManyManager(BaseManyToManyManager):
|
|
603
624
|
|
604
625
|
super().__init__(instance, rel)
|
605
626
|
|
606
|
-
def _build_remove_filters(self, removed_vals):
|
627
|
+
def _build_remove_filters(self, removed_vals: Any) -> Any:
|
607
628
|
filters = Q.create([(self.source_field_name, self.related_val)])
|
608
629
|
# No need to add a subquery condition if removed_vals is a QuerySet without
|
609
630
|
# filters.
|
@@ -611,11 +632,13 @@ class ReverseManyToManyManager(BaseManyToManyManager):
|
|
611
632
|
not isinstance(removed_vals, QuerySet) or removed_vals._has_filters()
|
612
633
|
)
|
613
634
|
if removed_vals_filters:
|
614
|
-
filters
|
635
|
+
filters = filters & Q.create( # type: ignore[unsupported-operator]
|
636
|
+
[(f"{self.target_field_name}__in", removed_vals)]
|
637
|
+
)
|
615
638
|
# Note: reverse relations are never symmetrical, so no symmetrical logic here
|
616
639
|
return filters
|
617
640
|
|
618
|
-
def add(self, *objs, through_defaults=None):
|
641
|
+
def add(self, *objs: Any, through_defaults: dict[str, Any] | None = None) -> None:
|
619
642
|
self._remove_prefetched_objects()
|
620
643
|
with transaction.atomic(savepoint=False):
|
621
644
|
self._add_items(
|
@@ -626,6 +649,6 @@ class ReverseManyToManyManager(BaseManyToManyManager):
|
|
626
649
|
)
|
627
650
|
# Reverse relations are never symmetrical, so no mirror entry logic
|
628
651
|
|
629
|
-
def remove(self, *objs):
|
652
|
+
def remove(self, *objs: Any) -> None:
|
630
653
|
self._remove_prefetched_objects()
|
631
654
|
self._remove_items(self.source_field_name, self.target_field_name, *objs)
|