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,7 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import copy
2
4
  from decimal import Decimal
5
+ from typing import TYPE_CHECKING, Any
3
6
 
4
- from plain.models import register_model
7
+ from plain.models import Options
5
8
  from plain.models.backends.base.schema import BaseDatabaseSchemaEditor
6
9
  from plain.models.backends.ddl_references import Statement
7
10
  from plain.models.backends.utils import strip_quotes
@@ -10,6 +13,13 @@ from plain.models.db import NotSupportedError
10
13
  from plain.models.registry import ModelsRegistry
11
14
  from plain.models.transaction import atomic
12
15
 
16
+ if TYPE_CHECKING:
17
+ from plain.models.base import Model
18
+ from plain.models.constraints import BaseConstraint
19
+ from plain.models.fields import Field
20
+ from plain.models.fields.related import ManyToManyField
21
+ from plain.models.fields.reverse_related import ManyToManyRel
22
+
13
23
 
14
24
  class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
15
25
  sql_delete_table = "DROP TABLE %(table)s"
@@ -22,7 +32,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
22
32
  sql_create_unique = "CREATE UNIQUE INDEX %(name)s ON %(table)s (%(columns)s)"
23
33
  sql_delete_unique = "DROP INDEX %(name)s"
24
34
 
25
- def __enter__(self):
35
+ def __enter__(self) -> DatabaseSchemaEditor:
26
36
  # Some SQLite schema alterations need foreign key constraints to be
27
37
  # disabled. Enforce it here for the duration of the schema edition.
28
38
  if not self.connection.disable_constraint_checking():
@@ -35,19 +45,19 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
35
45
  )
36
46
  return super().__enter__()
37
47
 
38
- def __exit__(self, exc_type, exc_value, traceback):
48
+ def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
39
49
  self.connection.check_constraints()
40
50
  super().__exit__(exc_type, exc_value, traceback)
41
51
  self.connection.enable_constraint_checking()
42
52
 
43
- def quote_value(self, value):
53
+ def quote_value(self, value: Any) -> str:
44
54
  # The backend "mostly works" without this function and there are use
45
55
  # cases for compiling Python without the sqlite3 libraries (e.g.
46
56
  # security hardening).
47
57
  try:
48
58
  import sqlite3
49
59
 
50
- value = sqlite3.adapt(value)
60
+ value = sqlite3.adapt(value) # type: ignore[call-overload]
51
61
  except ImportError:
52
62
  pass
53
63
  except sqlite3.ProgrammingError:
@@ -71,12 +81,12 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
71
81
  f"Cannot quote parameter value {value!r} of type {type(value)}"
72
82
  )
73
83
 
74
- def prepare_default(self, value):
84
+ def prepare_default(self, value: Any) -> str:
75
85
  return self.quote_value(value)
76
86
 
77
87
  def _is_referenced_by_fk_constraint(
78
- self, table_name, column_name=None, ignore_self=False
79
- ):
88
+ self, table_name: str, column_name: str | None = None, ignore_self: bool = False
89
+ ) -> bool:
80
90
  """
81
91
  Return whether or not the provided table name is referenced by another
82
92
  one. If `column_name` is specified, only references pointing to that
@@ -98,8 +108,12 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
98
108
  return False
99
109
 
100
110
  def alter_db_table(
101
- self, model, old_db_table, new_db_table, disable_constraints=True
102
- ):
111
+ self,
112
+ model: type[Model],
113
+ old_db_table: str,
114
+ new_db_table: str,
115
+ disable_constraints: bool = True,
116
+ ) -> None:
103
117
  if (
104
118
  not self.connection.features.supports_atomic_references_rename
105
119
  and disable_constraints
@@ -117,11 +131,17 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
117
131
  else:
118
132
  super().alter_db_table(model, old_db_table, new_db_table)
119
133
 
120
- def alter_field(self, model, old_field, new_field, strict=False):
134
+ def alter_field(
135
+ self,
136
+ model: type[Model],
137
+ old_field: Field,
138
+ new_field: Field,
139
+ strict: bool = False,
140
+ ) -> None:
121
141
  if not self._field_should_be_altered(old_field, new_field):
122
142
  return
123
143
  old_field_name = old_field.name
124
- table_name = model._meta.db_table
144
+ table_name = model.model_options.db_table
125
145
  _, old_column_name = old_field.get_attname_column()
126
146
  if (
127
147
  new_field.name != old_field_name
@@ -132,7 +152,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
132
152
  ):
133
153
  if self.connection.in_atomic_block:
134
154
  raise NotSupportedError(
135
- f"Renaming the {model._meta.db_table!r}.{old_field_name!r} column while in a transaction is not "
155
+ f"Renaming the {model.model_options.db_table!r}.{old_field_name!r} column while in a transaction is not "
136
156
  "supported on SQLite < 3.26 because it would break referential "
137
157
  "integrity. Try adding `atomic = False` to the Migration class."
138
158
  )
@@ -168,8 +188,12 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
168
188
  super().alter_field(model, old_field, new_field, strict=strict)
169
189
 
170
190
  def _remake_table(
171
- self, model, create_field=None, delete_field=None, alter_fields=None
172
- ):
191
+ self,
192
+ model: type[Model],
193
+ create_field: Field | None = None,
194
+ delete_field: Field | None = None,
195
+ alter_fields: list[tuple[Field, Field]] | None = None,
196
+ ) -> None:
173
197
  """
174
198
  Shortcut to transform a model from old_model into new_model
175
199
 
@@ -189,19 +213,19 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
189
213
  # Self-referential fields must be recreated rather than copied from
190
214
  # the old model to ensure their remote_field.field_name doesn't refer
191
215
  # to an altered field.
192
- def is_self_referential(f):
193
- return f.is_relation and f.remote_field.model is model
216
+ def is_self_referential(f: Field) -> bool:
217
+ return f.is_relation and f.remote_field.model is model # type: ignore[attr-defined]
194
218
 
195
219
  # Work out the new fields dict / mapping
196
220
  body = {
197
221
  f.name: f.clone() if is_self_referential(f) else f
198
- for f in model._meta.local_concrete_fields
222
+ for f in model._model_meta.local_concrete_fields
199
223
  }
200
224
  # Since mapping might mix column names and default values,
201
225
  # its values must be already quoted.
202
226
  mapping = {
203
227
  f.column: self.quote_name(f.column)
204
- for f in model._meta.local_concrete_fields
228
+ for f in model._model_meta.local_concrete_fields
205
229
  }
206
230
  # If any of the new or altered fields is introducing a new PK,
207
231
  # remove the old one
@@ -248,13 +272,13 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
248
272
  # Work inside a new app registry
249
273
  models_registry = ModelsRegistry()
250
274
 
251
- indexes = model._meta.indexes
275
+ indexes = model.model_options.indexes
252
276
  if delete_field:
253
277
  indexes = [
254
278
  index for index in indexes if delete_field.name not in index.fields
255
279
  ]
256
280
 
257
- constraints = list(model._meta.constraints)
281
+ constraints = list(model.model_options.constraints)
258
282
 
259
283
  # Provide isolated instances of the fields to the new model body so
260
284
  # that the existing model's internals aren't interfered with when
@@ -266,32 +290,33 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
266
290
  # table and solely exists for foreign key reference resolution purposes.
267
291
  # This wouldn't be required if the schema editor was operating on model
268
292
  # states instead of rendered models.
269
- meta_contents = {
270
- "package_label": model._meta.package_label,
271
- "db_table": model._meta.db_table,
272
- "indexes": indexes,
273
- "constraints": constraints,
274
- "models_registry": models_registry,
275
- }
276
- meta = type("Meta", (), meta_contents)
277
- body_copy["Meta"] = meta
293
+ meta_options = Options(
294
+ package_label=model.model_options.package_label,
295
+ db_table=model.model_options.db_table,
296
+ indexes=indexes,
297
+ constraints=constraints,
298
+ )
299
+ body_copy["model_options"] = meta_options
278
300
  body_copy["__module__"] = model.__module__
279
- register_model(type(model._meta.object_name, model.__bases__, body_copy))
301
+ temp_model: type[Model] = type( # type: ignore[assignment]
302
+ model.model_options.object_name, model.__bases__, body_copy
303
+ )
304
+ models_registry.register_model(model.model_options.package_label, temp_model)
280
305
 
281
306
  # Construct a model with a renamed table name.
282
307
  body_copy = copy.deepcopy(body)
283
- meta_contents = {
284
- "package_label": model._meta.package_label,
285
- "db_table": f"new__{strip_quotes(model._meta.db_table)}",
286
- "indexes": indexes,
287
- "constraints": constraints,
288
- "models_registry": models_registry,
289
- }
290
- meta = type("Meta", (), meta_contents)
291
- body_copy["Meta"] = meta
308
+ meta_options = Options(
309
+ package_label=model.model_options.package_label,
310
+ db_table=f"new__{strip_quotes(model.model_options.db_table)}",
311
+ indexes=indexes,
312
+ constraints=constraints,
313
+ )
314
+ body_copy["model_options"] = meta_options
292
315
  body_copy["__module__"] = model.__module__
293
- new_model = type(f"New{model._meta.object_name}", model.__bases__, body_copy)
294
- register_model(new_model)
316
+ new_model: type[Model] = type( # type: ignore[assignment]
317
+ f"New{model.model_options.object_name}", model.__bases__, body_copy
318
+ )
319
+ models_registry.register_model(model.model_options.package_label, new_model)
295
320
 
296
321
  # Create a new table with the updated schema.
297
322
  self.create_model(new_model)
@@ -299,10 +324,10 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
299
324
  # Copy data from the old table into the new table
300
325
  self.execute(
301
326
  "INSERT INTO {} ({}) SELECT {} FROM {}".format(
302
- self.quote_name(new_model._meta.db_table),
327
+ self.quote_name(new_model.model_options.db_table),
303
328
  ", ".join(self.quote_name(x) for x in mapping),
304
329
  ", ".join(mapping.values()),
305
- self.quote_name(model._meta.db_table),
330
+ self.quote_name(model.model_options.db_table),
306
331
  )
307
332
  )
308
333
 
@@ -311,9 +336,9 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
311
336
 
312
337
  # Rename the new table to take way for the old
313
338
  self.alter_db_table(
314
- new_model,
315
- new_model._meta.db_table,
316
- model._meta.db_table,
339
+ new_model, # type: ignore[arg-type]
340
+ new_model.model_options.db_table,
341
+ model.model_options.db_table,
317
342
  disable_constraints=False,
318
343
  )
319
344
 
@@ -325,7 +350,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
325
350
  if restore_pk_field:
326
351
  restore_pk_field.primary_key = True
327
352
 
328
- def delete_model(self, model, handle_autom2m=True):
353
+ def delete_model(self, model: type[Model], handle_autom2m: bool = True) -> None:
329
354
  if handle_autom2m:
330
355
  super().delete_model(model)
331
356
  else:
@@ -333,17 +358,17 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
333
358
  self.execute(
334
359
  self.sql_delete_table
335
360
  % {
336
- "table": self.quote_name(model._meta.db_table),
361
+ "table": self.quote_name(model.model_options.db_table),
337
362
  }
338
363
  )
339
364
  # Remove all deferred statements referencing the deleted table.
340
365
  for sql in list(self.deferred_sql):
341
366
  if isinstance(sql, Statement) and sql.references_table(
342
- model._meta.db_table
367
+ model.model_options.db_table
343
368
  ):
344
369
  self.deferred_sql.remove(sql)
345
370
 
346
- def add_field(self, model, field):
371
+ def add_field(self, model: type[Model], field: Field) -> None:
347
372
  """Create a field on a model."""
348
373
  if (
349
374
  # Primary keys are not supported in ALTER TABLE
@@ -360,7 +385,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
360
385
  else:
361
386
  super().add_field(model, field)
362
387
 
363
- def remove_field(self, model, field):
388
+ def remove_field(self, model: type[Model], field: Field) -> None:
364
389
  """
365
390
  Remove a field from a model. Usually involves deleting a column,
366
391
  but for M2Ms may involve deleting a table.
@@ -374,8 +399,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
374
399
  # Primary keys, unique fields, indexed fields, and foreign keys are
375
400
  # not supported in ALTER TABLE DROP COLUMN.
376
401
  and not field.primary_key
377
- and not (field.remote_field and field.db_index)
378
- and not (field.remote_field and field.db_constraint)
402
+ and not (field.remote_field and field.db_index) # type: ignore[attr-defined]
403
+ and not (field.remote_field and field.db_constraint) # type: ignore[attr-defined]
379
404
  ):
380
405
  super().remove_field(model, field)
381
406
  # For everything else, remake.
@@ -387,15 +412,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
387
412
 
388
413
  def _alter_field(
389
414
  self,
390
- model,
391
- old_field,
392
- new_field,
393
- old_type,
394
- new_type,
395
- old_db_params,
396
- new_db_params,
397
- strict=False,
398
- ):
415
+ model: type[Model],
416
+ old_field: Field,
417
+ new_field: Field,
418
+ old_type: str,
419
+ new_type: str,
420
+ old_db_params: dict[str, Any],
421
+ new_db_params: dict[str, Any],
422
+ strict: bool = False,
423
+ ) -> None:
399
424
  """Perform a "physical" (non-ManyToMany) field update."""
400
425
  # Use "ALTER TABLE ... RENAME COLUMN" if only the column name
401
426
  # changed and there aren't any constraints.
@@ -405,14 +430,14 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
405
430
  and self.column_sql(model, old_field) == self.column_sql(model, new_field)
406
431
  and not (
407
432
  old_field.remote_field
408
- and old_field.db_constraint
433
+ and old_field.db_constraint # type: ignore[attr-defined]
409
434
  or new_field.remote_field
410
- and new_field.db_constraint
435
+ and new_field.db_constraint # type: ignore[attr-defined]
411
436
  )
412
437
  ):
413
438
  return self.execute(
414
439
  self._rename_field_sql(
415
- model._meta.db_table, old_field, new_field, new_type
440
+ model.model_options.db_table, old_field, new_field, new_type
416
441
  )
417
442
  )
418
443
  # Alter by remaking table
@@ -424,8 +449,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
424
449
  old_type != new_type or old_collation != new_collation
425
450
  ):
426
451
  related_models = set()
427
- opts = new_field.model._meta
428
- for remote_field in opts.related_objects:
452
+ meta = new_field.model._model_meta
453
+ for remote_field in meta.related_objects:
429
454
  # Ignore self-relationship since the table was already rebuilt.
430
455
  if remote_field.related_model == model:
431
456
  continue
@@ -433,44 +458,54 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
433
458
  if remote_field.field_name == new_field.name:
434
459
  related_models.add(remote_field.related_model)
435
460
  if new_field.primary_key:
436
- for many_to_many in opts.many_to_many:
461
+ for many_to_many in meta.many_to_many:
437
462
  # Ignore self-relationship since the table was already rebuilt.
438
463
  if many_to_many.related_model == model:
439
464
  continue
440
465
  for related_model in related_models:
441
466
  self._remake_table(related_model)
442
467
 
443
- def _alter_many_to_many(self, model, old_field, new_field, strict):
468
+ def _alter_many_to_many(
469
+ self,
470
+ model: type[Model],
471
+ old_field: ManyToManyField,
472
+ new_field: ManyToManyField,
473
+ strict: bool,
474
+ ) -> None:
444
475
  """Alter M2Ms to repoint their to= endpoints."""
476
+ # Type narrow for ManyToManyField.remote_field
477
+ old_rel: ManyToManyRel = old_field.remote_field # type: ignore[assignment]
478
+ new_rel: ManyToManyRel = new_field.remote_field # type: ignore[assignment]
479
+
445
480
  if (
446
- old_field.remote_field.through._meta.db_table
447
- == new_field.remote_field.through._meta.db_table
481
+ old_rel.through.model_options.db_table
482
+ == new_rel.through.model_options.db_table
448
483
  ):
449
484
  # The field name didn't change, but some options did, so we have to
450
485
  # propagate this altering.
451
486
  self._remake_table(
452
- old_field.remote_field.through,
487
+ old_rel.through,
453
488
  alter_fields=[
454
489
  (
455
490
  # The field that points to the target model is needed,
456
491
  # so that table can be remade with the new m2m field -
457
492
  # this is m2m_reverse_field_name().
458
- old_field.remote_field.through._meta.get_field(
459
- old_field.m2m_reverse_field_name()
493
+ old_rel.through._model_meta.get_field(
494
+ old_field.m2m_reverse_field_name() # type: ignore[attr-defined]
460
495
  ),
461
- new_field.remote_field.through._meta.get_field(
462
- new_field.m2m_reverse_field_name()
496
+ new_rel.through._model_meta.get_field(
497
+ new_field.m2m_reverse_field_name() # type: ignore[attr-defined]
463
498
  ),
464
499
  ),
465
500
  (
466
501
  # The field that points to the model itself is needed,
467
502
  # so that table can be remade with the new self field -
468
503
  # this is m2m_field_name().
469
- old_field.remote_field.through._meta.get_field(
470
- old_field.m2m_field_name()
504
+ old_rel.through._model_meta.get_field(
505
+ old_field.m2m_field_name() # type: ignore[attr-defined]
471
506
  ),
472
- new_field.remote_field.through._meta.get_field(
473
- new_field.m2m_field_name()
507
+ new_rel.through._model_meta.get_field(
508
+ new_field.m2m_field_name() # type: ignore[attr-defined]
474
509
  ),
475
510
  ),
476
511
  ],
@@ -478,32 +513,32 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
478
513
  return
479
514
 
480
515
  # Make a new through table
481
- self.create_model(new_field.remote_field.through)
516
+ self.create_model(new_rel.through)
482
517
  # Copy the data across
483
518
  self.execute(
484
519
  "INSERT INTO {} ({}) SELECT {} FROM {}".format(
485
- self.quote_name(new_field.remote_field.through._meta.db_table),
520
+ self.quote_name(new_rel.through.model_options.db_table),
486
521
  ", ".join(
487
522
  [
488
523
  "id",
489
- new_field.m2m_column_name(),
490
- new_field.m2m_reverse_name(),
524
+ new_field.m2m_column_name(), # type: ignore[attr-defined]
525
+ new_field.m2m_reverse_name(), # type: ignore[attr-defined]
491
526
  ]
492
527
  ),
493
528
  ", ".join(
494
529
  [
495
530
  "id",
496
- old_field.m2m_column_name(),
497
- old_field.m2m_reverse_name(),
531
+ old_field.m2m_column_name(), # type: ignore[attr-defined]
532
+ old_field.m2m_reverse_name(), # type: ignore[attr-defined]
498
533
  ]
499
534
  ),
500
- self.quote_name(old_field.remote_field.through._meta.db_table),
535
+ self.quote_name(old_rel.through.model_options.db_table),
501
536
  )
502
537
  )
503
538
  # Delete the old through table
504
- self.delete_model(old_field.remote_field.through)
539
+ self.delete_model(old_rel.through)
505
540
 
506
- def add_constraint(self, model, constraint):
541
+ def add_constraint(self, model: type[Model], constraint: BaseConstraint) -> None:
507
542
  if isinstance(constraint, UniqueConstraint) and (
508
543
  constraint.condition
509
544
  or constraint.contains_expressions
@@ -514,7 +549,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
514
549
  else:
515
550
  self._remake_table(model)
516
551
 
517
- def remove_constraint(self, model, constraint):
552
+ def remove_constraint(self, model: type[Model], constraint: BaseConstraint) -> None:
518
553
  if isinstance(constraint, UniqueConstraint) and (
519
554
  constraint.condition
520
555
  or constraint.contains_expressions
@@ -525,5 +560,5 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
525
560
  else:
526
561
  self._remake_table(model)
527
562
 
528
- def _collate_sql(self, collation):
563
+ def _collate_sql(self, collation: str) -> str:
529
564
  return "COLLATE " + collation