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.
Files changed (105) hide show
  1. plain/models/CHANGELOG.md +13 -0
  2. plain/models/aggregates.py +42 -19
  3. plain/models/backends/base/base.py +125 -105
  4. plain/models/backends/base/client.py +11 -3
  5. plain/models/backends/base/creation.py +22 -12
  6. plain/models/backends/base/features.py +10 -4
  7. plain/models/backends/base/introspection.py +29 -16
  8. plain/models/backends/base/operations.py +187 -91
  9. plain/models/backends/base/schema.py +267 -165
  10. plain/models/backends/base/validation.py +12 -3
  11. plain/models/backends/ddl_references.py +85 -43
  12. plain/models/backends/mysql/base.py +29 -26
  13. plain/models/backends/mysql/client.py +7 -2
  14. plain/models/backends/mysql/compiler.py +12 -3
  15. plain/models/backends/mysql/creation.py +5 -2
  16. plain/models/backends/mysql/features.py +24 -22
  17. plain/models/backends/mysql/introspection.py +22 -13
  18. plain/models/backends/mysql/operations.py +106 -39
  19. plain/models/backends/mysql/schema.py +48 -24
  20. plain/models/backends/mysql/validation.py +13 -6
  21. plain/models/backends/postgresql/base.py +41 -34
  22. plain/models/backends/postgresql/client.py +7 -2
  23. plain/models/backends/postgresql/creation.py +10 -5
  24. plain/models/backends/postgresql/introspection.py +15 -8
  25. plain/models/backends/postgresql/operations.py +109 -42
  26. plain/models/backends/postgresql/schema.py +85 -46
  27. plain/models/backends/sqlite3/_functions.py +151 -115
  28. plain/models/backends/sqlite3/base.py +37 -23
  29. plain/models/backends/sqlite3/client.py +7 -1
  30. plain/models/backends/sqlite3/creation.py +9 -5
  31. plain/models/backends/sqlite3/features.py +5 -3
  32. plain/models/backends/sqlite3/introspection.py +32 -16
  33. plain/models/backends/sqlite3/operations.py +125 -42
  34. plain/models/backends/sqlite3/schema.py +82 -58
  35. plain/models/backends/utils.py +52 -29
  36. plain/models/backups/cli.py +8 -6
  37. plain/models/backups/clients.py +16 -7
  38. plain/models/backups/core.py +24 -13
  39. plain/models/base.py +113 -74
  40. plain/models/cli.py +94 -63
  41. plain/models/config.py +1 -1
  42. plain/models/connections.py +23 -7
  43. plain/models/constraints.py +65 -47
  44. plain/models/database_url.py +1 -1
  45. plain/models/db.py +6 -2
  46. plain/models/deletion.py +66 -43
  47. plain/models/entrypoints.py +1 -1
  48. plain/models/enums.py +22 -11
  49. plain/models/exceptions.py +23 -8
  50. plain/models/expressions.py +440 -257
  51. plain/models/fields/__init__.py +253 -202
  52. plain/models/fields/json.py +120 -54
  53. plain/models/fields/mixins.py +12 -8
  54. plain/models/fields/related.py +284 -252
  55. plain/models/fields/related_descriptors.py +31 -22
  56. plain/models/fields/related_lookups.py +23 -11
  57. plain/models/fields/related_managers.py +81 -47
  58. plain/models/fields/reverse_related.py +58 -55
  59. plain/models/forms.py +89 -63
  60. plain/models/functions/comparison.py +71 -18
  61. plain/models/functions/datetime.py +79 -29
  62. plain/models/functions/math.py +43 -10
  63. plain/models/functions/mixins.py +24 -7
  64. plain/models/functions/text.py +104 -25
  65. plain/models/functions/window.py +12 -6
  66. plain/models/indexes.py +52 -28
  67. plain/models/lookups.py +228 -153
  68. plain/models/migrations/autodetector.py +86 -43
  69. plain/models/migrations/exceptions.py +7 -3
  70. plain/models/migrations/executor.py +33 -7
  71. plain/models/migrations/graph.py +79 -50
  72. plain/models/migrations/loader.py +45 -22
  73. plain/models/migrations/migration.py +23 -18
  74. plain/models/migrations/operations/base.py +37 -19
  75. plain/models/migrations/operations/fields.py +89 -42
  76. plain/models/migrations/operations/models.py +245 -143
  77. plain/models/migrations/operations/special.py +82 -25
  78. plain/models/migrations/optimizer.py +7 -2
  79. plain/models/migrations/questioner.py +58 -31
  80. plain/models/migrations/recorder.py +18 -11
  81. plain/models/migrations/serializer.py +50 -39
  82. plain/models/migrations/state.py +220 -133
  83. plain/models/migrations/utils.py +29 -13
  84. plain/models/migrations/writer.py +17 -14
  85. plain/models/options.py +63 -56
  86. plain/models/otel.py +16 -6
  87. plain/models/preflight.py +35 -12
  88. plain/models/query.py +323 -228
  89. plain/models/query_utils.py +93 -58
  90. plain/models/registry.py +34 -16
  91. plain/models/sql/compiler.py +146 -97
  92. plain/models/sql/datastructures.py +38 -25
  93. plain/models/sql/query.py +255 -169
  94. plain/models/sql/subqueries.py +32 -21
  95. plain/models/sql/where.py +54 -29
  96. plain/models/test/pytest.py +15 -11
  97. plain/models/test/utils.py +4 -2
  98. plain/models/transaction.py +20 -7
  99. plain/models/utils.py +13 -5
  100. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/METADATA +1 -1
  101. plain_models-0.50.0.dist-info/RECORD +122 -0
  102. plain_models-0.49.2.dist-info/RECORD +0 -122
  103. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/WHEEL +0 -0
  104. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/entry_points.txt +0 -0
  105. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/licenses/LICENSE +0 -0
@@ -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(scope_model, 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(function, model, *related_models, **kwargs):
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 = self.remote_field.get_accessor_name() # i. e. "model_set"
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(model, related, field):
329
- field.remote_field.model = related
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, cls, self.remote_field.model, field=self
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(self, reverse_join=False):
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(self, value, connection, prepared=False):
731
- return self.target_field.get_db_prep_value(value, connection, prepared)
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(self, from_model=None, **kwargs):
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(self, direct=False, filtered_relation=None):
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(_, model, field):
1245
- field.remote_field.through = model
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, cls, self.remote_field.through, field=self
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}