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
@@ -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.model_options.package_label}.{relation}"
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
@@ -71,11 +82,11 @@ def lazy_related_operation(function, model, *related_models, **kwargs):
71
82
  references will be resolved relative to `model`.
72
83
 
73
84
  This is a convenience wrapper for `Packages.lazy_model_operation` - the app
74
- registry model used is the one found in `model._meta.models_registry`.
85
+ registry model used is the one found in `model._model_meta.models_registry`.
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._model_meta.models_registry
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.model_options.object_name}.{self.name}. Related name must be a valid Python identifier.",
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.meta.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.model_options.object_name
179
190
  )
180
191
  if rel_is_missing and rel_is_string:
181
192
  return [
@@ -190,16 +201,15 @@ 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] = []
199
209
 
200
210
  # f.remote_field.model may be a string instead of a model. Skip if
201
211
  # model name is not resolved.
202
- if not isinstance(self.remote_field.model, ModelBase):
212
+ if not isinstance(self.remote_field.model, ModelBase): # type: ignore[attr-defined]
203
213
  return []
204
214
 
205
215
  # Consider that we are checking field `Model.foreign` and the models
@@ -213,29 +223,32 @@ class RelatedField(FieldCacheMixin, Field):
213
223
  # foreign = models.ForeignKey(Target)
214
224
  # m2m = models.ManyToManyField(Target)
215
225
 
216
- # rel_opts.object_name == "Target"
217
- rel_opts = self.remote_field.model._meta
226
+ # rel_options.object_name == "Target"
227
+ rel_meta = self.remote_field.model._model_meta
228
+ rel_options = self.remote_field.model.model_options
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
- field_name = f"{opts.label}.{self.name}"
238
+ field_name = f"{self.model.model_options.label}.{self.name}"
226
239
 
227
240
  # Check clashes between accessor or reverse query name of `field`
228
241
  # and any other field name -- i.e. accessor for Model.foreign is
229
242
  # model_set and it clashes with Target.model_set.
230
- potential_clashes = rel_opts.fields + rel_opts.many_to_many
243
+ potential_clashes = rel_meta.fields + rel_meta.many_to_many
231
244
  for clash_field in potential_clashes:
232
245
  # i.e. "package_label.Target.model_set".
233
- clash_name = f"{rel_opts.label}.{clash_field.name}"
246
+ clash_name = f"{rel_options.label}.{clash_field.name}"
234
247
  if not rel_is_hidden and clash_field.name == rel_name:
235
248
  errors.append(
236
249
  PreflightResult(
237
250
  fix=(
238
- f"Reverse accessor '{rel_opts.object_name}.{rel_name}' "
251
+ f"Reverse accessor '{rel_options.object_name}.{rel_name}' "
239
252
  f"for '{field_name}' clashes with field name "
240
253
  f"'{clash_name}'. "
241
254
  f"Rename field '{clash_name}', or add/change a related_name "
@@ -262,17 +275,15 @@ class RelatedField(FieldCacheMixin, Field):
262
275
  # Check clashes between accessors/reverse query names of `field` and
263
276
  # any other field accessor -- i. e. Model.foreign accessor clashes with
264
277
  # Model.m2m accessor.
265
- potential_clashes = (r for r in rel_opts.related_objects if r.field is not self)
278
+ potential_clashes = (r for r in rel_meta.related_objects if r.field is not self)
266
279
  for clash_field in potential_clashes:
267
280
  # i.e. "package_label.Model.m2m".
268
- clash_name = (
269
- f"{clash_field.related_model._meta.label}.{clash_field.field.name}"
270
- )
281
+ clash_name = f"{clash_field.related_model.model_options.label}.{clash_field.field.name}"
271
282
  if not rel_is_hidden and clash_field.get_accessor_name() == rel_name:
272
283
  errors.append(
273
284
  PreflightResult(
274
285
  fix=(
275
- f"Reverse accessor '{rel_opts.object_name}.{rel_name}' "
286
+ f"Reverse accessor '{rel_options.object_name}.{rel_name}' "
276
287
  f"for '{field_name}' clashes with reverse accessor for "
277
288
  f"'{clash_name}'. "
278
289
  "Add or change a related_name argument "
@@ -299,42 +310,47 @@ class RelatedField(FieldCacheMixin, Field):
299
310
 
300
311
  return errors
301
312
 
302
- def db_type(self, connection):
313
+ def db_type(self, connection: BaseDatabaseWrapper) -> None:
303
314
  # By default related field will not have a column as it relates to
304
315
  # columns from another table.
305
316
  return None
306
317
 
307
- def contribute_to_class(self, cls, name):
308
- super().contribute_to_class(cls, name)
318
+ def contribute_to_class(self, cls: type[Model], name: str) -> None:
319
+ super().contribute_to_class(cls, name) # type: ignore[misc]
309
320
 
310
- self.opts = cls._meta
321
+ self.meta = cls._model_meta
311
322
 
312
- if self.remote_field.related_name:
313
- related_name = self.remote_field.related_name
323
+ if self.remote_field.related_name: # type: ignore[attr-defined]
324
+ related_name = self.remote_field.related_name # type: ignore[attr-defined]
314
325
  related_name %= {
315
326
  "class": cls.__name__.lower(),
316
- "model_name": cls._meta.model_name.lower(),
317
- "package_label": cls._meta.package_label.lower(),
327
+ "model_name": cls.model_options.model_name.lower(),
328
+ "package_label": cls.model_options.package_label.lower(),
318
329
  }
319
- self.remote_field.related_name = related_name
330
+ self.remote_field.related_name = related_name # type: ignore[attr-defined]
320
331
 
321
- if self.remote_field.related_query_name:
322
- related_query_name = self.remote_field.related_query_name % {
332
+ if self.remote_field.related_query_name: # type: ignore[attr-defined]
333
+ related_query_name = self.remote_field.related_query_name % { # type: ignore[attr-defined]
323
334
  "class": cls.__name__.lower(),
324
- "package_label": cls._meta.package_label.lower(),
335
+ "package_label": cls.model_options.package_label.lower(),
325
336
  }
326
- self.remote_field.related_query_name = related_query_name
337
+ self.remote_field.related_query_name = related_query_name # type: ignore[attr-defined]
327
338
 
328
- def resolve_related_class(model, related, field):
329
- field.remote_field.model = related
339
+ def resolve_related_class(
340
+ model: type[Model], related: type[Model], field: RelatedField
341
+ ) -> None:
342
+ field.remote_field.model = related # type: ignore[attr-defined]
330
343
  field.do_related_class(related, model)
331
344
 
332
345
  lazy_related_operation(
333
- resolve_related_class, cls, self.remote_field.model, field=self
346
+ resolve_related_class,
347
+ cls,
348
+ self.remote_field.model,
349
+ field=self, # type: ignore[attr-defined]
334
350
  )
335
351
 
336
- def deconstruct(self):
337
- name, path, args, kwargs = super().deconstruct()
352
+ def deconstruct(self) -> tuple[str, str, list[Any], dict[str, Any]]:
353
+ name, path, args, kwargs = super().deconstruct() # type: ignore[misc]
338
354
  if self._limit_choices_to:
339
355
  kwargs["limit_choices_to"] = self._limit_choices_to
340
356
  if self._related_name is not None:
@@ -343,7 +359,7 @@ class RelatedField(FieldCacheMixin, Field):
343
359
  kwargs["related_query_name"] = self._related_query_name
344
360
  return name, path, args, kwargs
345
361
 
346
- def get_forward_related_filter(self, obj):
362
+ def get_forward_related_filter(self, obj: Model) -> dict[str, Any]:
347
363
  """
348
364
  Return the keyword arguments that when supplied to
349
365
  self.model.object.filter(), would select all instances related through
@@ -353,10 +369,10 @@ class RelatedField(FieldCacheMixin, Field):
353
369
  """
354
370
  return {
355
371
  f"{self.name}__{rh_field.name}": getattr(obj, rh_field.attname)
356
- for _, rh_field in self.related_fields
372
+ for _, rh_field in self.related_fields # type: ignore[attr-defined]
357
373
  }
358
374
 
359
- def get_reverse_related_filter(self, obj):
375
+ def get_reverse_related_filter(self, obj: Model) -> Any:
360
376
  """
361
377
  Complement to get_forward_related_filter(). Return the keyword
362
378
  arguments that when passed to self.related_field.model.object.filter()
@@ -366,47 +382,49 @@ class RelatedField(FieldCacheMixin, Field):
366
382
  return Q.create(
367
383
  [
368
384
  (rh_field.attname, getattr(obj, lh_field.attname))
369
- for lh_field, rh_field in self.related_fields
385
+ for lh_field, rh_field in self.related_fields # type: ignore[attr-defined]
370
386
  ]
371
387
  )
372
388
 
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()
389
+ def set_attributes_from_rel(self) -> None:
390
+ self.name = self.name or (
391
+ self.remote_field.model.model_options.model_name + "_" + "id"
392
+ )
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.model.model_options.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,19 +460,19 @@ 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
- try:
455
- to._meta.model_name
456
- except AttributeError:
457
- if not isinstance(to, str):
472
+ if not isinstance(to, str):
473
+ try:
474
+ to.model_options.model_name
475
+ except AttributeError:
458
476
  raise TypeError(
459
477
  f"{self.__class__.__name__}({to!r}) is invalid. First parameter to ForeignKey must be "
460
478
  f"either a model, a model name, or the string {RECURSIVE_RELATIONSHIP_CONSTANT!r}"
@@ -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,14 +563,14 @@ 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
+ meta = self.remote_field.model._model_meta
569
+ from_meta = self.model._model_meta
550
570
  return [
551
571
  PathInfo(
552
- from_opts=from_opts,
553
- to_opts=opts,
572
+ from_meta=from_meta,
573
+ to_meta=meta,
554
574
  target_fields=self.foreign_related_fields,
555
575
  join_field=self,
556
576
  m2m=False,
@@ -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
+ meta = self.model._model_meta
589
+ from_meta = self.remote_field.model._model_meta
570
590
  return [
571
591
  PathInfo(
572
- from_opts=from_opts,
573
- to_opts=opts,
574
- target_fields=(opts.get_field("id"),),
575
- join_field=self.remote_field,
576
- m2m=not self.primary_key,
592
+ from_meta=from_meta,
593
+ to_meta=meta,
594
+ target_fields=(meta.get_field("id"),),
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:
602
- cls._meta.related_fkey_lookups.append(
603
- self.remote_field.limit_choices_to
621
+ if self.remote_field.limit_choices_to: # type: ignore[attr-defined]
622
+ cls._model_meta.related_fkey_lookups.append(
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.model_options.label_lower
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._model_meta.base_queryset.filter(
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.model_options.model_name,
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._model_meta.get_field("id")
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.model_options.label}.{self.name}' refers to field '{to_field.name}' which is not local to model "
723
+ f"'{self.remote_field.model.model_options.label}'."
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,20 +813,20 @@ 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
- try:
805
- to._meta
806
- except AttributeError:
807
- if not isinstance(to, str):
826
+ if not isinstance(to, str):
827
+ try:
828
+ to._model_meta
829
+ except AttributeError:
808
830
  raise TypeError(
809
831
  f"{self.__class__.__name__}({to!r}) is invalid. First parameter to ManyToManyField "
810
832
  f"must be either a model, a model name, or the string {RECURSIVE_RELATIONSHIP_CONSTANT!r}"
@@ -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,15 +912,17 @@ class ManyToManyField(RelatedField):
890
912
 
891
913
  return warnings
892
914
 
893
- def _check_relationship_model(self, from_model=None, **kwargs):
894
- if hasattr(self.remote_field.through, "_meta"):
895
- qualified_model_name = f"{self.remote_field.through._meta.package_label}.{self.remote_field.through.__name__}"
915
+ def _check_relationship_model(
916
+ self, from_model: type[Model] | None = None, **kwargs: Any
917
+ ) -> list[PreflightResult]:
918
+ if hasattr(self.remote_field.through, "_model_meta"):
919
+ qualified_model_name = f"{self.remote_field.through.model_options.package_label}.{self.remote_field.through.__name__}"
896
920
  else:
897
921
  qualified_model_name = self.remote_field.through
898
922
 
899
923
  errors = []
900
924
 
901
- if self.remote_field.through not in self.opts.models_registry.get_models():
925
+ if self.remote_field.through not in self.meta.models_registry.get_models():
902
926
  # The relationship model is not installed.
903
927
  errors.append(
904
928
  PreflightResult(
@@ -920,18 +944,20 @@ class ManyToManyField(RelatedField):
920
944
  )
921
945
  # Set some useful local variables
922
946
  to_model = resolve_relation(from_model, self.remote_field.model)
923
- from_model_name = from_model._meta.object_name
947
+ from_model_name = from_model.model_options.object_name
924
948
  if isinstance(to_model, str):
925
949
  to_model_name = to_model
926
950
  else:
927
- to_model_name = to_model._meta.object_name
928
- relationship_model_name = self.remote_field.through._meta.object_name
951
+ to_model_name = to_model.model_options.object_name
952
+ relationship_model_name = (
953
+ self.remote_field.through.model_options.object_name
954
+ )
929
955
  self_referential = from_model == to_model
930
956
  # Count foreign keys in intermediate model
931
957
  if self_referential:
932
958
  seen_self = sum(
933
959
  from_model == getattr(field.remote_field, "model", None)
934
- for field in self.remote_field.through._meta.fields
960
+ for field in self.remote_field.through._model_meta.fields
935
961
  )
936
962
 
937
963
  if seen_self > 2 and not self.remote_field.through_fields:
@@ -953,11 +979,11 @@ class ManyToManyField(RelatedField):
953
979
  # Count foreign keys in relationship model
954
980
  seen_from = sum(
955
981
  from_model == getattr(field.remote_field, "model", None)
956
- for field in self.remote_field.through._meta.fields
982
+ for field in self.remote_field.through._model_meta.fields
957
983
  )
958
984
  seen_to = sum(
959
985
  to_model == getattr(field.remote_field, "model", None)
960
- for field in self.remote_field.through._meta.fields
986
+ for field in self.remote_field.through._model_meta.fields
961
987
  )
962
988
 
963
989
  if seen_from > 1 and not self.remote_field.through_fields:
@@ -1054,7 +1080,7 @@ class ManyToManyField(RelatedField):
1054
1080
  (target_field_name, target),
1055
1081
  ):
1056
1082
  possible_field_names = []
1057
- for f in through._meta.fields:
1083
+ for f in through._model_meta.fields:
1058
1084
  if (
1059
1085
  hasattr(f, "remote_field")
1060
1086
  and getattr(f.remote_field, "model", None) == related_model
@@ -1064,7 +1090,7 @@ class ManyToManyField(RelatedField):
1064
1090
  fix = (
1065
1091
  "Did you mean one of the following foreign keys to '{}': "
1066
1092
  "{}?".format(
1067
- related_model._meta.object_name,
1093
+ related_model.model_options.object_name,
1068
1094
  ", ".join(possible_field_names),
1069
1095
  )
1070
1096
  )
@@ -1072,7 +1098,7 @@ class ManyToManyField(RelatedField):
1072
1098
  fix = ""
1073
1099
 
1074
1100
  try:
1075
- field = through._meta.get_field(field_name)
1101
+ field = through._model_meta.get_field(field_name)
1076
1102
  except FieldDoesNotExist:
1077
1103
  errors.append(
1078
1104
  PreflightResult(
@@ -1089,7 +1115,7 @@ class ManyToManyField(RelatedField):
1089
1115
  ):
1090
1116
  errors.append(
1091
1117
  PreflightResult(
1092
- fix=f"'{through._meta.object_name}.{field_name}' is not a foreign key to '{related_model._meta.object_name}'. {fix}",
1118
+ fix=f"'{through.model_options.object_name}.{field_name}' is not a foreign key to '{related_model.model_options.object_name}'. {fix}",
1093
1119
  obj=self,
1094
1120
  id="fields.m2m_through_field_not_fk_to_model",
1095
1121
  )
@@ -1097,19 +1123,19 @@ class ManyToManyField(RelatedField):
1097
1123
 
1098
1124
  return errors
1099
1125
 
1100
- def _check_table_uniqueness(self, **kwargs):
1101
- if isinstance(self.remote_field.through, str):
1126
+ def _check_table_uniqueness(self, **kwargs: Any) -> list[PreflightResult]:
1127
+ if isinstance(self.remote_field.through, str): # type: ignore[attr-defined]
1102
1128
  return []
1103
1129
  registered_tables = {
1104
- model._meta.db_table: model
1105
- for model in self.opts.models_registry.get_models()
1106
- if model != self.remote_field.through
1130
+ model.model_options.db_table: model
1131
+ for model in self.meta.models_registry.get_models() # type: ignore[attr-defined]
1132
+ if model != self.remote_field.through # type: ignore[attr-defined]
1107
1133
  }
1108
- m2m_db_table = self.m2m_db_table()
1134
+ m2m_db_table = self.m2m_db_table() # type: ignore[attr-defined]
1109
1135
  model = registered_tables.get(m2m_db_table)
1110
1136
  # Check if there's already a m2m field using the same through model.
1111
- if model and model != self.remote_field.through:
1112
- clashing_obj = model._meta.label
1137
+ if model and model != self.remote_field.through: # type: ignore[attr-defined]
1138
+ clashing_obj = model.model_options.label
1113
1139
  return [
1114
1140
  PreflightResult(
1115
1141
  fix=(
@@ -1123,71 +1149,73 @@ class ManyToManyField(RelatedField):
1123
1149
  ]
1124
1150
  return []
1125
1151
 
1126
- def deconstruct(self):
1152
+ def deconstruct(self) -> tuple[str, str, list[Any], dict[str, Any]]:
1127
1153
  name, path, args, kwargs = super().deconstruct()
1128
1154
 
1129
- if self.remote_field.db_constraint is not True:
1130
- kwargs["db_constraint"] = self.remote_field.db_constraint
1155
+ if self.remote_field.db_constraint is not True: # type: ignore[attr-defined]
1156
+ kwargs["db_constraint"] = self.remote_field.db_constraint # type: ignore[attr-defined]
1131
1157
 
1132
1158
  # 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(".")
1159
+ if isinstance(self.remote_field.model, str): # type: ignore[attr-defined]
1160
+ if "." in self.remote_field.model: # type: ignore[attr-defined]
1161
+ package_label, model_name = self.remote_field.model.split(".") # type: ignore[attr-defined]
1136
1162
  kwargs["to"] = f"{package_label}.{model_name.lower()}"
1137
1163
  else:
1138
- kwargs["to"] = self.remote_field.model.lower()
1164
+ kwargs["to"] = self.remote_field.model.lower() # type: ignore[attr-defined]
1139
1165
  else:
1140
- kwargs["to"] = self.remote_field.model._meta.label_lower
1166
+ kwargs["to"] = self.remote_field.model.model_options.label_lower
1141
1167
 
1142
- if isinstance(self.remote_field.through, str):
1143
- kwargs["through"] = self.remote_field.through
1168
+ if isinstance(self.remote_field.through, str): # type: ignore[attr-defined]
1169
+ kwargs["through"] = self.remote_field.through # type: ignore[attr-defined]
1144
1170
  else:
1145
- kwargs["through"] = self.remote_field.through._meta.label
1171
+ kwargs["through"] = self.remote_field.through.model_options.label
1146
1172
 
1147
1173
  return name, path, args, kwargs
1148
1174
 
1149
- def _get_path_info(self, direct=False, filtered_relation=None):
1175
+ def _get_path_info(
1176
+ self, direct: bool = False, filtered_relation: Any = None
1177
+ ) -> list[PathInfo]:
1150
1178
  """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())
1179
+ int_model = self.remote_field.through # type: ignore[attr-defined]
1180
+ linkfield1 = int_model._model_meta.get_field(self.m2m_field_name())
1181
+ linkfield2 = int_model._model_meta.get_field(self.m2m_reverse_field_name())
1154
1182
  if direct:
1155
- join1infos = linkfield1.reverse_path_infos
1183
+ join1infos = linkfield1.reverse_path_infos # type: ignore[attr-defined]
1156
1184
  if filtered_relation:
1157
- join2infos = linkfield2.get_path_info(filtered_relation)
1185
+ join2infos = linkfield2.get_path_info(filtered_relation) # type: ignore[attr-defined]
1158
1186
  else:
1159
- join2infos = linkfield2.path_infos
1187
+ join2infos = linkfield2.path_infos # type: ignore[attr-defined]
1160
1188
  else:
1161
- join1infos = linkfield2.reverse_path_infos
1189
+ join1infos = linkfield2.reverse_path_infos # type: ignore[attr-defined]
1162
1190
  if filtered_relation:
1163
- join2infos = linkfield1.get_path_info(filtered_relation)
1191
+ join2infos = linkfield1.get_path_info(filtered_relation) # type: ignore[attr-defined]
1164
1192
  else:
1165
- join2infos = linkfield1.path_infos
1193
+ join2infos = linkfield1.path_infos # type: ignore[attr-defined]
1166
1194
 
1167
1195
  return [*join1infos, *join2infos]
1168
1196
 
1169
- def get_path_info(self, filtered_relation=None):
1197
+ def get_path_info(self, filtered_relation: Any = None) -> list[PathInfo]:
1170
1198
  return self._get_path_info(direct=True, filtered_relation=filtered_relation)
1171
1199
 
1172
1200
  @cached_property
1173
- def path_infos(self):
1201
+ def path_infos(self) -> list[PathInfo]:
1174
1202
  return self.get_path_info()
1175
1203
 
1176
- def get_reverse_path_info(self, filtered_relation=None):
1204
+ def get_reverse_path_info(self, filtered_relation: Any = None) -> list[PathInfo]:
1177
1205
  return self._get_path_info(direct=False, filtered_relation=filtered_relation)
1178
1206
 
1179
1207
  @cached_property
1180
- def reverse_path_infos(self):
1208
+ def reverse_path_infos(self) -> list[PathInfo]:
1181
1209
  return self.get_reverse_path_info()
1182
1210
 
1183
- def _get_m2m_db_table(self):
1211
+ def _get_m2m_db_table(self) -> str:
1184
1212
  """
1185
1213
  Function that can be curried to provide the m2m table name for this
1186
1214
  relation.
1187
1215
  """
1188
- return self.remote_field.through._meta.db_table
1216
+ return self.remote_field.through.model_options.db_table
1189
1217
 
1190
- def _get_m2m_attr(self, related, attr):
1218
+ def _get_m2m_attr(self, related: Any, attr: str) -> Any:
1191
1219
  """
1192
1220
  Function that can be curried to provide the source accessor or DB
1193
1221
  column name for the m2m table.
@@ -1195,20 +1223,21 @@ class ManyToManyField(RelatedField):
1195
1223
  cache_attr = f"_m2m_{attr}_cache"
1196
1224
  if hasattr(self, cache_attr):
1197
1225
  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]
1226
+ if self.remote_field.through_fields is not None: # type: ignore[attr-defined]
1227
+ link_field_name: str | None = self.remote_field.through_fields[0] # type: ignore[attr-defined]
1200
1228
  else:
1201
1229
  link_field_name = None
1202
- for f in self.remote_field.through._meta.fields:
1230
+ for f in self.remote_field.through._model_meta.fields:
1203
1231
  if (
1204
- f.is_relation
1205
- and f.remote_field.model == related.related_model
1232
+ f.is_relation # type: ignore[attr-defined]
1233
+ and f.remote_field.model == related.related_model # type: ignore[attr-defined]
1206
1234
  and (link_field_name is None or link_field_name == f.name)
1207
1235
  ):
1208
1236
  setattr(self, cache_attr, getattr(f, attr))
1209
1237
  return getattr(self, cache_attr)
1238
+ return None
1210
1239
 
1211
- def _get_m2m_reverse_attr(self, related, attr):
1240
+ def _get_m2m_reverse_attr(self, related: Any, attr: str) -> Any:
1212
1241
  """
1213
1242
  Function that can be curried to provide the related accessor or DB
1214
1243
  column name for the m2m table.
@@ -1217,12 +1246,12 @@ class ManyToManyField(RelatedField):
1217
1246
  if hasattr(self, cache_attr):
1218
1247
  return getattr(self, cache_attr)
1219
1248
  found = False
1220
- if self.remote_field.through_fields is not None:
1221
- link_field_name = self.remote_field.through_fields[1]
1249
+ if self.remote_field.through_fields is not None: # type: ignore[attr-defined]
1250
+ link_field_name: str | None = self.remote_field.through_fields[1] # type: ignore[attr-defined]
1222
1251
  else:
1223
1252
  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:
1253
+ for f in self.remote_field.through._model_meta.fields:
1254
+ if f.is_relation and f.remote_field.model == related.model: # type: ignore[attr-defined]
1226
1255
  if link_field_name is None and related.related_model == related.model:
1227
1256
  # If this is an m2m-intermediate to self,
1228
1257
  # the first foreign key you find will be
@@ -1238,64 +1267,69 @@ class ManyToManyField(RelatedField):
1238
1267
  break
1239
1268
  return getattr(self, cache_attr)
1240
1269
 
1241
- def contribute_to_class(self, cls, name):
1270
+ def contribute_to_class(self, cls: type[Model], name: str) -> None:
1242
1271
  super().contribute_to_class(cls, name)
1243
1272
 
1244
- def resolve_through_model(_, model, field):
1245
- field.remote_field.through = model
1273
+ def resolve_through_model(
1274
+ _: Any, model: type[Model], field: ManyToManyField
1275
+ ) -> None:
1276
+ field.remote_field.through = model # type: ignore[attr-defined]
1246
1277
 
1247
1278
  lazy_related_operation(
1248
- resolve_through_model, cls, self.remote_field.through, field=self
1279
+ resolve_through_model,
1280
+ cls,
1281
+ self.remote_field.through,
1282
+ field=self, # type: ignore[attr-defined]
1249
1283
  )
1250
1284
 
1251
1285
  # Add the descriptor for the m2m relation.
1252
- setattr(cls, self.name, ForwardManyToManyDescriptor(self.remote_field))
1286
+ setattr(cls, self.name, ForwardManyToManyDescriptor(self.remote_field)) # type: ignore[attr-defined]
1253
1287
 
1254
1288
  # Set up the accessor for the m2m table name for the relation.
1255
- self.m2m_db_table = self._get_m2m_db_table
1289
+ self.m2m_db_table = self._get_m2m_db_table # type: ignore[method-assign]
1256
1290
 
1257
- def contribute_to_related_class(self, cls, related):
1291
+ def contribute_to_related_class(self, cls: type[Model], related: Any) -> None:
1258
1292
  # Internal M2Ms (i.e., those with a related name ending with '+')
1259
1293
  # don't get a related descriptor.
1260
- if not self.remote_field.is_hidden():
1294
+ if not self.remote_field.is_hidden(): # type: ignore[attr-defined]
1261
1295
  setattr(
1262
1296
  cls,
1263
1297
  related.get_accessor_name(),
1264
- ReverseManyToManyDescriptor(self.remote_field),
1298
+ ReverseManyToManyDescriptor(self.remote_field), # type: ignore[attr-defined]
1265
1299
  )
1266
1300
 
1267
1301
  # 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")
1302
+ self.m2m_column_name = partial(self._get_m2m_attr, related, "column") # type: ignore[method-assign]
1303
+ self.m2m_reverse_name = partial(self._get_m2m_reverse_attr, related, "column") # type: ignore[method-assign]
1270
1304
 
1271
- self.m2m_field_name = partial(self._get_m2m_attr, related, "name")
1272
- self.m2m_reverse_field_name = partial(
1305
+ self.m2m_field_name = partial(self._get_m2m_attr, related, "name") # type: ignore[method-assign]
1306
+ self.m2m_reverse_field_name = partial( # type: ignore[method-assign]
1273
1307
  self._get_m2m_reverse_attr, related, "name"
1274
1308
  )
1275
1309
 
1276
1310
  get_m2m_rel = partial(self._get_m2m_attr, related, "remote_field")
1277
- self.m2m_target_field_name = lambda: get_m2m_rel().field_name
1311
+ self.m2m_target_field_name = lambda: get_m2m_rel().field_name # type: ignore[method-assign,attr-defined]
1278
1312
  get_m2m_reverse_rel = partial(
1279
1313
  self._get_m2m_reverse_attr, related, "remote_field"
1280
1314
  )
1281
- self.m2m_reverse_target_field_name = lambda: get_m2m_reverse_rel().field_name
1315
+ self.m2m_reverse_target_field_name = lambda: get_m2m_reverse_rel().field_name # type: ignore[method-assign,attr-defined]
1282
1316
 
1283
- def set_attributes_from_rel(self):
1317
+ def set_attributes_from_rel(self) -> None:
1284
1318
  pass
1285
1319
 
1286
- def value_from_object(self, obj):
1287
- return [] if obj.id is None else list(getattr(obj, self.attname).all())
1320
+ def value_from_object(self, obj: Model) -> list[Any]:
1321
+ return [] if obj.id is None else list(getattr(obj, self.attname).all()) # type: ignore[attr-defined]
1288
1322
 
1289
- def save_form_data(self, instance, data):
1290
- getattr(instance, self.attname).set(data)
1323
+ def save_form_data(self, instance: Model, data: Any) -> None:
1324
+ getattr(instance, self.attname).set(data) # type: ignore[attr-defined]
1291
1325
 
1292
- def db_check(self, connection):
1326
+ def db_check(self, connection: BaseDatabaseWrapper) -> None:
1293
1327
  return None
1294
1328
 
1295
- def db_type(self, connection):
1329
+ def db_type(self, connection: BaseDatabaseWrapper) -> None:
1296
1330
  # A ManyToManyField is not represented by a single column,
1297
1331
  # so return None.
1298
1332
  return None
1299
1333
 
1300
- def db_parameters(self, connection):
1334
+ def db_parameters(self, connection: BaseDatabaseWrapper) -> dict[str, None]:
1301
1335
  return {"type": None, "check": None}