plain.models 0.33.1__py3-none-any.whl → 0.34.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 +17 -0
- plain/models/README.md +8 -10
- plain/models/__init__.py +2 -6
- plain/models/backends/base/base.py +10 -18
- plain/models/backends/base/creation.py +3 -4
- plain/models/backends/base/introspection.py +2 -3
- plain/models/backends/base/schema.py +3 -9
- plain/models/backends/mysql/validation.py +1 -1
- plain/models/backends/postgresql/base.py +15 -23
- plain/models/backends/postgresql/schema.py +0 -2
- plain/models/backends/sqlite3/base.py +1 -1
- plain/models/backends/sqlite3/creation.py +2 -2
- plain/models/backends/sqlite3/features.py +1 -1
- plain/models/backends/sqlite3/schema.py +1 -1
- plain/models/backends/utils.py +2 -6
- plain/models/backups/core.py +15 -22
- plain/models/base.py +179 -225
- plain/models/cli.py +25 -62
- plain/models/connections.py +48 -165
- plain/models/constraints.py +10 -10
- plain/models/db.py +7 -15
- plain/models/default_settings.py +13 -20
- plain/models/deletion.py +14 -16
- plain/models/expressions.py +7 -10
- plain/models/fields/__init__.py +56 -76
- plain/models/fields/json.py +9 -12
- plain/models/fields/related.py +5 -17
- plain/models/fields/related_descriptors.py +43 -95
- plain/models/forms.py +2 -4
- plain/models/indexes.py +2 -3
- plain/models/lookups.py +0 -7
- plain/models/manager.py +1 -14
- plain/models/migrations/executor.py +0 -16
- plain/models/migrations/loader.py +1 -1
- plain/models/migrations/migration.py +1 -1
- plain/models/migrations/operations/base.py +4 -11
- plain/models/migrations/operations/fields.py +4 -4
- plain/models/migrations/operations/models.py +10 -10
- plain/models/migrations/operations/special.py +6 -14
- plain/models/migrations/recorder.py +1 -1
- plain/models/options.py +4 -7
- plain/models/preflight.py +25 -44
- plain/models/query.py +47 -102
- plain/models/query_utils.py +4 -4
- plain/models/sql/compiler.py +7 -11
- plain/models/sql/query.py +32 -42
- plain/models/sql/subqueries.py +6 -8
- plain/models/sql/where.py +1 -1
- plain/models/test/pytest.py +21 -32
- plain/models/test/utils.py +7 -143
- plain/models/transaction.py +66 -164
- {plain_models-0.33.1.dist-info → plain_models-0.34.0.dist-info}/METADATA +9 -11
- {plain_models-0.33.1.dist-info → plain_models-0.34.0.dist-info}/RECORD +56 -55
- {plain_models-0.33.1.dist-info → plain_models-0.34.0.dist-info}/WHEEL +0 -0
- {plain_models-0.33.1.dist-info → plain_models-0.34.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.33.1.dist-info → plain_models-0.34.0.dist-info}/licenses/LICENSE +0 -0
plain/models/base.py
CHANGED
@@ -16,11 +16,9 @@ from plain.models import models_registry, transaction
|
|
16
16
|
from plain.models.constants import LOOKUP_SEP
|
17
17
|
from plain.models.constraints import CheckConstraint, UniqueConstraint
|
18
18
|
from plain.models.db import (
|
19
|
-
DEFAULT_DB_ALIAS,
|
20
19
|
PLAIN_VERSION_PICKLE_KEY,
|
21
20
|
DatabaseError,
|
22
|
-
|
23
|
-
router,
|
21
|
+
db_connection,
|
24
22
|
)
|
25
23
|
from plain.models.deletion import Collector
|
26
24
|
from plain.models.expressions import RawSQL, Value
|
@@ -202,7 +200,6 @@ class ModelStateFieldsCacheDescriptor:
|
|
202
200
|
class ModelState:
|
203
201
|
"""Store model instance state."""
|
204
202
|
|
205
|
-
db = None
|
206
203
|
# If true, uniqueness validation checks will consider this a new, unsaved
|
207
204
|
# object. Necessary for correct validation of new instances of objects with
|
208
205
|
# explicit (non-auto) PKs. This impacts validation only; it has no effect
|
@@ -322,7 +319,7 @@ class Model(metaclass=ModelBase):
|
|
322
319
|
super().__init__()
|
323
320
|
|
324
321
|
@classmethod
|
325
|
-
def from_db(cls,
|
322
|
+
def from_db(cls, field_names, values):
|
326
323
|
if len(values) != len(cls._meta.concrete_fields):
|
327
324
|
values_iter = iter(values)
|
328
325
|
values = [
|
@@ -331,7 +328,6 @@ class Model(metaclass=ModelBase):
|
|
331
328
|
]
|
332
329
|
new = cls(*values)
|
333
330
|
new._state.adding = False
|
334
|
-
new._state.db = db
|
335
331
|
return new
|
336
332
|
|
337
333
|
def __repr__(self):
|
@@ -418,7 +414,7 @@ class Model(metaclass=ModelBase):
|
|
418
414
|
if f.attname not in self.__dict__
|
419
415
|
}
|
420
416
|
|
421
|
-
def refresh_from_db(self,
|
417
|
+
def refresh_from_db(self, fields=None):
|
422
418
|
"""
|
423
419
|
Reload field values from the database.
|
424
420
|
|
@@ -449,10 +445,7 @@ class Model(metaclass=ModelBase):
|
|
449
445
|
"are not allowed in fields."
|
450
446
|
)
|
451
447
|
|
452
|
-
|
453
|
-
db_instance_qs = self.__class__._base_manager.db_manager(
|
454
|
-
using, hints=hints
|
455
|
-
).filter(pk=self.pk)
|
448
|
+
db_instance_qs = self.__class__._base_manager.get_queryset().filter(pk=self.pk)
|
456
449
|
|
457
450
|
# Use provided fields, if not set then reload all non-deferred fields.
|
458
451
|
deferred_fields = self.get_deferred_fields()
|
@@ -483,8 +476,6 @@ class Model(metaclass=ModelBase):
|
|
483
476
|
if field.is_cached(self):
|
484
477
|
field.delete_cached_value(self)
|
485
478
|
|
486
|
-
self._state.db = db_instance._state.db
|
487
|
-
|
488
479
|
def serializable_value(self, field_name):
|
489
480
|
"""
|
490
481
|
Return the value of the field name for this instance. If the field is
|
@@ -508,7 +499,6 @@ class Model(metaclass=ModelBase):
|
|
508
499
|
clean_and_validate=True,
|
509
500
|
force_insert=False,
|
510
501
|
force_update=False,
|
511
|
-
using=None,
|
512
502
|
update_fields=None,
|
513
503
|
):
|
514
504
|
"""
|
@@ -521,7 +511,6 @@ class Model(metaclass=ModelBase):
|
|
521
511
|
"""
|
522
512
|
self._prepare_related_fields_for_save(operation_name="save")
|
523
513
|
|
524
|
-
using = using or router.db_for_write(self.__class__, instance=self)
|
525
514
|
if force_insert and (force_update or update_fields):
|
526
515
|
raise ValueError("Cannot force both insert and updating in model saving.")
|
527
516
|
|
@@ -545,9 +534,9 @@ class Model(metaclass=ModelBase):
|
|
545
534
|
)
|
546
535
|
)
|
547
536
|
|
548
|
-
# If
|
549
|
-
#
|
550
|
-
elif not force_insert and deferred_fields
|
537
|
+
# If this model is deferred, automatically do an "update_fields" save
|
538
|
+
# on the loaded fields.
|
539
|
+
elif not force_insert and deferred_fields:
|
551
540
|
field_names = set()
|
552
541
|
for field in self._meta.concrete_fields:
|
553
542
|
if not field.primary_key and not hasattr(field, "through"):
|
@@ -560,7 +549,6 @@ class Model(metaclass=ModelBase):
|
|
560
549
|
self.full_clean(exclude=deferred_fields)
|
561
550
|
|
562
551
|
self.save_base(
|
563
|
-
using=using,
|
564
552
|
force_insert=force_insert,
|
565
553
|
force_update=force_update,
|
566
554
|
update_fields=update_fields,
|
@@ -572,7 +560,6 @@ class Model(metaclass=ModelBase):
|
|
572
560
|
raw=False,
|
573
561
|
force_insert=False,
|
574
562
|
force_update=False,
|
575
|
-
using=None,
|
576
563
|
update_fields=None,
|
577
564
|
):
|
578
565
|
"""
|
@@ -584,22 +571,18 @@ class Model(metaclass=ModelBase):
|
|
584
571
|
models and not to do any changes to the values before save. This
|
585
572
|
is used by fixture loading.
|
586
573
|
"""
|
587
|
-
using = using or router.db_for_write(self.__class__, instance=self)
|
588
574
|
assert not (force_insert and (force_update or update_fields))
|
589
575
|
assert update_fields is None or update_fields
|
590
576
|
cls = self.__class__
|
591
577
|
|
592
|
-
with transaction.mark_for_rollback_on_error(
|
578
|
+
with transaction.mark_for_rollback_on_error():
|
593
579
|
self._save_table(
|
594
580
|
raw,
|
595
581
|
cls,
|
596
582
|
force_insert,
|
597
583
|
force_update,
|
598
|
-
using,
|
599
584
|
update_fields,
|
600
585
|
)
|
601
|
-
# Store the database on which the object was saved
|
602
|
-
self._state.db = using
|
603
586
|
# Once saved, this is no longer a to-be-added instance.
|
604
587
|
self._state.adding = False
|
605
588
|
|
@@ -609,7 +592,6 @@ class Model(metaclass=ModelBase):
|
|
609
592
|
cls=None,
|
610
593
|
force_insert=False,
|
611
594
|
force_update=False,
|
612
|
-
using=None,
|
613
595
|
update_fields=None,
|
614
596
|
):
|
615
597
|
"""
|
@@ -645,7 +627,7 @@ class Model(metaclass=ModelBase):
|
|
645
627
|
force_insert = True
|
646
628
|
# If possible, try an UPDATE. If that doesn't update anything, do an INSERT.
|
647
629
|
if pk_set and not force_insert:
|
648
|
-
base_qs = cls._base_manager
|
630
|
+
base_qs = cls._base_manager
|
649
631
|
values = [
|
650
632
|
(
|
651
633
|
f,
|
@@ -656,7 +638,7 @@ class Model(metaclass=ModelBase):
|
|
656
638
|
]
|
657
639
|
forced_update = update_fields or force_update
|
658
640
|
updated = self._do_update(
|
659
|
-
base_qs,
|
641
|
+
base_qs, pk_val, values, update_fields, forced_update
|
660
642
|
)
|
661
643
|
if force_update and not updated:
|
662
644
|
raise DatabaseError("Forced update did not affect any rows.")
|
@@ -668,15 +650,13 @@ class Model(metaclass=ModelBase):
|
|
668
650
|
fields = [f for f in fields if f is not meta.auto_field]
|
669
651
|
|
670
652
|
returning_fields = meta.db_returning_fields
|
671
|
-
results = self._do_insert(
|
672
|
-
cls._base_manager, using, fields, returning_fields, raw
|
673
|
-
)
|
653
|
+
results = self._do_insert(cls._base_manager, fields, returning_fields, raw)
|
674
654
|
if results:
|
675
655
|
for value, field in zip(results[0], returning_fields):
|
676
656
|
setattr(self, field.attname, value)
|
677
657
|
return updated
|
678
658
|
|
679
|
-
def _do_update(self, base_qs,
|
659
|
+
def _do_update(self, base_qs, pk_val, values, update_fields, forced_update):
|
680
660
|
"""
|
681
661
|
Try to update the model. Return True if the model was updated (if an
|
682
662
|
update query was done and a matching row was found in the DB).
|
@@ -691,7 +671,7 @@ class Model(metaclass=ModelBase):
|
|
691
671
|
return update_fields is not None or filtered.exists()
|
692
672
|
return filtered._update(values) > 0
|
693
673
|
|
694
|
-
def _do_insert(self, manager,
|
674
|
+
def _do_insert(self, manager, fields, returning_fields, raw):
|
695
675
|
"""
|
696
676
|
Do an INSERT. If returning_fields is defined then this method should
|
697
677
|
return the newly created data for the model.
|
@@ -700,7 +680,6 @@ class Model(metaclass=ModelBase):
|
|
700
680
|
[self],
|
701
681
|
fields=fields,
|
702
682
|
returning_fields=returning_fields,
|
703
|
-
using=using,
|
704
683
|
raw=raw,
|
705
684
|
)
|
706
685
|
|
@@ -741,14 +720,13 @@ class Model(metaclass=ModelBase):
|
|
741
720
|
):
|
742
721
|
field.delete_cached_value(self)
|
743
722
|
|
744
|
-
def delete(self
|
723
|
+
def delete(self):
|
745
724
|
if self.pk is None:
|
746
725
|
raise ValueError(
|
747
726
|
f"{self._meta.object_name} object can't be deleted because its {self._meta.pk.attname} attribute is set "
|
748
727
|
"to None."
|
749
728
|
)
|
750
|
-
|
751
|
-
collector = Collector(using=using, origin=self)
|
729
|
+
collector = Collector(origin=self)
|
752
730
|
collector.collect([self])
|
753
731
|
return collector.delete()
|
754
732
|
|
@@ -769,8 +747,7 @@ class Model(metaclass=ModelBase):
|
|
769
747
|
q = Q.create([(field.name, param), (f"pk__{op}", self.pk)], connector=Q.AND)
|
770
748
|
q = Q.create([q, (f"{field.name}__{op}", param)], connector=Q.OR)
|
771
749
|
qs = (
|
772
|
-
self.__class__._default_manager.
|
773
|
-
.filter(**kwargs)
|
750
|
+
self.__class__._default_manager.filter(**kwargs)
|
774
751
|
.filter(q)
|
775
752
|
.order_by(f"{order}{field.name}", f"{order}pk")
|
776
753
|
)
|
@@ -858,9 +835,7 @@ class Model(metaclass=ModelBase):
|
|
858
835
|
# TODO: Handle multiple backends with different feature flags.
|
859
836
|
if lookup_value is None or (
|
860
837
|
lookup_value == ""
|
861
|
-
and
|
862
|
-
DEFAULT_DB_ALIAS
|
863
|
-
].features.interprets_empty_strings_as_nulls
|
838
|
+
and db_connection.features.interprets_empty_strings_as_nulls
|
864
839
|
):
|
865
840
|
# no value, skip the lookup
|
866
841
|
continue
|
@@ -941,13 +916,12 @@ class Model(metaclass=ModelBase):
|
|
941
916
|
|
942
917
|
def validate_constraints(self, exclude=None):
|
943
918
|
constraints = self.get_constraints()
|
944
|
-
using = router.db_for_write(self.__class__, instance=self)
|
945
919
|
|
946
920
|
errors = {}
|
947
921
|
for model_class, model_constraints in constraints:
|
948
922
|
for constraint in model_constraints:
|
949
923
|
try:
|
950
|
-
constraint.validate(model_class, self, exclude=exclude
|
924
|
+
constraint.validate(model_class, self, exclude=exclude)
|
951
925
|
except ValidationError as e:
|
952
926
|
if (
|
953
927
|
getattr(e, "code", None) == "unique"
|
@@ -1039,11 +1013,11 @@ class Model(metaclass=ModelBase):
|
|
1039
1013
|
*cls._check_managers(**kwargs),
|
1040
1014
|
]
|
1041
1015
|
|
1042
|
-
|
1016
|
+
database = kwargs.get("database", False)
|
1043
1017
|
errors += [
|
1044
1018
|
*cls._check_fields(**kwargs),
|
1045
1019
|
*cls._check_m2m_through_same_relationship(),
|
1046
|
-
*cls._check_long_column_names(
|
1020
|
+
*cls._check_long_column_names(database),
|
1047
1021
|
]
|
1048
1022
|
clash_errors = (
|
1049
1023
|
*cls._check_id_field(),
|
@@ -1058,35 +1032,31 @@ class Model(metaclass=ModelBase):
|
|
1058
1032
|
if not clash_errors:
|
1059
1033
|
errors.extend(cls._check_column_name_clashes())
|
1060
1034
|
errors += [
|
1061
|
-
*cls._check_indexes(
|
1035
|
+
*cls._check_indexes(database),
|
1062
1036
|
*cls._check_ordering(),
|
1063
|
-
*cls._check_constraints(
|
1064
|
-
*cls._check_db_table_comment(
|
1037
|
+
*cls._check_constraints(database),
|
1038
|
+
*cls._check_db_table_comment(database),
|
1065
1039
|
]
|
1066
1040
|
|
1067
1041
|
return errors
|
1068
1042
|
|
1069
1043
|
@classmethod
|
1070
|
-
def _check_db_table_comment(cls,
|
1071
|
-
if not cls._meta.db_table_comment:
|
1044
|
+
def _check_db_table_comment(cls, database):
|
1045
|
+
if not cls._meta.db_table_comment or not database:
|
1072
1046
|
return []
|
1073
1047
|
errors = []
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
f"{connection.display_name} does not support comments on "
|
1085
|
-
f"tables (db_table_comment).",
|
1086
|
-
obj=cls,
|
1087
|
-
id="models.W046",
|
1088
|
-
)
|
1048
|
+
if not (
|
1049
|
+
db_connection.features.supports_comments
|
1050
|
+
or "supports_comments" in cls._meta.required_db_features
|
1051
|
+
):
|
1052
|
+
errors.append(
|
1053
|
+
preflight.Warning(
|
1054
|
+
f"{db_connection.display_name} does not support comments on "
|
1055
|
+
f"tables (db_table_comment).",
|
1056
|
+
obj=cls,
|
1057
|
+
id="models.W046",
|
1089
1058
|
)
|
1059
|
+
)
|
1090
1060
|
return errors
|
1091
1061
|
|
1092
1062
|
@classmethod
|
@@ -1275,7 +1245,7 @@ class Model(metaclass=ModelBase):
|
|
1275
1245
|
return errors
|
1276
1246
|
|
1277
1247
|
@classmethod
|
1278
|
-
def _check_indexes(cls,
|
1248
|
+
def _check_indexes(cls, database):
|
1279
1249
|
"""Check fields, names, and conditions of indexes."""
|
1280
1250
|
errors = []
|
1281
1251
|
references = set()
|
@@ -1305,55 +1275,63 @@ class Model(metaclass=ModelBase):
|
|
1305
1275
|
references.update(
|
1306
1276
|
ref[0] for ref in cls._get_expr_references(expression)
|
1307
1277
|
)
|
1308
|
-
|
1309
|
-
|
1310
|
-
|
1311
|
-
|
1312
|
-
if not (
|
1313
|
-
connection.features.supports_partial_indexes
|
1278
|
+
if (
|
1279
|
+
database
|
1280
|
+
and not (
|
1281
|
+
db_connection.features.supports_partial_indexes
|
1314
1282
|
or "supports_partial_indexes" in cls._meta.required_db_features
|
1315
|
-
)
|
1316
|
-
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1283
|
+
)
|
1284
|
+
and any(index.condition is not None for index in cls._meta.indexes)
|
1285
|
+
):
|
1286
|
+
errors.append(
|
1287
|
+
preflight.Warning(
|
1288
|
+
f"{db_connection.display_name} does not support indexes with conditions.",
|
1289
|
+
hint=(
|
1290
|
+
"Conditions will be ignored. Silence this warning "
|
1291
|
+
"if you don't care about it."
|
1292
|
+
),
|
1293
|
+
obj=cls,
|
1294
|
+
id="models.W037",
|
1326
1295
|
)
|
1327
|
-
|
1328
|
-
|
1296
|
+
)
|
1297
|
+
if (
|
1298
|
+
database
|
1299
|
+
and not (
|
1300
|
+
db_connection.features.supports_covering_indexes
|
1329
1301
|
or "supports_covering_indexes" in cls._meta.required_db_features
|
1330
|
-
)
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1335
|
-
|
1336
|
-
|
1337
|
-
|
1338
|
-
|
1339
|
-
|
1340
|
-
|
1302
|
+
)
|
1303
|
+
and any(index.include for index in cls._meta.indexes)
|
1304
|
+
):
|
1305
|
+
errors.append(
|
1306
|
+
preflight.Warning(
|
1307
|
+
f"{db_connection.display_name} does not support indexes with non-key columns.",
|
1308
|
+
hint=(
|
1309
|
+
"Non-key columns will be ignored. Silence this "
|
1310
|
+
"warning if you don't care about it."
|
1311
|
+
),
|
1312
|
+
obj=cls,
|
1313
|
+
id="models.W040",
|
1341
1314
|
)
|
1342
|
-
|
1343
|
-
|
1315
|
+
)
|
1316
|
+
if (
|
1317
|
+
database
|
1318
|
+
and not (
|
1319
|
+
db_connection.features.supports_expression_indexes
|
1344
1320
|
or "supports_expression_indexes" in cls._meta.required_db_features
|
1345
|
-
)
|
1346
|
-
|
1347
|
-
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1355
|
-
|
1321
|
+
)
|
1322
|
+
and any(index.contains_expressions for index in cls._meta.indexes)
|
1323
|
+
):
|
1324
|
+
errors.append(
|
1325
|
+
preflight.Warning(
|
1326
|
+
f"{db_connection.display_name} does not support indexes on expressions.",
|
1327
|
+
hint=(
|
1328
|
+
"An index won't be created. Silence this warning "
|
1329
|
+
"if you don't care about it."
|
1330
|
+
),
|
1331
|
+
obj=cls,
|
1332
|
+
id="models.W043",
|
1356
1333
|
)
|
1334
|
+
)
|
1357
1335
|
fields = [
|
1358
1336
|
field for index in cls._meta.indexes for field, _ in index.fields_orders
|
1359
1337
|
]
|
@@ -1508,33 +1486,19 @@ class Model(metaclass=ModelBase):
|
|
1508
1486
|
return errors
|
1509
1487
|
|
1510
1488
|
@classmethod
|
1511
|
-
def _check_long_column_names(cls,
|
1489
|
+
def _check_long_column_names(cls, database):
|
1512
1490
|
"""
|
1513
1491
|
Check that any auto-generated column names are shorter than the limits
|
1514
1492
|
for each database in which the model will be created.
|
1515
1493
|
"""
|
1516
|
-
if not
|
1494
|
+
if not database:
|
1517
1495
|
return []
|
1518
1496
|
errors = []
|
1519
1497
|
allowed_len = None
|
1520
|
-
db_alias = None
|
1521
1498
|
|
1522
|
-
|
1523
|
-
|
1524
|
-
|
1525
|
-
if not router.allow_migrate_model(db, cls):
|
1526
|
-
continue
|
1527
|
-
connection = connections[db]
|
1528
|
-
max_name_length = connection.ops.max_name_length()
|
1529
|
-
if max_name_length is None or connection.features.truncates_names:
|
1530
|
-
continue
|
1531
|
-
else:
|
1532
|
-
if allowed_len is None:
|
1533
|
-
allowed_len = max_name_length
|
1534
|
-
db_alias = db
|
1535
|
-
elif max_name_length < allowed_len:
|
1536
|
-
allowed_len = max_name_length
|
1537
|
-
db_alias = db
|
1499
|
+
max_name_length = db_connection.ops.max_name_length()
|
1500
|
+
if max_name_length is not None and not db_connection.features.truncates_names:
|
1501
|
+
allowed_len = max_name_length
|
1538
1502
|
|
1539
1503
|
if allowed_len is None:
|
1540
1504
|
return errors
|
@@ -1552,7 +1516,7 @@ class Model(metaclass=ModelBase):
|
|
1552
1516
|
errors.append(
|
1553
1517
|
preflight.Error(
|
1554
1518
|
f'Autogenerated column name too long for field "{column_name}". '
|
1555
|
-
f'Maximum length is "{allowed_len}" for database
|
1519
|
+
f'Maximum length is "{allowed_len}" for the database.',
|
1556
1520
|
hint="Set the column name manually using 'db_column'.",
|
1557
1521
|
obj=cls,
|
1558
1522
|
id="models.E018",
|
@@ -1576,7 +1540,7 @@ class Model(metaclass=ModelBase):
|
|
1576
1540
|
errors.append(
|
1577
1541
|
preflight.Error(
|
1578
1542
|
"Autogenerated column name too long for M2M field "
|
1579
|
-
f'"{rel_name}". Maximum length is "{allowed_len}" for database
|
1543
|
+
f'"{rel_name}". Maximum length is "{allowed_len}" for the database.',
|
1580
1544
|
hint=(
|
1581
1545
|
"Use 'through' to create a separate model for "
|
1582
1546
|
"M2M and then set column_name using 'db_column'."
|
@@ -1605,14 +1569,11 @@ class Model(metaclass=ModelBase):
|
|
1605
1569
|
yield from cls._get_expr_references(src_expr)
|
1606
1570
|
|
1607
1571
|
@classmethod
|
1608
|
-
def _check_constraints(cls,
|
1572
|
+
def _check_constraints(cls, database):
|
1609
1573
|
errors = []
|
1610
|
-
|
1611
|
-
if not router.allow_migrate_model(db, cls):
|
1612
|
-
continue
|
1613
|
-
connection = connections[db]
|
1574
|
+
if database:
|
1614
1575
|
if not (
|
1615
|
-
|
1576
|
+
db_connection.features.supports_table_check_constraints
|
1616
1577
|
or "supports_table_check_constraints" in cls._meta.required_db_features
|
1617
1578
|
) and any(
|
1618
1579
|
isinstance(constraint, CheckConstraint)
|
@@ -1620,7 +1581,7 @@ class Model(metaclass=ModelBase):
|
|
1620
1581
|
):
|
1621
1582
|
errors.append(
|
1622
1583
|
preflight.Warning(
|
1623
|
-
f"{
|
1584
|
+
f"{db_connection.display_name} does not support check constraints.",
|
1624
1585
|
hint=(
|
1625
1586
|
"A constraint won't be created. Silence this "
|
1626
1587
|
"warning if you don't care about it."
|
@@ -1630,7 +1591,7 @@ class Model(metaclass=ModelBase):
|
|
1630
1591
|
)
|
1631
1592
|
)
|
1632
1593
|
if not (
|
1633
|
-
|
1594
|
+
db_connection.features.supports_partial_indexes
|
1634
1595
|
or "supports_partial_indexes" in cls._meta.required_db_features
|
1635
1596
|
) and any(
|
1636
1597
|
isinstance(constraint, UniqueConstraint)
|
@@ -1639,7 +1600,7 @@ class Model(metaclass=ModelBase):
|
|
1639
1600
|
):
|
1640
1601
|
errors.append(
|
1641
1602
|
preflight.Warning(
|
1642
|
-
f"{
|
1603
|
+
f"{db_connection.display_name} does not support unique constraints with "
|
1643
1604
|
"conditions.",
|
1644
1605
|
hint=(
|
1645
1606
|
"A constraint won't be created. Silence this "
|
@@ -1650,7 +1611,7 @@ class Model(metaclass=ModelBase):
|
|
1650
1611
|
)
|
1651
1612
|
)
|
1652
1613
|
if not (
|
1653
|
-
|
1614
|
+
db_connection.features.supports_deferrable_unique_constraints
|
1654
1615
|
or "supports_deferrable_unique_constraints"
|
1655
1616
|
in cls._meta.required_db_features
|
1656
1617
|
) and any(
|
@@ -1660,7 +1621,7 @@ class Model(metaclass=ModelBase):
|
|
1660
1621
|
):
|
1661
1622
|
errors.append(
|
1662
1623
|
preflight.Warning(
|
1663
|
-
f"{
|
1624
|
+
f"{db_connection.display_name} does not support deferrable unique constraints.",
|
1664
1625
|
hint=(
|
1665
1626
|
"A constraint won't be created. Silence this "
|
1666
1627
|
"warning if you don't care about it."
|
@@ -1670,7 +1631,7 @@ class Model(metaclass=ModelBase):
|
|
1670
1631
|
)
|
1671
1632
|
)
|
1672
1633
|
if not (
|
1673
|
-
|
1634
|
+
db_connection.features.supports_covering_indexes
|
1674
1635
|
or "supports_covering_indexes" in cls._meta.required_db_features
|
1675
1636
|
) and any(
|
1676
1637
|
isinstance(constraint, UniqueConstraint) and constraint.include
|
@@ -1678,7 +1639,7 @@ class Model(metaclass=ModelBase):
|
|
1678
1639
|
):
|
1679
1640
|
errors.append(
|
1680
1641
|
preflight.Warning(
|
1681
|
-
f"{
|
1642
|
+
f"{db_connection.display_name} does not support unique constraints with non-key "
|
1682
1643
|
"columns.",
|
1683
1644
|
hint=(
|
1684
1645
|
"A constraint won't be created. Silence this "
|
@@ -1689,7 +1650,7 @@ class Model(metaclass=ModelBase):
|
|
1689
1650
|
)
|
1690
1651
|
)
|
1691
1652
|
if not (
|
1692
|
-
|
1653
|
+
db_connection.features.supports_expression_indexes
|
1693
1654
|
or "supports_expression_indexes" in cls._meta.required_db_features
|
1694
1655
|
) and any(
|
1695
1656
|
isinstance(constraint, UniqueConstraint)
|
@@ -1698,7 +1659,7 @@ class Model(metaclass=ModelBase):
|
|
1698
1659
|
):
|
1699
1660
|
errors.append(
|
1700
1661
|
preflight.Warning(
|
1701
|
-
f"{
|
1662
|
+
f"{db_connection.display_name} does not support unique constraints on "
|
1702
1663
|
"expressions.",
|
1703
1664
|
hint=(
|
1704
1665
|
"A constraint won't be created. Silence this "
|
@@ -1708,90 +1669,83 @@ class Model(metaclass=ModelBase):
|
|
1708
1669
|
id="models.W044",
|
1709
1670
|
)
|
1710
1671
|
)
|
1711
|
-
|
1712
|
-
|
1713
|
-
|
1714
|
-
|
1715
|
-
|
1716
|
-
)
|
1672
|
+
fields = set(
|
1673
|
+
chain.from_iterable(
|
1674
|
+
(*constraint.fields, *constraint.include)
|
1675
|
+
for constraint in cls._meta.constraints
|
1676
|
+
if isinstance(constraint, UniqueConstraint)
|
1717
1677
|
)
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
connection.features.supports_partial_indexes
|
1723
|
-
or "supports_partial_indexes"
|
1724
|
-
not in cls._meta.required_db_features
|
1725
|
-
) and isinstance(constraint.condition, Q):
|
1726
|
-
references.update(
|
1727
|
-
cls._get_expr_references(constraint.condition)
|
1728
|
-
)
|
1729
|
-
if (
|
1730
|
-
connection.features.supports_expression_indexes
|
1731
|
-
or "supports_expression_indexes"
|
1732
|
-
not in cls._meta.required_db_features
|
1733
|
-
) and constraint.contains_expressions:
|
1734
|
-
for expression in constraint.expressions:
|
1735
|
-
references.update(cls._get_expr_references(expression))
|
1736
|
-
elif isinstance(constraint, CheckConstraint):
|
1737
|
-
if (
|
1738
|
-
connection.features.supports_table_check_constraints
|
1739
|
-
or "supports_table_check_constraints"
|
1740
|
-
not in cls._meta.required_db_features
|
1741
|
-
):
|
1742
|
-
if isinstance(constraint.check, Q):
|
1743
|
-
references.update(
|
1744
|
-
cls._get_expr_references(constraint.check)
|
1745
|
-
)
|
1746
|
-
if any(
|
1747
|
-
isinstance(expr, RawSQL)
|
1748
|
-
for expr in constraint.check.flatten()
|
1749
|
-
):
|
1750
|
-
errors.append(
|
1751
|
-
preflight.Warning(
|
1752
|
-
f"Check constraint {constraint.name!r} contains "
|
1753
|
-
f"RawSQL() expression and won't be validated "
|
1754
|
-
f"during the model full_clean().",
|
1755
|
-
hint=(
|
1756
|
-
"Silence this warning if you don't care about "
|
1757
|
-
"it."
|
1758
|
-
),
|
1759
|
-
obj=cls,
|
1760
|
-
id="models.W045",
|
1761
|
-
),
|
1762
|
-
)
|
1763
|
-
for field_name, *lookups in references:
|
1764
|
-
# pk is an alias that won't be found by opts.get_field.
|
1765
|
-
if field_name != "pk":
|
1766
|
-
fields.add(field_name)
|
1767
|
-
if not lookups:
|
1768
|
-
# If it has no lookups it cannot result in a JOIN.
|
1769
|
-
continue
|
1770
|
-
try:
|
1771
|
-
if field_name == "pk":
|
1772
|
-
field = cls._meta.pk
|
1773
|
-
else:
|
1774
|
-
field = cls._meta.get_field(field_name)
|
1775
|
-
if not field.is_relation or field.many_to_many or field.one_to_many:
|
1776
|
-
continue
|
1777
|
-
except FieldDoesNotExist:
|
1778
|
-
continue
|
1779
|
-
# JOIN must happen at the first lookup.
|
1780
|
-
first_lookup = lookups[0]
|
1678
|
+
)
|
1679
|
+
references = set()
|
1680
|
+
for constraint in cls._meta.constraints:
|
1681
|
+
if isinstance(constraint, UniqueConstraint):
|
1781
1682
|
if (
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1785
|
-
|
1683
|
+
db_connection.features.supports_partial_indexes
|
1684
|
+
or "supports_partial_indexes" not in cls._meta.required_db_features
|
1685
|
+
) and isinstance(constraint.condition, Q):
|
1686
|
+
references.update(cls._get_expr_references(constraint.condition))
|
1687
|
+
if (
|
1688
|
+
db_connection.features.supports_expression_indexes
|
1689
|
+
or "supports_expression_indexes"
|
1690
|
+
not in cls._meta.required_db_features
|
1691
|
+
) and constraint.contains_expressions:
|
1692
|
+
for expression in constraint.expressions:
|
1693
|
+
references.update(cls._get_expr_references(expression))
|
1694
|
+
elif isinstance(constraint, CheckConstraint):
|
1695
|
+
if (
|
1696
|
+
db_connection.features.supports_table_check_constraints
|
1697
|
+
or "supports_table_check_constraints"
|
1698
|
+
not in cls._meta.required_db_features
|
1786
1699
|
):
|
1787
|
-
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
1700
|
+
if isinstance(constraint.check, Q):
|
1701
|
+
references.update(cls._get_expr_references(constraint.check))
|
1702
|
+
if any(
|
1703
|
+
isinstance(expr, RawSQL) for expr in constraint.check.flatten()
|
1704
|
+
):
|
1705
|
+
errors.append(
|
1706
|
+
preflight.Warning(
|
1707
|
+
f"Check constraint {constraint.name!r} contains "
|
1708
|
+
f"RawSQL() expression and won't be validated "
|
1709
|
+
f"during the model full_clean().",
|
1710
|
+
hint=(
|
1711
|
+
"Silence this warning if you don't care about it."
|
1712
|
+
),
|
1713
|
+
obj=cls,
|
1714
|
+
id="models.W045",
|
1715
|
+
),
|
1792
1716
|
)
|
1717
|
+
for field_name, *lookups in references:
|
1718
|
+
# pk is an alias that won't be found by opts.get_field.
|
1719
|
+
if field_name != "pk":
|
1720
|
+
fields.add(field_name)
|
1721
|
+
if not lookups:
|
1722
|
+
# If it has no lookups it cannot result in a JOIN.
|
1723
|
+
continue
|
1724
|
+
try:
|
1725
|
+
if field_name == "pk":
|
1726
|
+
field = cls._meta.pk
|
1727
|
+
else:
|
1728
|
+
field = cls._meta.get_field(field_name)
|
1729
|
+
if not field.is_relation or field.many_to_many or field.one_to_many:
|
1730
|
+
continue
|
1731
|
+
except FieldDoesNotExist:
|
1732
|
+
continue
|
1733
|
+
# JOIN must happen at the first lookup.
|
1734
|
+
first_lookup = lookups[0]
|
1735
|
+
if (
|
1736
|
+
hasattr(field, "get_transform")
|
1737
|
+
and hasattr(field, "get_lookup")
|
1738
|
+
and field.get_transform(first_lookup) is None
|
1739
|
+
and field.get_lookup(first_lookup) is None
|
1740
|
+
):
|
1741
|
+
errors.append(
|
1742
|
+
preflight.Error(
|
1743
|
+
f"'constraints' refers to the joined field '{LOOKUP_SEP.join([field_name] + lookups)}'.",
|
1744
|
+
obj=cls,
|
1745
|
+
id="models.E041",
|
1793
1746
|
)
|
1794
|
-
|
1747
|
+
)
|
1748
|
+
errors.extend(cls._check_local_fields(fields, "constraints"))
|
1795
1749
|
return errors
|
1796
1750
|
|
1797
1751
|
|