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.
- plain/models/CHANGELOG.md +27 -0
- plain/models/README.md +26 -42
- plain/models/__init__.py +2 -0
- plain/models/aggregates.py +42 -19
- plain/models/backends/base/base.py +125 -105
- plain/models/backends/base/client.py +11 -3
- plain/models/backends/base/creation.py +24 -14
- plain/models/backends/base/features.py +10 -4
- plain/models/backends/base/introspection.py +37 -20
- plain/models/backends/base/operations.py +187 -91
- plain/models/backends/base/schema.py +338 -218
- plain/models/backends/base/validation.py +13 -4
- plain/models/backends/ddl_references.py +85 -43
- plain/models/backends/mysql/base.py +29 -26
- plain/models/backends/mysql/client.py +7 -2
- plain/models/backends/mysql/compiler.py +13 -4
- plain/models/backends/mysql/creation.py +5 -2
- plain/models/backends/mysql/features.py +24 -22
- plain/models/backends/mysql/introspection.py +22 -13
- plain/models/backends/mysql/operations.py +107 -40
- plain/models/backends/mysql/schema.py +52 -28
- plain/models/backends/mysql/validation.py +13 -6
- plain/models/backends/postgresql/base.py +41 -34
- plain/models/backends/postgresql/client.py +7 -2
- plain/models/backends/postgresql/creation.py +10 -5
- plain/models/backends/postgresql/introspection.py +15 -8
- plain/models/backends/postgresql/operations.py +110 -43
- plain/models/backends/postgresql/schema.py +88 -49
- plain/models/backends/sqlite3/_functions.py +151 -115
- plain/models/backends/sqlite3/base.py +37 -23
- plain/models/backends/sqlite3/client.py +7 -1
- plain/models/backends/sqlite3/creation.py +9 -5
- plain/models/backends/sqlite3/features.py +5 -3
- plain/models/backends/sqlite3/introspection.py +32 -16
- plain/models/backends/sqlite3/operations.py +126 -43
- plain/models/backends/sqlite3/schema.py +127 -92
- plain/models/backends/utils.py +52 -29
- plain/models/backups/cli.py +8 -6
- plain/models/backups/clients.py +16 -7
- plain/models/backups/core.py +24 -13
- plain/models/base.py +221 -229
- plain/models/cli.py +98 -67
- plain/models/config.py +1 -1
- plain/models/connections.py +23 -7
- plain/models/constraints.py +79 -56
- plain/models/database_url.py +1 -1
- plain/models/db.py +6 -2
- plain/models/deletion.py +80 -56
- plain/models/entrypoints.py +1 -1
- plain/models/enums.py +22 -11
- plain/models/exceptions.py +23 -8
- plain/models/expressions.py +441 -258
- plain/models/fields/__init__.py +272 -217
- plain/models/fields/json.py +123 -57
- plain/models/fields/mixins.py +12 -8
- plain/models/fields/related.py +324 -290
- plain/models/fields/related_descriptors.py +33 -24
- plain/models/fields/related_lookups.py +24 -12
- plain/models/fields/related_managers.py +102 -79
- plain/models/fields/reverse_related.py +66 -63
- plain/models/forms.py +101 -75
- plain/models/functions/comparison.py +71 -18
- plain/models/functions/datetime.py +79 -29
- plain/models/functions/math.py +43 -10
- plain/models/functions/mixins.py +24 -7
- plain/models/functions/text.py +104 -25
- plain/models/functions/window.py +12 -6
- plain/models/indexes.py +57 -32
- plain/models/lookups.py +228 -153
- plain/models/meta.py +505 -0
- plain/models/migrations/autodetector.py +86 -43
- plain/models/migrations/exceptions.py +7 -3
- plain/models/migrations/executor.py +33 -7
- plain/models/migrations/graph.py +79 -50
- plain/models/migrations/loader.py +45 -22
- plain/models/migrations/migration.py +23 -18
- plain/models/migrations/operations/base.py +38 -20
- plain/models/migrations/operations/fields.py +95 -48
- plain/models/migrations/operations/models.py +246 -142
- plain/models/migrations/operations/special.py +82 -25
- plain/models/migrations/optimizer.py +7 -2
- plain/models/migrations/questioner.py +58 -31
- plain/models/migrations/recorder.py +27 -16
- plain/models/migrations/serializer.py +50 -39
- plain/models/migrations/state.py +232 -156
- plain/models/migrations/utils.py +30 -14
- plain/models/migrations/writer.py +17 -14
- plain/models/options.py +189 -518
- plain/models/otel.py +16 -6
- plain/models/preflight.py +42 -17
- plain/models/query.py +400 -251
- plain/models/query_utils.py +109 -69
- plain/models/registry.py +40 -21
- plain/models/sql/compiler.py +190 -127
- plain/models/sql/datastructures.py +38 -25
- plain/models/sql/query.py +320 -225
- plain/models/sql/subqueries.py +36 -25
- plain/models/sql/where.py +54 -29
- plain/models/test/pytest.py +15 -11
- plain/models/test/utils.py +4 -2
- plain/models/transaction.py +20 -7
- plain/models/utils.py +17 -6
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/METADATA +27 -43
- plain_models-0.51.0.dist-info/RECORD +123 -0
- plain_models-0.49.2.dist-info/RECORD +0 -122
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/WHEEL +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/entry_points.txt +0 -0
- {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
|
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,
|
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(
|
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.
|
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.
|
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,
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
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
|
-
|
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
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
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(
|
294
|
-
|
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.
|
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.
|
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.
|
316
|
-
model.
|
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.
|
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.
|
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.
|
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
|
-
|
428
|
-
for remote_field in
|
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
|
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(
|
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
|
-
|
447
|
-
==
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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(
|
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(
|
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(
|
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
|