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.
Files changed (108) hide show
  1. plain/models/CHANGELOG.md +27 -0
  2. plain/models/README.md +26 -42
  3. plain/models/__init__.py +2 -0
  4. plain/models/aggregates.py +42 -19
  5. plain/models/backends/base/base.py +125 -105
  6. plain/models/backends/base/client.py +11 -3
  7. plain/models/backends/base/creation.py +24 -14
  8. plain/models/backends/base/features.py +10 -4
  9. plain/models/backends/base/introspection.py +37 -20
  10. plain/models/backends/base/operations.py +187 -91
  11. plain/models/backends/base/schema.py +338 -218
  12. plain/models/backends/base/validation.py +13 -4
  13. plain/models/backends/ddl_references.py +85 -43
  14. plain/models/backends/mysql/base.py +29 -26
  15. plain/models/backends/mysql/client.py +7 -2
  16. plain/models/backends/mysql/compiler.py +13 -4
  17. plain/models/backends/mysql/creation.py +5 -2
  18. plain/models/backends/mysql/features.py +24 -22
  19. plain/models/backends/mysql/introspection.py +22 -13
  20. plain/models/backends/mysql/operations.py +107 -40
  21. plain/models/backends/mysql/schema.py +52 -28
  22. plain/models/backends/mysql/validation.py +13 -6
  23. plain/models/backends/postgresql/base.py +41 -34
  24. plain/models/backends/postgresql/client.py +7 -2
  25. plain/models/backends/postgresql/creation.py +10 -5
  26. plain/models/backends/postgresql/introspection.py +15 -8
  27. plain/models/backends/postgresql/operations.py +110 -43
  28. plain/models/backends/postgresql/schema.py +88 -49
  29. plain/models/backends/sqlite3/_functions.py +151 -115
  30. plain/models/backends/sqlite3/base.py +37 -23
  31. plain/models/backends/sqlite3/client.py +7 -1
  32. plain/models/backends/sqlite3/creation.py +9 -5
  33. plain/models/backends/sqlite3/features.py +5 -3
  34. plain/models/backends/sqlite3/introspection.py +32 -16
  35. plain/models/backends/sqlite3/operations.py +126 -43
  36. plain/models/backends/sqlite3/schema.py +127 -92
  37. plain/models/backends/utils.py +52 -29
  38. plain/models/backups/cli.py +8 -6
  39. plain/models/backups/clients.py +16 -7
  40. plain/models/backups/core.py +24 -13
  41. plain/models/base.py +221 -229
  42. plain/models/cli.py +98 -67
  43. plain/models/config.py +1 -1
  44. plain/models/connections.py +23 -7
  45. plain/models/constraints.py +79 -56
  46. plain/models/database_url.py +1 -1
  47. plain/models/db.py +6 -2
  48. plain/models/deletion.py +80 -56
  49. plain/models/entrypoints.py +1 -1
  50. plain/models/enums.py +22 -11
  51. plain/models/exceptions.py +23 -8
  52. plain/models/expressions.py +441 -258
  53. plain/models/fields/__init__.py +272 -217
  54. plain/models/fields/json.py +123 -57
  55. plain/models/fields/mixins.py +12 -8
  56. plain/models/fields/related.py +324 -290
  57. plain/models/fields/related_descriptors.py +33 -24
  58. plain/models/fields/related_lookups.py +24 -12
  59. plain/models/fields/related_managers.py +102 -79
  60. plain/models/fields/reverse_related.py +66 -63
  61. plain/models/forms.py +101 -75
  62. plain/models/functions/comparison.py +71 -18
  63. plain/models/functions/datetime.py +79 -29
  64. plain/models/functions/math.py +43 -10
  65. plain/models/functions/mixins.py +24 -7
  66. plain/models/functions/text.py +104 -25
  67. plain/models/functions/window.py +12 -6
  68. plain/models/indexes.py +57 -32
  69. plain/models/lookups.py +228 -153
  70. plain/models/meta.py +505 -0
  71. plain/models/migrations/autodetector.py +86 -43
  72. plain/models/migrations/exceptions.py +7 -3
  73. plain/models/migrations/executor.py +33 -7
  74. plain/models/migrations/graph.py +79 -50
  75. plain/models/migrations/loader.py +45 -22
  76. plain/models/migrations/migration.py +23 -18
  77. plain/models/migrations/operations/base.py +38 -20
  78. plain/models/migrations/operations/fields.py +95 -48
  79. plain/models/migrations/operations/models.py +246 -142
  80. plain/models/migrations/operations/special.py +82 -25
  81. plain/models/migrations/optimizer.py +7 -2
  82. plain/models/migrations/questioner.py +58 -31
  83. plain/models/migrations/recorder.py +27 -16
  84. plain/models/migrations/serializer.py +50 -39
  85. plain/models/migrations/state.py +232 -156
  86. plain/models/migrations/utils.py +30 -14
  87. plain/models/migrations/writer.py +17 -14
  88. plain/models/options.py +189 -518
  89. plain/models/otel.py +16 -6
  90. plain/models/preflight.py +42 -17
  91. plain/models/query.py +400 -251
  92. plain/models/query_utils.py +109 -69
  93. plain/models/registry.py +40 -21
  94. plain/models/sql/compiler.py +190 -127
  95. plain/models/sql/datastructures.py +38 -25
  96. plain/models/sql/query.py +320 -225
  97. plain/models/sql/subqueries.py +36 -25
  98. plain/models/sql/where.py +54 -29
  99. plain/models/test/pytest.py +15 -11
  100. plain/models/test/utils.py +4 -2
  101. plain/models/transaction.py +20 -7
  102. plain/models/utils.py +17 -6
  103. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/METADATA +27 -43
  104. plain_models-0.51.0.dist-info/RECORD +123 -0
  105. plain_models-0.49.2.dist-info/RECORD +0 -122
  106. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/WHEEL +0 -0
  107. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/entry_points.txt +0 -0
  108. {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(queryset, field_name, instances):
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
- # Use the base queryset class for this model
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(self, instances, queryset=None):
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.base_queryset_class(model=self.model)
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._meta.object_name}' instance expected, got {obj!r}"
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._meta.base_queryset.filter(id__in=ids).update(
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.base_queryset_class(model=self.model).create(**kwargs)
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.base_queryset_class(model=self.model).get_or_create(**kwargs)
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.base_queryset_class(model=self.model).update_or_create(**kwargs)
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._meta.object_name}' instance expected, got {obj!r}"
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._meta.get_field(self.source_field_name)
296
- self.target_field = self.through._meta.get_field(self.target_field_name)
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.base_queryset_class(model=self.model)
336
+ queryset = self.model.query
334
337
  return self._apply_rel_filters(queryset)
335
338
 
336
- def get_prefetch_queryset(self, instances, queryset=None):
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.base_queryset_class(model=self.model)
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._meta.get_field(self.source_field_name)
347
- join_table = fk.model._meta.db_table
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(self, objs, *, clear=False, through_defaults=None):
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(self, *, through_defaults=None, **kwargs):
410
- new_obj = self.base_queryset_class(model=self.model).create(**kwargs)
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(self, *, through_defaults=None, **kwargs):
415
- obj, created = self.base_queryset_class(model=self.model).get_or_create(
416
- **kwargs
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(self, *, through_defaults=None, **kwargs):
425
- obj, created = self.base_queryset_class(model=self.model).update_or_create(
426
- **kwargs
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._meta.get_field(target_field_name)
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._meta.object_name}' instance expected, got {obj!r}"
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(self, source_field_name, target_field_name, 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, source_field_name, target_field_name, *objs, through_defaults=None
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(self, source_field_name, target_field_name, *objs):
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.base_queryset_class(model=self.model)
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 &= Q.create([(f"{self.target_field_name}__in", removed_vals)])
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 &= Q.create(
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 |= symmetrical_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 &= Q.create([(f"{self.target_field_name}__in", removed_vals)])
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)