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,6 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
4
  import operator
5
+ from collections.abc import Generator
3
6
  from datetime import datetime
7
+ from typing import TYPE_CHECKING, Any
4
8
 
5
9
  from plain.models.backends.ddl_references import (
6
10
  Columns,
@@ -17,10 +21,20 @@ from plain.models.sql import Query
17
21
  from plain.models.transaction import TransactionManagementError, atomic
18
22
  from plain.utils import timezone
19
23
 
24
+ if TYPE_CHECKING:
25
+ from collections.abc import Iterable
26
+
27
+ from plain.models.backends.base.base import BaseDatabaseWrapper
28
+ from plain.models.base import Model
29
+ from plain.models.constraints import BaseConstraint
30
+ from plain.models.fields import Field
31
+ from plain.models.fields.related import ForeignKey, ManyToManyField
32
+ from plain.models.fields.reverse_related import ManyToManyRel
33
+
20
34
  logger = logging.getLogger("plain.models.backends.schema")
21
35
 
22
36
 
23
- def _is_relevant_relation(relation, altered_field):
37
+ def _is_relevant_relation(relation: Any, altered_field: Field) -> bool:
24
38
  """
25
39
  When altering the given field, must constraints on its model from the given
26
40
  relation be temporarily dropped?
@@ -36,10 +50,10 @@ def _is_relevant_relation(relation, altered_field):
36
50
  return altered_field.name == "id"
37
51
 
38
52
 
39
- def _all_related_fields(model):
53
+ def _all_related_fields(model: type[Model]) -> list[Any]:
40
54
  # Related fields must be returned in a deterministic order.
41
55
  return sorted(
42
- model._meta._get_fields(
56
+ model._model_meta._get_fields(
43
57
  forward=False,
44
58
  reverse=True,
45
59
  include_hidden=True,
@@ -48,7 +62,9 @@ def _all_related_fields(model):
48
62
  )
49
63
 
50
64
 
51
- def _related_non_m2m_objects(old_field, new_field):
65
+ def _related_non_m2m_objects(
66
+ old_field: Field, new_field: Field
67
+ ) -> Generator[tuple[Any, Any], None, None]:
52
68
  # Filter out m2m objects from reverse relations.
53
69
  # Return (old_relation, new_relation) tuples.
54
70
  related_fields = zip(
@@ -140,23 +156,28 @@ class BaseDatabaseSchemaEditor:
140
156
  sql_alter_table_comment = "COMMENT ON TABLE %(table)s IS %(comment)s"
141
157
  sql_alter_column_comment = "COMMENT ON COLUMN %(table)s.%(column)s IS %(comment)s"
142
158
 
143
- def __init__(self, connection, collect_sql=False, atomic=True):
159
+ def __init__(
160
+ self,
161
+ connection: BaseDatabaseWrapper,
162
+ collect_sql: bool = False,
163
+ atomic: bool = True,
164
+ ):
144
165
  self.connection = connection
145
166
  self.collect_sql = collect_sql
146
167
  if self.collect_sql:
147
- self.collected_sql = []
168
+ self.collected_sql: list[str] = []
148
169
  self.atomic_migration = self.connection.features.can_rollback_ddl and atomic
149
170
 
150
171
  # State-managing methods
151
172
 
152
- def __enter__(self):
153
- self.deferred_sql = []
173
+ def __enter__(self) -> BaseDatabaseSchemaEditor:
174
+ self.deferred_sql: list[Any] = []
154
175
  if self.atomic_migration:
155
176
  self.atomic = atomic()
156
177
  self.atomic.__enter__()
157
178
  return self
158
179
 
159
- def __exit__(self, exc_type, exc_value, traceback):
180
+ def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
160
181
  if exc_type is None:
161
182
  for sql in self.deferred_sql:
162
183
  self.execute(sql)
@@ -165,7 +186,9 @@ class BaseDatabaseSchemaEditor:
165
186
 
166
187
  # Core utility functions
167
188
 
168
- def execute(self, sql, params=()):
189
+ def execute(
190
+ self, sql: str | Statement, params: tuple[Any, ...] | list[Any] | None = ()
191
+ ) -> None:
169
192
  """Execute the given SQL statement, with optional parameters."""
170
193
  # Don't perform the transactional DDL check if SQL is being collected
171
194
  # as it's not going to be executed anyway.
@@ -196,15 +219,15 @@ class BaseDatabaseSchemaEditor:
196
219
  with self.connection.cursor() as cursor:
197
220
  cursor.execute(sql, params)
198
221
 
199
- def quote_name(self, name):
222
+ def quote_name(self, name: str) -> str:
200
223
  return self.connection.ops.quote_name(name)
201
224
 
202
- def table_sql(self, model):
225
+ def table_sql(self, model: type[Model]) -> tuple[str, list[Any]]:
203
226
  """Take a model and return its table definition."""
204
227
  # Create column SQL, add FK deferreds if needed.
205
228
  column_sqls = []
206
229
  params = []
207
- for field in model._meta.local_fields:
230
+ for field in model._model_meta.local_fields:
208
231
  # SQL.
209
232
  definition, extra_params = self.column_sql(model, field)
210
233
  if definition is None:
@@ -219,9 +242,9 @@ class BaseDatabaseSchemaEditor:
219
242
  definition += f" {col_type_suffix}"
220
243
  params.extend(extra_params)
221
244
  # FK.
222
- if field.remote_field and field.db_constraint:
223
- to_table = field.remote_field.model._meta.db_table
224
- to_column = field.remote_field.model._meta.get_field(
245
+ if field.remote_field and field.db_constraint: # type: ignore[attr-defined]
246
+ to_table = field.remote_field.model.model_options.db_table
247
+ to_column = field.remote_field.model._model_meta.get_field(
225
248
  field.remote_field.field_name
226
249
  ).column
227
250
  if self.sql_create_inline_fk:
@@ -241,16 +264,16 @@ class BaseDatabaseSchemaEditor:
241
264
  # variant).
242
265
  if field.get_internal_type() in ("PrimaryKeyField",):
243
266
  autoinc_sql = self.connection.ops.autoinc_sql(
244
- model._meta.db_table, field.column
267
+ model.model_options.db_table, field.column
245
268
  )
246
269
  if autoinc_sql:
247
270
  self.deferred_sql.extend(autoinc_sql)
248
271
  constraints = [
249
272
  constraint.constraint_sql(model, self)
250
- for constraint in model._meta.constraints
273
+ for constraint in model.model_options.constraints
251
274
  ]
252
275
  sql = self.sql_create_table % {
253
- "table": self.quote_name(model._meta.db_table),
276
+ "table": self.quote_name(model.model_options.db_table),
254
277
  "definition": ", ".join(
255
278
  str(constraint)
256
279
  for constraint in (*column_sqls, *constraints)
@@ -262,8 +285,14 @@ class BaseDatabaseSchemaEditor:
262
285
  # Field <-> database mapping functions
263
286
 
264
287
  def _iter_column_sql(
265
- self, column_db_type, params, model, field, field_db_params, include_default
266
- ):
288
+ self,
289
+ column_db_type: str,
290
+ params: list[Any],
291
+ model: type[Model],
292
+ field: Field,
293
+ field_db_params: dict[str, Any],
294
+ include_default: bool,
295
+ ) -> Generator[str, None, None]:
267
296
  yield column_db_type
268
297
  if collation := field_db_params.get("collation"):
269
298
  yield self._collate_sql(collation)
@@ -302,7 +331,9 @@ class BaseDatabaseSchemaEditor:
302
331
  if field.primary_key:
303
332
  yield "PRIMARY KEY"
304
333
 
305
- def column_sql(self, model, field, include_default=False):
334
+ def column_sql(
335
+ self, model: type[Model], field: Field, include_default: bool = False
336
+ ) -> tuple[str | None, list[Any] | None]:
306
337
  """
307
338
  Return the column definition for a field. The field must already have
308
339
  had set_attributes_from_name() called.
@@ -313,7 +344,7 @@ class BaseDatabaseSchemaEditor:
313
344
  # Check for fields that aren't actually columns (e.g. M2M).
314
345
  if column_db_type is None:
315
346
  return None, None
316
- params = []
347
+ params: list[Any] = []
317
348
  return (
318
349
  " ".join(
319
350
  # This appends to the params being returned.
@@ -329,21 +360,21 @@ class BaseDatabaseSchemaEditor:
329
360
  params,
330
361
  )
331
362
 
332
- def skip_default(self, field):
363
+ def skip_default(self, field: Field) -> bool:
333
364
  """
334
365
  Some backends don't accept default values for certain columns types
335
366
  (i.e. MySQL longtext and longblob).
336
367
  """
337
368
  return False
338
369
 
339
- def skip_default_on_alter(self, field):
370
+ def skip_default_on_alter(self, field: Field) -> bool:
340
371
  """
341
372
  Some backends don't accept default values for certain columns types
342
373
  (i.e. MySQL longtext and longblob) in the ALTER COLUMN statement.
343
374
  """
344
375
  return False
345
376
 
346
- def prepare_default(self, value):
377
+ def prepare_default(self, value: Any) -> str:
347
378
  """
348
379
  Only used for backends which have requires_literal_defaults feature
349
380
  """
@@ -352,7 +383,7 @@ class BaseDatabaseSchemaEditor:
352
383
  "requires_literal_defaults must provide a prepare_default() method"
353
384
  )
354
385
 
355
- def _column_default_sql(self, field):
386
+ def _column_default_sql(self, field: Field) -> str:
356
387
  """
357
388
  Return the SQL to use in a DEFAULT clause. The resulting string should
358
389
  contain a '%s' placeholder for a default value.
@@ -360,7 +391,7 @@ class BaseDatabaseSchemaEditor:
360
391
  return "%s"
361
392
 
362
393
  @staticmethod
363
- def _effective_default(field):
394
+ def _effective_default(field: Field) -> Any:
364
395
  # This method allows testing its logic without a connection.
365
396
  if field.has_default():
366
397
  default = field.get_default()
@@ -385,11 +416,11 @@ class BaseDatabaseSchemaEditor:
385
416
  default = None
386
417
  return default
387
418
 
388
- def effective_default(self, field):
419
+ def effective_default(self, field: Field) -> Any:
389
420
  """Return a field's effective database default value."""
390
421
  return field.get_db_prep_save(self._effective_default(field), self.connection)
391
422
 
392
- def quote_value(self, value):
423
+ def quote_value(self, value: Any) -> str:
393
424
  """
394
425
  Return a quoted version of the value so it's safe to use in an SQL
395
426
  string. This is not safe against injection from user code; it is
@@ -401,7 +432,7 @@ class BaseDatabaseSchemaEditor:
401
432
 
402
433
  # Actions
403
434
 
404
- def create_model(self, model):
435
+ def create_model(self, model: type[Model]) -> None:
405
436
  """
406
437
  Create a table and any accompanying indexes or unique constraints for
407
438
  the given `model`.
@@ -413,11 +444,13 @@ class BaseDatabaseSchemaEditor:
413
444
 
414
445
  if self.connection.features.supports_comments:
415
446
  # Add table comment.
416
- if model._meta.db_table_comment:
417
- self.alter_db_table_comment(model, None, model._meta.db_table_comment)
447
+ if model.model_options.db_table_comment:
448
+ self.alter_db_table_comment(
449
+ model, None, model.model_options.db_table_comment
450
+ )
418
451
  # Add column comments.
419
452
  if not self.connection.features.supports_comments_inline:
420
- for field in model._meta.local_fields:
453
+ for field in model._model_meta.local_fields:
421
454
  if field.db_comment:
422
455
  field_db_params = field.db_parameters(
423
456
  connection=self.connection
@@ -431,24 +464,24 @@ class BaseDatabaseSchemaEditor:
431
464
  # Add any field index (deferred as SQLite _remake_table needs it).
432
465
  self.deferred_sql.extend(self._model_indexes_sql(model))
433
466
 
434
- def delete_model(self, model):
467
+ def delete_model(self, model: type[Model]) -> None:
435
468
  """Delete a model from the database."""
436
469
 
437
470
  # Delete the table
438
471
  self.execute(
439
472
  self.sql_delete_table
440
473
  % {
441
- "table": self.quote_name(model._meta.db_table),
474
+ "table": self.quote_name(model.model_options.db_table),
442
475
  }
443
476
  )
444
477
  # Remove all deferred statements referencing the deleted table.
445
478
  for sql in list(self.deferred_sql):
446
479
  if isinstance(sql, Statement) and sql.references_table(
447
- model._meta.db_table
480
+ model.model_options.db_table
448
481
  ):
449
482
  self.deferred_sql.remove(sql)
450
483
 
451
- def add_index(self, model, index):
484
+ def add_index(self, model: type[Model], index: Index) -> None:
452
485
  """Add an index on a model."""
453
486
  if (
454
487
  index.contains_expressions
@@ -459,7 +492,7 @@ class BaseDatabaseSchemaEditor:
459
492
  # necessity to avoid escaping attempts on execution.
460
493
  self.execute(index.create_sql(model, self), params=None)
461
494
 
462
- def remove_index(self, model, index):
495
+ def remove_index(self, model: type[Model], index: Index) -> None:
463
496
  """Remove an index from a model."""
464
497
  if (
465
498
  index.contains_expressions
@@ -468,7 +501,9 @@ class BaseDatabaseSchemaEditor:
468
501
  return None
469
502
  self.execute(index.remove_sql(model, self))
470
503
 
471
- def rename_index(self, model, old_index, new_index):
504
+ def rename_index(
505
+ self, model: type[Model], old_index: Index, new_index: Index
506
+ ) -> None:
472
507
  if self.connection.features.can_rename_index:
473
508
  self.execute(
474
509
  self._rename_index_sql(model, old_index.name, new_index.name),
@@ -478,7 +513,7 @@ class BaseDatabaseSchemaEditor:
478
513
  self.remove_index(model, old_index)
479
514
  self.add_index(model, new_index)
480
515
 
481
- def add_constraint(self, model, constraint):
516
+ def add_constraint(self, model: type[Model], constraint: BaseConstraint) -> None:
482
517
  """Add a constraint to a model."""
483
518
  sql = constraint.create_sql(model, self)
484
519
  if sql:
@@ -486,13 +521,15 @@ class BaseDatabaseSchemaEditor:
486
521
  # params=None a necessity to avoid escaping attempts on execution.
487
522
  self.execute(sql, params=None)
488
523
 
489
- def remove_constraint(self, model, constraint):
524
+ def remove_constraint(self, model: type[Model], constraint: BaseConstraint) -> None:
490
525
  """Remove a constraint from a model."""
491
526
  sql = constraint.remove_sql(model, self)
492
527
  if sql:
493
528
  self.execute(sql)
494
529
 
495
- def alter_db_table(self, model, old_db_table, new_db_table):
530
+ def alter_db_table(
531
+ self, model: type[Model], old_db_table: str, new_db_table: str
532
+ ) -> None:
496
533
  """Rename the table a model points to."""
497
534
  if old_db_table == new_db_table or (
498
535
  self.connection.features.ignores_table_name_case
@@ -511,16 +548,21 @@ class BaseDatabaseSchemaEditor:
511
548
  if isinstance(sql, Statement):
512
549
  sql.rename_table_references(old_db_table, new_db_table)
513
550
 
514
- def alter_db_table_comment(self, model, old_db_table_comment, new_db_table_comment):
551
+ def alter_db_table_comment(
552
+ self,
553
+ model: type[Model],
554
+ old_db_table_comment: str | None,
555
+ new_db_table_comment: str | None,
556
+ ) -> None:
515
557
  self.execute(
516
558
  self.sql_alter_table_comment
517
559
  % {
518
- "table": self.quote_name(model._meta.db_table),
560
+ "table": self.quote_name(model.model_options.db_table),
519
561
  "comment": self.quote_value(new_db_table_comment or ""),
520
562
  }
521
563
  )
522
564
 
523
- def add_field(self, model, field):
565
+ def add_field(self, model: type[Model], field: Field) -> None:
524
566
  """
525
567
  Create a field on a model. Usually involves adding a column, but may
526
568
  involve adding a table instead (for M2M fields).
@@ -539,16 +581,16 @@ class BaseDatabaseSchemaEditor:
539
581
  if (
540
582
  field.remote_field
541
583
  and self.connection.features.supports_foreign_keys
542
- and field.db_constraint
584
+ and field.db_constraint # type: ignore[attr-defined]
543
585
  ):
544
586
  constraint_suffix = "_fk_%(to_table)s_%(to_column)s"
545
587
  # Add FK constraint inline, if supported.
546
588
  if self.sql_create_column_inline_fk:
547
- to_table = field.remote_field.model._meta.db_table
548
- to_column = field.remote_field.model._meta.get_field(
589
+ to_table = field.remote_field.model.model_options.db_table
590
+ to_column = field.remote_field.model._model_meta.get_field(
549
591
  field.remote_field.field_name
550
592
  ).column
551
- namespace, _ = split_identifier(model._meta.db_table)
593
+ namespace, _ = split_identifier(model.model_options.db_table)
552
594
  definition += " " + self.sql_create_column_inline_fk % {
553
595
  "name": self._fk_constraint_name(model, field, constraint_suffix),
554
596
  "namespace": f"{self.quote_name(namespace)}." if namespace else "",
@@ -564,7 +606,7 @@ class BaseDatabaseSchemaEditor:
564
606
  )
565
607
  # Build the SQL and run it
566
608
  sql = self.sql_create_column % {
567
- "table": self.quote_name(model._meta.db_table),
609
+ "table": self.quote_name(model.model_options.db_table),
568
610
  "column": self.quote_name(field.column),
569
611
  "definition": definition,
570
612
  }
@@ -579,7 +621,7 @@ class BaseDatabaseSchemaEditor:
579
621
  model, None, field, drop=True
580
622
  )
581
623
  sql = self.sql_alter_column % {
582
- "table": self.quote_name(model._meta.db_table),
624
+ "table": self.quote_name(model.model_options.db_table),
583
625
  "changes": changes_sql,
584
626
  }
585
627
  self.execute(sql, params)
@@ -601,7 +643,7 @@ class BaseDatabaseSchemaEditor:
601
643
  if self.connection.features.connection_persists_old_columns:
602
644
  self.connection.close()
603
645
 
604
- def remove_field(self, model, field):
646
+ def remove_field(self, model: type[Model], field: Field) -> None:
605
647
  """
606
648
  Remove a field from a model. Usually involves deleting a column,
607
649
  but for M2Ms may involve deleting a table.
@@ -616,7 +658,7 @@ class BaseDatabaseSchemaEditor:
616
658
  self.execute(self._delete_fk_sql(model, fk_name))
617
659
  # Delete the column
618
660
  sql = self.sql_delete_column % {
619
- "table": self.quote_name(model._meta.db_table),
661
+ "table": self.quote_name(model.model_options.db_table),
620
662
  "column": self.quote_name(field.column),
621
663
  }
622
664
  self.execute(sql)
@@ -626,11 +668,17 @@ class BaseDatabaseSchemaEditor:
626
668
  # Remove all deferred statements referencing the deleted column.
627
669
  for sql in list(self.deferred_sql):
628
670
  if isinstance(sql, Statement) and sql.references_column(
629
- model._meta.db_table, field.column
671
+ model.model_options.db_table, field.column
630
672
  ):
631
673
  self.deferred_sql.remove(sql)
632
674
 
633
- def alter_field(self, model, old_field, new_field, strict=False):
675
+ def alter_field(
676
+ self,
677
+ model: type[Model],
678
+ old_field: Field,
679
+ new_field: Field,
680
+ strict: bool = False,
681
+ ) -> None:
634
682
  """
635
683
  Allow a field's type, uniqueness, nullability, default, column,
636
684
  constraints, etc. to be modified.
@@ -655,7 +703,7 @@ class BaseDatabaseSchemaEditor:
655
703
  elif (
656
704
  old_type is None
657
705
  and new_type is None
658
- and (old_field.remote_field.through and new_field.remote_field.through)
706
+ and (old_field.remote_field.through and new_field.remote_field.through) # type: ignore[attr-defined]
659
707
  ):
660
708
  # Both sides have through models; this is a no-op.
661
709
  return
@@ -677,7 +725,9 @@ class BaseDatabaseSchemaEditor:
677
725
  strict,
678
726
  )
679
727
 
680
- def _field_db_check(self, field, field_db_params):
728
+ def _field_db_check(
729
+ self, field: Field, field_db_params: dict[str, Any]
730
+ ) -> str | None:
681
731
  # Always check constraints with the same mocked column name to avoid
682
732
  # recreating constrains when the column is renamed.
683
733
  check_constraints = self.connection.data_type_check_constraints
@@ -690,22 +740,22 @@ class BaseDatabaseSchemaEditor:
690
740
 
691
741
  def _alter_field(
692
742
  self,
693
- model,
694
- old_field,
695
- new_field,
696
- old_type,
697
- new_type,
698
- old_db_params,
699
- new_db_params,
700
- strict=False,
701
- ):
743
+ model: type[Model],
744
+ old_field: Field,
745
+ new_field: Field,
746
+ old_type: str,
747
+ new_type: str,
748
+ old_db_params: dict[str, Any],
749
+ new_db_params: dict[str, Any],
750
+ strict: bool = False,
751
+ ) -> None:
702
752
  """Perform a "physical" (non-ManyToMany) field update."""
703
753
  # Drop any FK constraints, we'll remake them later
704
754
  fks_dropped = set()
705
755
  if (
706
756
  self.connection.features.supports_foreign_keys
707
757
  and old_field.remote_field
708
- and old_field.db_constraint
758
+ and old_field.db_constraint # type: ignore[attr-defined]
709
759
  and self._field_should_be_altered(
710
760
  old_field,
711
761
  new_field,
@@ -717,7 +767,7 @@ class BaseDatabaseSchemaEditor:
717
767
  )
718
768
  if strict and len(fk_names) != 1:
719
769
  raise ValueError(
720
- f"Found wrong number ({len(fk_names)}) of foreign key constraints for {model._meta.db_table}.{old_field.column}"
770
+ f"Found wrong number ({len(fk_names)}) of foreign key constraints for {model.model_options.db_table}.{old_field.column}"
721
771
  )
722
772
  for fk_name in fk_names:
723
773
  fks_dropped.add((old_field.column,))
@@ -729,7 +779,7 @@ class BaseDatabaseSchemaEditor:
729
779
  ):
730
780
  # Find the unique constraint for this field
731
781
  meta_constraint_names = {
732
- constraint.name for constraint in model._meta.constraints
782
+ constraint.name for constraint in model.model_options.constraints
733
783
  }
734
784
  constraint_names = self._constraint_names(
735
785
  model,
@@ -740,7 +790,7 @@ class BaseDatabaseSchemaEditor:
740
790
  )
741
791
  if strict and len(constraint_names) != 1:
742
792
  raise ValueError(
743
- f"Found wrong number ({len(constraint_names)}) of unique constraints for {model._meta.db_table}.{old_field.column}"
793
+ f"Found wrong number ({len(constraint_names)}) of unique constraints for {model.model_options.db_table}.{old_field.column}"
744
794
  )
745
795
  for constraint_name in constraint_names:
746
796
  self.execute(self._delete_unique_sql(model, constraint_name))
@@ -754,7 +804,7 @@ class BaseDatabaseSchemaEditor:
754
804
  and ((old_type != new_type) or (old_collation != new_collation))
755
805
  )
756
806
  if drop_foreign_keys:
757
- # '_meta.related_field' also contains M2M reverse fields, these
807
+ # '_model_meta.related_field' also contains M2M reverse fields, these
758
808
  # will be filtered out
759
809
  for _old_rel, new_rel in _related_non_m2m_objects(old_field, new_field):
760
810
  rel_fk_names = self._constraint_names(
@@ -773,15 +823,15 @@ class BaseDatabaseSchemaEditor:
773
823
  # True | False | False | True
774
824
  # True | False | True | True
775
825
  if (
776
- (old_field.remote_field and old_field.db_index)
826
+ (old_field.remote_field and old_field.db_index) # type: ignore[attr-defined]
777
827
  and not old_field.primary_key
778
828
  and (
779
- not (new_field.remote_field and new_field.db_index)
829
+ not (new_field.remote_field and new_field.db_index) # type: ignore[attr-defined]
780
830
  or new_field.primary_key
781
831
  )
782
832
  ):
783
833
  # Find the index for this field
784
- meta_index_names = {index.name for index in model._meta.indexes}
834
+ meta_index_names = {index.name for index in model.model_options.indexes}
785
835
  # Retrieve only BTREE indexes since this is what's created with
786
836
  # db_index=True.
787
837
  index_names = self._constraint_names(
@@ -801,7 +851,7 @@ class BaseDatabaseSchemaEditor:
801
851
  new_db_check = self._field_db_check(new_field, new_db_params)
802
852
  if old_db_check != new_db_check and old_db_check:
803
853
  meta_constraint_names = {
804
- constraint.name for constraint in model._meta.constraints
854
+ constraint.name for constraint in model.model_options.constraints
805
855
  }
806
856
  constraint_names = self._constraint_names(
807
857
  model,
@@ -811,7 +861,7 @@ class BaseDatabaseSchemaEditor:
811
861
  )
812
862
  if strict and len(constraint_names) != 1:
813
863
  raise ValueError(
814
- f"Found wrong number ({len(constraint_names)}) of check constraints for {model._meta.db_table}.{old_field.column}"
864
+ f"Found wrong number ({len(constraint_names)}) of check constraints for {model.model_options.db_table}.{old_field.column}"
815
865
  )
816
866
  for constraint_name in constraint_names:
817
867
  self.execute(self._delete_check_sql(model, constraint_name))
@@ -819,14 +869,14 @@ class BaseDatabaseSchemaEditor:
819
869
  if old_field.column != new_field.column:
820
870
  self.execute(
821
871
  self._rename_field_sql(
822
- model._meta.db_table, old_field, new_field, new_type
872
+ model.model_options.db_table, old_field, new_field, new_type
823
873
  )
824
874
  )
825
875
  # Rename all references to the renamed column.
826
876
  for sql in self.deferred_sql:
827
877
  if isinstance(sql, Statement):
828
878
  sql.rename_column_references(
829
- model._meta.db_table, old_field.column, new_field.column
879
+ model.model_options.db_table, old_field.column, new_field.column
830
880
  )
831
881
  # Next, start accumulating actions to do
832
882
  actions = []
@@ -893,7 +943,7 @@ class BaseDatabaseSchemaEditor:
893
943
  self.execute(
894
944
  self.sql_alter_column
895
945
  % {
896
- "table": self.quote_name(model._meta.db_table),
946
+ "table": self.quote_name(model.model_options.db_table),
897
947
  "changes": sql,
898
948
  },
899
949
  params,
@@ -903,7 +953,7 @@ class BaseDatabaseSchemaEditor:
903
953
  self.execute(
904
954
  self.sql_update_with_default
905
955
  % {
906
- "table": self.quote_name(model._meta.db_table),
956
+ "table": self.quote_name(model.model_options.db_table),
907
957
  "column": self.quote_name(new_field.column),
908
958
  "default": "%s",
909
959
  },
@@ -915,7 +965,7 @@ class BaseDatabaseSchemaEditor:
915
965
  self.execute(
916
966
  self.sql_alter_column
917
967
  % {
918
- "table": self.quote_name(model._meta.db_table),
968
+ "table": self.quote_name(model.model_options.db_table),
919
969
  "changes": sql,
920
970
  },
921
971
  params,
@@ -938,10 +988,10 @@ class BaseDatabaseSchemaEditor:
938
988
  # True | True | True | False
939
989
  if (
940
990
  (
941
- not (old_field.remote_field and old_field.db_index)
991
+ not (old_field.remote_field and old_field.db_index) # type: ignore[attr-defined]
942
992
  or old_field.primary_key
943
993
  )
944
- and (new_field.remote_field and new_field.db_index)
994
+ and (new_field.remote_field and new_field.db_index) # type: ignore[attr-defined]
945
995
  and not new_field.primary_key
946
996
  ):
947
997
  self.execute(self._create_index_sql(model, fields=[new_field]))
@@ -974,7 +1024,9 @@ class BaseDatabaseSchemaEditor:
974
1024
  self.execute(
975
1025
  self.sql_alter_column
976
1026
  % {
977
- "table": self.quote_name(new_rel.related_model._meta.db_table),
1027
+ "table": self.quote_name(
1028
+ new_rel.related_model.model_options.db_table
1029
+ ),
978
1030
  "changes": fragment[0],
979
1031
  },
980
1032
  fragment[1],
@@ -986,9 +1038,9 @@ class BaseDatabaseSchemaEditor:
986
1038
  self.connection.features.supports_foreign_keys
987
1039
  and new_field.remote_field
988
1040
  and (
989
- fks_dropped or not old_field.remote_field or not old_field.db_constraint
1041
+ fks_dropped or not old_field.remote_field or not old_field.db_constraint # type: ignore[attr-defined]
990
1042
  )
991
- and new_field.db_constraint
1043
+ and new_field.db_constraint # type: ignore[attr-defined]
992
1044
  ):
993
1045
  self.execute(
994
1046
  self._create_fk_sql(model, new_field, "_fk_%(to_table)s_%(to_column)s")
@@ -1003,7 +1055,7 @@ class BaseDatabaseSchemaEditor:
1003
1055
  # Does it have check constraints we need to add?
1004
1056
  if old_db_check != new_db_check and new_db_check:
1005
1057
  constraint_name = self._create_index_name(
1006
- model._meta.db_table, [new_field.column], suffix="_check"
1058
+ model.model_options.db_table, [new_field.column], suffix="_check"
1007
1059
  )
1008
1060
  self.execute(
1009
1061
  self._create_check_sql(model, constraint_name, new_db_params["check"])
@@ -1015,7 +1067,7 @@ class BaseDatabaseSchemaEditor:
1015
1067
  model, old_field, new_field, drop=True
1016
1068
  )
1017
1069
  sql = self.sql_alter_column % {
1018
- "table": self.quote_name(model._meta.db_table),
1070
+ "table": self.quote_name(model.model_options.db_table),
1019
1071
  "changes": changes_sql,
1020
1072
  }
1021
1073
  self.execute(sql, params)
@@ -1023,7 +1075,9 @@ class BaseDatabaseSchemaEditor:
1023
1075
  if self.connection.features.connection_persists_old_columns:
1024
1076
  self.connection.close()
1025
1077
 
1026
- def _alter_column_null_sql(self, model, old_field, new_field):
1078
+ def _alter_column_null_sql(
1079
+ self, model: type[Model], old_field: Field, new_field: Field
1080
+ ) -> tuple[str, list[Any]]:
1027
1081
  """
1028
1082
  Hook to specialize column null alteration.
1029
1083
 
@@ -1045,7 +1099,13 @@ class BaseDatabaseSchemaEditor:
1045
1099
  [],
1046
1100
  )
1047
1101
 
1048
- def _alter_column_default_sql(self, model, old_field, new_field, drop=False):
1102
+ def _alter_column_default_sql(
1103
+ self,
1104
+ model: type[Model],
1105
+ old_field: Field | None,
1106
+ new_field: Field,
1107
+ drop: bool = False,
1108
+ ) -> tuple[str, list[Any]]:
1049
1109
  """
1050
1110
  Hook to specialize column default alteration.
1051
1111
 
@@ -1084,8 +1144,14 @@ class BaseDatabaseSchemaEditor:
1084
1144
  )
1085
1145
 
1086
1146
  def _alter_column_type_sql(
1087
- self, model, old_field, new_field, new_type, old_collation, new_collation
1088
- ):
1147
+ self,
1148
+ model: type[Model],
1149
+ old_field: Field,
1150
+ new_field: Field,
1151
+ new_type: str,
1152
+ old_collation: str | None,
1153
+ new_collation: str | None,
1154
+ ) -> tuple[tuple[str, list[Any]], list[tuple[str, list[Any]]]]:
1089
1155
  """
1090
1156
  Hook to specialize column type alteration for different backends,
1091
1157
  for cases when a creation type is different to an alteration type
@@ -1097,7 +1163,7 @@ class BaseDatabaseSchemaEditor:
1097
1163
  """
1098
1164
  other_actions = []
1099
1165
  if collate_sql := self._collate_sql(
1100
- new_collation, old_collation, model._meta.db_table
1166
+ new_collation, old_collation, model.model_options.db_table
1101
1167
  ):
1102
1168
  collate_sql = f" {collate_sql}"
1103
1169
  else:
@@ -1129,53 +1195,71 @@ class BaseDatabaseSchemaEditor:
1129
1195
  other_actions,
1130
1196
  )
1131
1197
 
1132
- def _alter_column_comment_sql(self, model, new_field, new_type, new_db_comment):
1198
+ def _alter_column_comment_sql(
1199
+ self,
1200
+ model: type[Model],
1201
+ new_field: Field,
1202
+ new_type: str,
1203
+ new_db_comment: str | None,
1204
+ ) -> tuple[str, list[Any]]:
1133
1205
  return (
1134
1206
  self.sql_alter_column_comment
1135
1207
  % {
1136
- "table": self.quote_name(model._meta.db_table),
1208
+ "table": self.quote_name(model.model_options.db_table),
1137
1209
  "column": self.quote_name(new_field.column),
1138
1210
  "comment": self._comment_sql(new_db_comment),
1139
1211
  },
1140
1212
  [],
1141
1213
  )
1142
1214
 
1143
- def _comment_sql(self, comment):
1215
+ def _comment_sql(self, comment: str | None) -> str:
1144
1216
  return self.quote_value(comment or "")
1145
1217
 
1146
- def _alter_many_to_many(self, model, old_field, new_field, strict):
1218
+ def _alter_many_to_many(
1219
+ self,
1220
+ model: type[Model],
1221
+ old_field: ManyToManyField,
1222
+ new_field: ManyToManyField,
1223
+ strict: bool,
1224
+ ) -> None:
1147
1225
  """Alter M2Ms to repoint their to= endpoints."""
1226
+ # Type narrow for ManyToManyField.remote_field
1227
+ old_rel: ManyToManyRel = old_field.remote_field # type: ignore[assignment]
1228
+ new_rel: ManyToManyRel = new_field.remote_field # type: ignore[assignment]
1229
+
1148
1230
  # Rename the through table
1149
1231
  if (
1150
- old_field.remote_field.through._meta.db_table
1151
- != new_field.remote_field.through._meta.db_table
1232
+ old_rel.through.model_options.db_table
1233
+ != new_rel.through.model_options.db_table
1152
1234
  ):
1153
1235
  self.alter_db_table(
1154
- old_field.remote_field.through,
1155
- old_field.remote_field.through._meta.db_table,
1156
- new_field.remote_field.through._meta.db_table,
1236
+ old_rel.through,
1237
+ old_rel.through.model_options.db_table,
1238
+ new_rel.through.model_options.db_table,
1157
1239
  )
1158
1240
  # Repoint the FK to the other side
1159
1241
  self.alter_field(
1160
- new_field.remote_field.through,
1242
+ new_rel.through,
1161
1243
  # The field that points to the target model is needed, so we can
1162
1244
  # tell alter_field to change it - this is m2m_reverse_field_name()
1163
1245
  # (as opposed to m2m_field_name(), which points to our model).
1164
- old_field.remote_field.through._meta.get_field(
1165
- old_field.m2m_reverse_field_name()
1246
+ old_rel.through._model_meta.get_field(
1247
+ old_field.m2m_reverse_field_name() # type: ignore[attr-defined]
1166
1248
  ),
1167
- new_field.remote_field.through._meta.get_field(
1168
- new_field.m2m_reverse_field_name()
1249
+ new_rel.through._model_meta.get_field(
1250
+ new_field.m2m_reverse_field_name() # type: ignore[attr-defined]
1169
1251
  ),
1170
1252
  )
1171
1253
  self.alter_field(
1172
- new_field.remote_field.through,
1254
+ new_rel.through,
1173
1255
  # for self-referential models we need to alter field from the other end too
1174
- old_field.remote_field.through._meta.get_field(old_field.m2m_field_name()),
1175
- new_field.remote_field.through._meta.get_field(new_field.m2m_field_name()),
1256
+ old_rel.through._model_meta.get_field(old_field.m2m_field_name()),
1257
+ new_rel.through._model_meta.get_field(new_field.m2m_field_name()),
1176
1258
  )
1177
1259
 
1178
- def _create_index_name(self, table_name, column_names, suffix=""):
1260
+ def _create_index_name(
1261
+ self, table_name: str, column_names: list[str], suffix: str = ""
1262
+ ) -> str:
1179
1263
  """
1180
1264
  Generate a unique name for an index/unique constraint.
1181
1265
 
@@ -1208,34 +1292,36 @@ class BaseDatabaseSchemaEditor:
1208
1292
  index_name = f"D{index_name[:-1]}"
1209
1293
  return index_name
1210
1294
 
1211
- def _index_condition_sql(self, condition):
1295
+ def _index_condition_sql(self, condition: str | None) -> str:
1212
1296
  if condition:
1213
1297
  return " WHERE " + condition
1214
1298
  return ""
1215
1299
 
1216
- def _index_include_sql(self, model, columns):
1300
+ def _index_include_sql(
1301
+ self, model: type[Model], columns: list[str] | None
1302
+ ) -> str | Statement:
1217
1303
  if not columns or not self.connection.features.supports_covering_indexes:
1218
1304
  return ""
1219
1305
  return Statement(
1220
1306
  " INCLUDE (%(columns)s)",
1221
- columns=Columns(model._meta.db_table, columns, self.quote_name),
1307
+ columns=Columns(model.model_options.db_table, columns, self.quote_name),
1222
1308
  )
1223
1309
 
1224
1310
  def _create_index_sql(
1225
1311
  self,
1226
- model,
1312
+ model: type[Model],
1227
1313
  *,
1228
- fields=None,
1229
- name=None,
1230
- suffix="",
1231
- using="",
1232
- col_suffixes=(),
1233
- sql=None,
1234
- opclasses=(),
1235
- condition=None,
1236
- include=None,
1237
- expressions=None,
1238
- ):
1314
+ fields: list[Field] | None = None,
1315
+ name: str | None = None,
1316
+ suffix: str = "",
1317
+ using: str = "",
1318
+ col_suffixes: tuple[str, ...] = (),
1319
+ sql: str | None = None,
1320
+ opclasses: tuple[str, ...] = (),
1321
+ condition: str | None = None,
1322
+ include: list[str] | None = None,
1323
+ expressions: Any = None,
1324
+ ) -> Statement:
1239
1325
  """
1240
1326
  Return the SQL statement to create the index for one or several fields
1241
1327
  or expressions. `sql` can be specified if the syntax differs from the
@@ -1246,9 +1332,9 @@ class BaseDatabaseSchemaEditor:
1246
1332
  compiler = Query(model, alias_cols=False).get_compiler()
1247
1333
  columns = [field.column for field in fields]
1248
1334
  sql_create_index = sql or self.sql_create_index
1249
- table = model._meta.db_table
1335
+ table = model.model_options.db_table
1250
1336
 
1251
- def create_index_name(*args, **kwargs):
1337
+ def create_index_name(*args: Any, **kwargs: Any) -> str:
1252
1338
  nonlocal name
1253
1339
  if name is None:
1254
1340
  name = self._create_index_name(*args, **kwargs)
@@ -1269,33 +1355,43 @@ class BaseDatabaseSchemaEditor:
1269
1355
  include=self._index_include_sql(model, include),
1270
1356
  )
1271
1357
 
1272
- def _delete_index_sql(self, model, name, sql=None):
1358
+ def _delete_index_sql(
1359
+ self, model: type[Model], name: str, sql: str | None = None
1360
+ ) -> Statement:
1273
1361
  return Statement(
1274
1362
  sql or self.sql_delete_index,
1275
- table=Table(model._meta.db_table, self.quote_name),
1363
+ table=Table(model.model_options.db_table, self.quote_name),
1276
1364
  name=self.quote_name(name),
1277
1365
  )
1278
1366
 
1279
- def _rename_index_sql(self, model, old_name, new_name):
1367
+ def _rename_index_sql(
1368
+ self, model: type[Model], old_name: str, new_name: str
1369
+ ) -> Statement:
1280
1370
  return Statement(
1281
1371
  self.sql_rename_index,
1282
- table=Table(model._meta.db_table, self.quote_name),
1372
+ table=Table(model.model_options.db_table, self.quote_name),
1283
1373
  old_name=self.quote_name(old_name),
1284
1374
  new_name=self.quote_name(new_name),
1285
1375
  )
1286
1376
 
1287
- def _index_columns(self, table, columns, col_suffixes, opclasses):
1377
+ def _index_columns(
1378
+ self,
1379
+ table: str,
1380
+ columns: list[str],
1381
+ col_suffixes: tuple[str, ...],
1382
+ opclasses: tuple[str, ...],
1383
+ ) -> Columns:
1288
1384
  return Columns(table, columns, self.quote_name, col_suffixes=col_suffixes)
1289
1385
 
1290
- def _model_indexes_sql(self, model):
1386
+ def _model_indexes_sql(self, model: type[Model]) -> list[Statement | None]:
1291
1387
  """
1292
1388
  Return a list of all index SQL statements (field indexes, Meta.indexes) for the specified model.
1293
1389
  """
1294
- output = []
1295
- for field in model._meta.local_fields:
1390
+ output: list[Statement | None] = []
1391
+ for field in model._model_meta.local_fields:
1296
1392
  output.extend(self._field_indexes_sql(model, field))
1297
1393
 
1298
- for index in model._meta.indexes:
1394
+ for index in model.model_options.indexes:
1299
1395
  if (
1300
1396
  not index.contains_expressions
1301
1397
  or self.connection.features.supports_expression_indexes
@@ -1303,16 +1399,18 @@ class BaseDatabaseSchemaEditor:
1303
1399
  output.append(index.create_sql(model, self))
1304
1400
  return output
1305
1401
 
1306
- def _field_indexes_sql(self, model, field):
1402
+ def _field_indexes_sql(self, model: type[Model], field: Field) -> list[Statement]:
1307
1403
  """
1308
1404
  Return a list of all index SQL statements for the specified field.
1309
1405
  """
1310
- output = []
1406
+ output: list[Statement] = []
1311
1407
  if self._field_should_be_indexed(model, field):
1312
1408
  output.append(self._create_index_sql(model, fields=[field]))
1313
1409
  return output
1314
1410
 
1315
- def _field_should_be_altered(self, old_field, new_field, ignore=None):
1411
+ def _field_should_be_altered(
1412
+ self, old_field: Field, new_field: Field, ignore: set[str] | None = None
1413
+ ) -> bool:
1316
1414
  ignore = ignore or set()
1317
1415
  _, old_path, old_args, old_kwargs = old_field.deconstruct()
1318
1416
  _, new_path, new_args, new_kwargs = new_field.deconstruct()
@@ -1321,21 +1419,23 @@ class BaseDatabaseSchemaEditor:
1321
1419
  # - changing an attribute that doesn't affect the schema
1322
1420
  # - changing an attribute in the provided set of ignored attributes
1323
1421
  # - adding only a db_column and the column name is not changed
1324
- for attr in ignore.union(old_field.non_db_attrs):
1422
+ for attr in ignore.union(old_field.non_db_attrs): # type: ignore[attr-defined]
1325
1423
  old_kwargs.pop(attr, None)
1326
- for attr in ignore.union(new_field.non_db_attrs):
1424
+ for attr in ignore.union(new_field.non_db_attrs): # type: ignore[attr-defined]
1327
1425
  new_kwargs.pop(attr, None)
1328
1426
  return self.quote_name(old_field.column) != self.quote_name(
1329
1427
  new_field.column
1330
1428
  ) or (old_path, old_args, old_kwargs) != (new_path, new_args, new_kwargs)
1331
1429
 
1332
- def _field_should_be_indexed(self, model, field):
1333
- return (field.remote_field and field.db_index) and not field.primary_key
1430
+ def _field_should_be_indexed(self, model: type[Model], field: Field) -> bool:
1431
+ return (field.remote_field and field.db_index) and not field.primary_key # type: ignore[attr-defined]
1334
1432
 
1335
- def _field_became_primary_key(self, old_field, new_field):
1433
+ def _field_became_primary_key(self, old_field: Field, new_field: Field) -> bool:
1336
1434
  return not old_field.primary_key and new_field.primary_key
1337
1435
 
1338
- def _rename_field_sql(self, table, old_field, new_field, new_type):
1436
+ def _rename_field_sql(
1437
+ self, table: str, old_field: Field, new_field: Field, new_type: str
1438
+ ) -> str:
1339
1439
  return self.sql_rename_column % {
1340
1440
  "table": self.quote_name(table),
1341
1441
  "old_column": self.quote_name(old_field.column),
@@ -1343,14 +1443,18 @@ class BaseDatabaseSchemaEditor:
1343
1443
  "type": new_type,
1344
1444
  }
1345
1445
 
1346
- def _create_fk_sql(self, model, field, suffix):
1347
- table = Table(model._meta.db_table, self.quote_name)
1446
+ def _create_fk_sql(
1447
+ self, model: type[Model], field: ForeignKey, suffix: str
1448
+ ) -> Statement:
1449
+ table = Table(model.model_options.db_table, self.quote_name)
1348
1450
  name = self._fk_constraint_name(model, field, suffix)
1349
- column = Columns(model._meta.db_table, [field.column], self.quote_name)
1350
- to_table = Table(field.target_field.model._meta.db_table, self.quote_name)
1451
+ column = Columns(model.model_options.db_table, [field.column], self.quote_name)
1452
+ to_table = Table(
1453
+ field.target_field.model.model_options.db_table, self.quote_name
1454
+ )
1351
1455
  to_column = Columns(
1352
- field.target_field.model._meta.db_table,
1353
- [field.target_field.column],
1456
+ field.target_field.model.model_options.db_table,
1457
+ [field.target_field.column], # type: ignore[attr-defined]
1354
1458
  self.quote_name,
1355
1459
  )
1356
1460
  deferrable = self.connection.ops.deferrable_sql()
@@ -1364,41 +1468,44 @@ class BaseDatabaseSchemaEditor:
1364
1468
  deferrable=deferrable,
1365
1469
  )
1366
1470
 
1367
- def _fk_constraint_name(self, model, field, suffix):
1368
- def create_fk_name(*args, **kwargs):
1471
+ def _fk_constraint_name(
1472
+ self, model: type[Model], field: ForeignKey, suffix: str
1473
+ ) -> ForeignKeyName:
1474
+ def create_fk_name(*args: Any, **kwargs: Any) -> str:
1369
1475
  return self.quote_name(self._create_index_name(*args, **kwargs))
1370
1476
 
1371
1477
  return ForeignKeyName(
1372
- model._meta.db_table,
1478
+ model.model_options.db_table,
1373
1479
  [field.column],
1374
- split_identifier(field.target_field.model._meta.db_table)[1],
1375
- [field.target_field.column],
1480
+ split_identifier(field.target_field.model.model_options.db_table)[1],
1481
+ [field.target_field.column], # type: ignore[attr-defined]
1376
1482
  suffix,
1377
1483
  create_fk_name,
1378
1484
  )
1379
1485
 
1380
- def _delete_fk_sql(self, model, name):
1486
+ def _delete_fk_sql(self, model: type[Model], name: str) -> Statement:
1381
1487
  return self._delete_constraint_sql(self.sql_delete_fk, model, name)
1382
1488
 
1383
- def _deferrable_constraint_sql(self, deferrable):
1489
+ def _deferrable_constraint_sql(self, deferrable: Deferrable | None) -> str:
1384
1490
  if deferrable is None:
1385
1491
  return ""
1386
1492
  if deferrable == Deferrable.DEFERRED:
1387
1493
  return " DEFERRABLE INITIALLY DEFERRED"
1388
1494
  if deferrable == Deferrable.IMMEDIATE:
1389
1495
  return " DEFERRABLE INITIALLY IMMEDIATE"
1496
+ return ""
1390
1497
 
1391
1498
  def _unique_sql(
1392
1499
  self,
1393
- model,
1394
- fields,
1395
- name,
1396
- condition=None,
1397
- deferrable=None,
1398
- include=None,
1399
- opclasses=None,
1400
- expressions=None,
1401
- ):
1500
+ model: type[Model],
1501
+ fields: Iterable[Field],
1502
+ name: str,
1503
+ condition: str | None = None,
1504
+ deferrable: Deferrable | None = None,
1505
+ include: list[str] | None = None,
1506
+ opclasses: tuple[str, ...] | None = None,
1507
+ expressions: Any = None,
1508
+ ) -> str | None:
1402
1509
  if (
1403
1510
  deferrable
1404
1511
  and not self.connection.features.supports_deferrable_unique_constraints
@@ -1430,15 +1537,15 @@ class BaseDatabaseSchemaEditor:
1430
1537
 
1431
1538
  def _create_unique_sql(
1432
1539
  self,
1433
- model,
1434
- fields,
1435
- name=None,
1436
- condition=None,
1437
- deferrable=None,
1438
- include=None,
1439
- opclasses=None,
1440
- expressions=None,
1441
- ):
1540
+ model: type[Model],
1541
+ fields: Iterable[Field],
1542
+ name: str | None = None,
1543
+ condition: str | None = None,
1544
+ deferrable: Deferrable | None = None,
1545
+ include: list[str] | None = None,
1546
+ opclasses: tuple[str, ...] | None = None,
1547
+ expressions: Any = None,
1548
+ ) -> Statement | None:
1442
1549
  if (
1443
1550
  (
1444
1551
  deferrable
@@ -1453,7 +1560,7 @@ class BaseDatabaseSchemaEditor:
1453
1560
  return None
1454
1561
 
1455
1562
  compiler = Query(model, alias_cols=False).get_compiler()
1456
- table = model._meta.db_table
1563
+ table = model.model_options.db_table
1457
1564
  columns = [field.column for field in fields]
1458
1565
  if name is None:
1459
1566
  name = self._unique_constraint_name(table, columns, quote=True)
@@ -1464,25 +1571,27 @@ class BaseDatabaseSchemaEditor:
1464
1571
  else:
1465
1572
  sql = self.sql_create_unique
1466
1573
  if columns:
1467
- columns = self._index_columns(
1574
+ columns_obj: Columns | Expressions = self._index_columns(
1468
1575
  table, columns, col_suffixes=(), opclasses=opclasses
1469
1576
  )
1470
1577
  else:
1471
- columns = Expressions(table, expressions, compiler, self.quote_value)
1578
+ columns_obj = Expressions(table, expressions, compiler, self.quote_value)
1472
1579
  return Statement(
1473
1580
  sql,
1474
1581
  table=Table(table, self.quote_name),
1475
1582
  name=name,
1476
- columns=columns,
1583
+ columns=columns_obj,
1477
1584
  condition=self._index_condition_sql(condition),
1478
1585
  deferrable=self._deferrable_constraint_sql(deferrable),
1479
1586
  include=self._index_include_sql(model, include),
1480
1587
  )
1481
1588
 
1482
- def _unique_constraint_name(self, table, columns, quote=True):
1589
+ def _unique_constraint_name(
1590
+ self, table: str, columns: list[str], quote: bool = True
1591
+ ) -> IndexName | str:
1483
1592
  if quote:
1484
1593
 
1485
- def create_unique_name(*args, **kwargs):
1594
+ def create_unique_name(*args: Any, **kwargs: Any) -> str:
1486
1595
  return self.quote_name(self._create_index_name(*args, **kwargs))
1487
1596
 
1488
1597
  else:
@@ -1492,14 +1601,14 @@ class BaseDatabaseSchemaEditor:
1492
1601
 
1493
1602
  def _delete_unique_sql(
1494
1603
  self,
1495
- model,
1496
- name,
1497
- condition=None,
1498
- deferrable=None,
1499
- include=None,
1500
- opclasses=None,
1501
- expressions=None,
1502
- ):
1604
+ model: type[Model],
1605
+ name: str,
1606
+ condition: str | None = None,
1607
+ deferrable: Deferrable | None = None,
1608
+ include: list[str] | None = None,
1609
+ opclasses: tuple[str, ...] | None = None,
1610
+ expressions: Any = None,
1611
+ ) -> Statement | None:
1503
1612
  if (
1504
1613
  (
1505
1614
  deferrable
@@ -1518,46 +1627,50 @@ class BaseDatabaseSchemaEditor:
1518
1627
  sql = self.sql_delete_unique
1519
1628
  return self._delete_constraint_sql(sql, model, name)
1520
1629
 
1521
- def _check_sql(self, name, check):
1630
+ def _check_sql(self, name: str, check: str) -> str:
1522
1631
  return self.sql_constraint % {
1523
1632
  "name": self.quote_name(name),
1524
1633
  "constraint": self.sql_check_constraint % {"check": check},
1525
1634
  }
1526
1635
 
1527
- def _create_check_sql(self, model, name, check):
1636
+ def _create_check_sql(
1637
+ self, model: type[Model], name: str, check: str
1638
+ ) -> Statement | None:
1528
1639
  if not self.connection.features.supports_table_check_constraints:
1529
1640
  return None
1530
1641
  return Statement(
1531
1642
  self.sql_create_check,
1532
- table=Table(model._meta.db_table, self.quote_name),
1643
+ table=Table(model.model_options.db_table, self.quote_name),
1533
1644
  name=self.quote_name(name),
1534
1645
  check=check,
1535
1646
  )
1536
1647
 
1537
- def _delete_check_sql(self, model, name):
1648
+ def _delete_check_sql(self, model: type[Model], name: str) -> Statement | None:
1538
1649
  if not self.connection.features.supports_table_check_constraints:
1539
1650
  return None
1540
1651
  return self._delete_constraint_sql(self.sql_delete_check, model, name)
1541
1652
 
1542
- def _delete_constraint_sql(self, template, model, name):
1653
+ def _delete_constraint_sql(
1654
+ self, template: str, model: type[Model], name: str
1655
+ ) -> Statement:
1543
1656
  return Statement(
1544
1657
  template,
1545
- table=Table(model._meta.db_table, self.quote_name),
1658
+ table=Table(model.model_options.db_table, self.quote_name),
1546
1659
  name=self.quote_name(name),
1547
1660
  )
1548
1661
 
1549
1662
  def _constraint_names(
1550
1663
  self,
1551
- model,
1552
- column_names=None,
1553
- unique=None,
1554
- primary_key=None,
1555
- index=None,
1556
- foreign_key=None,
1557
- check=None,
1558
- type_=None,
1559
- exclude=None,
1560
- ):
1664
+ model: type[Model],
1665
+ column_names: list[str] | None = None,
1666
+ unique: bool | None = None,
1667
+ primary_key: bool | None = None,
1668
+ index: bool | None = None,
1669
+ foreign_key: bool | None = None,
1670
+ check: bool | None = None,
1671
+ type_: str | None = None,
1672
+ exclude: set[str] | None = None,
1673
+ ) -> list[str]:
1561
1674
  """Return all constraint names matching the columns and conditions."""
1562
1675
  if column_names is not None:
1563
1676
  column_names = [
@@ -1570,9 +1683,9 @@ class BaseDatabaseSchemaEditor:
1570
1683
  ]
1571
1684
  with self.connection.cursor() as cursor:
1572
1685
  constraints = self.connection.introspection.get_constraints(
1573
- cursor, model._meta.db_table
1686
+ cursor, model.model_options.db_table
1574
1687
  )
1575
- result = []
1688
+ result: list[str] = []
1576
1689
  for name, infodict in constraints.items():
1577
1690
  if column_names is None or column_names == infodict["columns"]:
1578
1691
  if unique is not None and infodict["unique"] != unique:
@@ -1591,29 +1704,36 @@ class BaseDatabaseSchemaEditor:
1591
1704
  result.append(name)
1592
1705
  return result
1593
1706
 
1594
- def _delete_primary_key(self, model, strict=False):
1707
+ def _delete_primary_key(self, model: type[Model], strict: bool = False) -> None:
1595
1708
  constraint_names = self._constraint_names(model, primary_key=True)
1596
1709
  if strict and len(constraint_names) != 1:
1597
1710
  raise ValueError(
1598
- f"Found wrong number ({len(constraint_names)}) of PK constraints for {model._meta.db_table}"
1711
+ f"Found wrong number ({len(constraint_names)}) of PK constraints for {model.model_options.db_table}"
1599
1712
  )
1600
1713
  for constraint_name in constraint_names:
1601
1714
  self.execute(self._delete_primary_key_sql(model, constraint_name))
1602
1715
 
1603
- def _create_primary_key_sql(self, model, field):
1716
+ def _create_primary_key_sql(self, model: type[Model], field: Field) -> Statement:
1604
1717
  return Statement(
1605
1718
  self.sql_create_pk,
1606
- table=Table(model._meta.db_table, self.quote_name),
1719
+ table=Table(model.model_options.db_table, self.quote_name),
1607
1720
  name=self.quote_name(
1608
1721
  self._create_index_name(
1609
- model._meta.db_table, [field.column], suffix="_pk"
1722
+ model.model_options.db_table, [field.column], suffix="_pk"
1610
1723
  )
1611
1724
  ),
1612
- columns=Columns(model._meta.db_table, [field.column], self.quote_name),
1725
+ columns=Columns(
1726
+ model.model_options.db_table, [field.column], self.quote_name
1727
+ ),
1613
1728
  )
1614
1729
 
1615
- def _delete_primary_key_sql(self, model, name):
1730
+ def _delete_primary_key_sql(self, model: type[Model], name: str) -> Statement:
1616
1731
  return self._delete_constraint_sql(self.sql_delete_pk, model, name)
1617
1732
 
1618
- def _collate_sql(self, collation, old_collation=None, table_name=None):
1733
+ def _collate_sql(
1734
+ self,
1735
+ collation: str | None,
1736
+ old_collation: str | None = None,
1737
+ table_name: str | None = None,
1738
+ ) -> str:
1619
1739
  return "COLLATE " + self.quote_name(collation) if collation else ""