plain.models 0.44.0__py3-none-any.whl → 0.46.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 +23 -0
- plain/models/backends/base/validation.py +1 -1
- plain/models/backends/mysql/base.py +0 -1
- plain/models/backends/mysql/validation.py +16 -16
- plain/models/backends/postgresql/base.py +0 -1
- plain/models/backends/postgresql/operations.py +1 -2
- plain/models/backends/sqlite3/base.py +7 -2
- plain/models/backends/sqlite3/features.py +1 -0
- plain/models/base.py +206 -234
- plain/models/config.py +1 -9
- plain/models/fields/__init__.py +117 -227
- plain/models/fields/json.py +22 -22
- plain/models/fields/mixins.py +11 -10
- plain/models/fields/related.py +131 -119
- plain/models/migrations/state.py +1 -1
- plain/models/preflight.py +105 -98
- {plain_models-0.44.0.dist-info → plain_models-0.46.0.dist-info}/METADATA +1 -1
- {plain_models-0.44.0.dist-info → plain_models-0.46.0.dist-info}/RECORD +21 -21
- {plain_models-0.44.0.dist-info → plain_models-0.46.0.dist-info}/WHEEL +0 -0
- {plain_models-0.44.0.dist-info → plain_models-0.46.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.44.0.dist-info → plain_models-0.46.0.dist-info}/licenses/LICENSE +0 -0
plain/models/base.py
CHANGED
@@ -4,7 +4,6 @@ import warnings
|
|
4
4
|
from itertools import chain
|
5
5
|
|
6
6
|
import plain.runtime
|
7
|
-
from plain import preflight
|
8
7
|
from plain.exceptions import (
|
9
8
|
NON_FIELD_ERRORS,
|
10
9
|
FieldDoesNotExist,
|
@@ -27,6 +26,7 @@ from plain.models.fields.reverse_related import ForeignObjectRel
|
|
27
26
|
from plain.models.options import Options
|
28
27
|
from plain.models.query import F, Q, QuerySet
|
29
28
|
from plain.packages import packages_registry
|
29
|
+
from plain.preflight import PreflightResult
|
30
30
|
from plain.utils.encoding import force_str
|
31
31
|
from plain.utils.hashable import make_hashable
|
32
32
|
|
@@ -984,14 +984,13 @@ class Model(metaclass=ModelBase):
|
|
984
984
|
raise ValidationError(errors)
|
985
985
|
|
986
986
|
@classmethod
|
987
|
-
def
|
987
|
+
def preflight(cls):
|
988
988
|
errors = []
|
989
989
|
|
990
|
-
database = kwargs.get("database", False)
|
991
990
|
errors += [
|
992
|
-
*cls._check_fields(
|
991
|
+
*cls._check_fields(),
|
993
992
|
*cls._check_m2m_through_same_relationship(),
|
994
|
-
*cls._check_long_column_names(
|
993
|
+
*cls._check_long_column_names(),
|
995
994
|
]
|
996
995
|
clash_errors = (
|
997
996
|
*cls._check_id_field(),
|
@@ -1006,17 +1005,17 @@ class Model(metaclass=ModelBase):
|
|
1006
1005
|
if not clash_errors:
|
1007
1006
|
errors.extend(cls._check_column_name_clashes())
|
1008
1007
|
errors += [
|
1009
|
-
*cls._check_indexes(
|
1008
|
+
*cls._check_indexes(),
|
1010
1009
|
*cls._check_ordering(),
|
1011
|
-
*cls._check_constraints(
|
1012
|
-
*cls._check_db_table_comment(
|
1010
|
+
*cls._check_constraints(),
|
1011
|
+
*cls._check_db_table_comment(),
|
1013
1012
|
]
|
1014
1013
|
|
1015
1014
|
return errors
|
1016
1015
|
|
1017
1016
|
@classmethod
|
1018
|
-
def _check_db_table_comment(cls
|
1019
|
-
if not cls._meta.db_table_comment
|
1017
|
+
def _check_db_table_comment(cls):
|
1018
|
+
if not cls._meta.db_table_comment:
|
1020
1019
|
return []
|
1021
1020
|
errors = []
|
1022
1021
|
if not (
|
@@ -1024,23 +1023,24 @@ class Model(metaclass=ModelBase):
|
|
1024
1023
|
or "supports_comments" in cls._meta.required_db_features
|
1025
1024
|
):
|
1026
1025
|
errors.append(
|
1027
|
-
|
1028
|
-
f"{db_connection.display_name} does not support comments on "
|
1026
|
+
PreflightResult(
|
1027
|
+
fix=f"{db_connection.display_name} does not support comments on "
|
1029
1028
|
f"tables (db_table_comment).",
|
1030
1029
|
obj=cls,
|
1031
|
-
id="models.
|
1030
|
+
id="models.db_table_comment_unsupported",
|
1031
|
+
warning=True,
|
1032
1032
|
)
|
1033
1033
|
)
|
1034
1034
|
return errors
|
1035
1035
|
|
1036
1036
|
@classmethod
|
1037
|
-
def _check_fields(cls
|
1037
|
+
def _check_fields(cls):
|
1038
1038
|
"""Perform all field checks."""
|
1039
1039
|
errors = []
|
1040
1040
|
for field in cls._meta.local_fields:
|
1041
|
-
errors.extend(field.
|
1041
|
+
errors.extend(field.preflight(from_model=cls))
|
1042
1042
|
for field in cls._meta.local_many_to_many:
|
1043
|
-
errors.extend(field.
|
1043
|
+
errors.extend(field.preflight(from_model=cls))
|
1044
1044
|
return errors
|
1045
1045
|
|
1046
1046
|
@classmethod
|
@@ -1067,11 +1067,11 @@ class Model(metaclass=ModelBase):
|
|
1067
1067
|
)
|
1068
1068
|
if signature in seen_intermediary_signatures:
|
1069
1069
|
errors.append(
|
1070
|
-
|
1071
|
-
"The model has two identical many-to-many relations "
|
1070
|
+
PreflightResult(
|
1071
|
+
fix="The model has two identical many-to-many relations "
|
1072
1072
|
f"through the intermediate model '{f.remote_field.through._meta.label}'.",
|
1073
1073
|
obj=cls,
|
1074
|
-
id="models.
|
1074
|
+
id="models.duplicate_many_to_many_relations",
|
1075
1075
|
)
|
1076
1076
|
)
|
1077
1077
|
else:
|
@@ -1085,10 +1085,10 @@ class Model(metaclass=ModelBase):
|
|
1085
1085
|
f for f in cls._meta.local_fields if f.name == "id" and not f.auto_created
|
1086
1086
|
):
|
1087
1087
|
return [
|
1088
|
-
|
1089
|
-
"'id' is a reserved word that cannot be used as a field name.",
|
1088
|
+
PreflightResult(
|
1089
|
+
fix="'id' is a reserved word that cannot be used as a field name.",
|
1090
1090
|
obj=cls,
|
1091
|
-
id="models.
|
1091
|
+
id="models.reserved_field_name_id",
|
1092
1092
|
)
|
1093
1093
|
]
|
1094
1094
|
return []
|
@@ -1110,11 +1110,11 @@ class Model(metaclass=ModelBase):
|
|
1110
1110
|
)
|
1111
1111
|
if clash and not id_conflict:
|
1112
1112
|
errors.append(
|
1113
|
-
|
1114
|
-
f"The field '{f.name}' clashes with the field '{clash.name}' "
|
1113
|
+
PreflightResult(
|
1114
|
+
fix=f"The field '{f.name}' clashes with the field '{clash.name}' "
|
1115
1115
|
f"from model '{clash.model._meta}'.",
|
1116
1116
|
obj=f,
|
1117
|
-
id="models.
|
1117
|
+
id="models.field_name_clash",
|
1118
1118
|
)
|
1119
1119
|
)
|
1120
1120
|
used_fields[f.name] = f
|
@@ -1134,12 +1134,11 @@ class Model(metaclass=ModelBase):
|
|
1134
1134
|
# Ensure the column name is not already in use.
|
1135
1135
|
if column_name and column_name in used_column_names:
|
1136
1136
|
errors.append(
|
1137
|
-
|
1138
|
-
f"Field '{f.name}' has column name '{column_name}' that is used by "
|
1139
|
-
"another field.",
|
1140
|
-
hint="Specify a 'db_column' for the field.",
|
1137
|
+
PreflightResult(
|
1138
|
+
fix=f"Field '{f.name}' has column name '{column_name}' that is used by "
|
1139
|
+
"another field. Specify a 'db_column' for the field.",
|
1141
1140
|
obj=cls,
|
1142
|
-
id="models.
|
1141
|
+
id="models.db_column_clash",
|
1143
1142
|
)
|
1144
1143
|
)
|
1145
1144
|
else:
|
@@ -1153,20 +1152,20 @@ class Model(metaclass=ModelBase):
|
|
1153
1152
|
model_name = cls.__name__
|
1154
1153
|
if model_name.startswith("_") or model_name.endswith("_"):
|
1155
1154
|
errors.append(
|
1156
|
-
|
1157
|
-
f"The model name '{model_name}' cannot start or end with an underscore "
|
1155
|
+
PreflightResult(
|
1156
|
+
fix=f"The model name '{model_name}' cannot start or end with an underscore "
|
1158
1157
|
"as it collides with the query lookup syntax.",
|
1159
1158
|
obj=cls,
|
1160
|
-
id="models.
|
1159
|
+
id="models.model_name_underscore_bounds",
|
1161
1160
|
)
|
1162
1161
|
)
|
1163
1162
|
elif LOOKUP_SEP in model_name:
|
1164
1163
|
errors.append(
|
1165
|
-
|
1166
|
-
f"The model name '{model_name}' cannot contain double underscores as "
|
1164
|
+
PreflightResult(
|
1165
|
+
fix=f"The model name '{model_name}' cannot contain double underscores as "
|
1167
1166
|
"it collides with the query lookup syntax.",
|
1168
1167
|
obj=cls,
|
1169
|
-
id="models.
|
1168
|
+
id="models.model_name_double_underscore",
|
1170
1169
|
)
|
1171
1170
|
)
|
1172
1171
|
return errors
|
@@ -1183,11 +1182,11 @@ class Model(metaclass=ModelBase):
|
|
1183
1182
|
for accessor in related_field_accessors:
|
1184
1183
|
if accessor in property_names:
|
1185
1184
|
errors.append(
|
1186
|
-
|
1187
|
-
f"The property '{accessor}' clashes with a related field "
|
1185
|
+
PreflightResult(
|
1186
|
+
fix=f"The property '{accessor}' clashes with a related field "
|
1188
1187
|
"accessor.",
|
1189
1188
|
obj=cls,
|
1190
|
-
id="models.
|
1189
|
+
id="models.property_related_field_clash",
|
1191
1190
|
)
|
1192
1191
|
)
|
1193
1192
|
return errors
|
@@ -1197,17 +1196,17 @@ class Model(metaclass=ModelBase):
|
|
1197
1196
|
errors = []
|
1198
1197
|
if sum(1 for f in cls._meta.local_fields if f.primary_key) > 1:
|
1199
1198
|
errors.append(
|
1200
|
-
|
1201
|
-
"The model cannot have more than one field with "
|
1199
|
+
PreflightResult(
|
1200
|
+
fix="The model cannot have more than one field with "
|
1202
1201
|
"'primary_key=True'.",
|
1203
1202
|
obj=cls,
|
1204
|
-
id="models.
|
1203
|
+
id="models.multiple_primary_keys",
|
1205
1204
|
)
|
1206
1205
|
)
|
1207
1206
|
return errors
|
1208
1207
|
|
1209
1208
|
@classmethod
|
1210
|
-
def _check_indexes(cls
|
1209
|
+
def _check_indexes(cls):
|
1211
1210
|
"""Check fields, names, and conditions of indexes."""
|
1212
1211
|
errors = []
|
1213
1212
|
references = set()
|
@@ -1216,20 +1215,20 @@ class Model(metaclass=ModelBase):
|
|
1216
1215
|
# for cross-database compatibility with Oracle.
|
1217
1216
|
if index.name[0] == "_" or index.name[0].isdigit():
|
1218
1217
|
errors.append(
|
1219
|
-
|
1220
|
-
f"The index name '{index.name}' cannot start with an underscore "
|
1218
|
+
PreflightResult(
|
1219
|
+
fix=f"The index name '{index.name}' cannot start with an underscore "
|
1221
1220
|
"or a number.",
|
1222
1221
|
obj=cls,
|
1223
|
-
id="models.
|
1222
|
+
id="models.index_name_invalid_start",
|
1224
1223
|
),
|
1225
1224
|
)
|
1226
1225
|
if len(index.name) > index.max_name_length:
|
1227
1226
|
errors.append(
|
1228
|
-
|
1229
|
-
"The index name '%s' cannot be longer than %d " # noqa: UP031
|
1227
|
+
PreflightResult(
|
1228
|
+
fix="The index name '%s' cannot be longer than %d " # noqa: UP031
|
1230
1229
|
"characters." % (index.name, index.max_name_length),
|
1231
1230
|
obj=cls,
|
1232
|
-
id="models.
|
1231
|
+
id="models.index_name_too_long",
|
1233
1232
|
),
|
1234
1233
|
)
|
1235
1234
|
if index.contains_expressions:
|
@@ -1237,61 +1236,46 @@ class Model(metaclass=ModelBase):
|
|
1237
1236
|
references.update(
|
1238
1237
|
ref[0] for ref in cls._get_expr_references(expression)
|
1239
1238
|
)
|
1240
|
-
if (
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
or "supports_partial_indexes" in cls._meta.required_db_features
|
1245
|
-
)
|
1246
|
-
and any(index.condition is not None for index in cls._meta.indexes)
|
1247
|
-
):
|
1239
|
+
if not (
|
1240
|
+
db_connection.features.supports_partial_indexes
|
1241
|
+
or "supports_partial_indexes" in cls._meta.required_db_features
|
1242
|
+
) and any(index.condition is not None for index in cls._meta.indexes):
|
1248
1243
|
errors.append(
|
1249
|
-
|
1250
|
-
f"{db_connection.display_name} does not support indexes with conditions."
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
),
|
1244
|
+
PreflightResult(
|
1245
|
+
fix=f"{db_connection.display_name} does not support indexes with conditions. "
|
1246
|
+
"Conditions will be ignored. Silence this warning "
|
1247
|
+
"if you don't care about it.",
|
1248
|
+
warning=True,
|
1255
1249
|
obj=cls,
|
1256
|
-
id="models.
|
1250
|
+
id="models.index_conditions_ignored",
|
1257
1251
|
)
|
1258
1252
|
)
|
1259
|
-
if (
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
or "supports_covering_indexes" in cls._meta.required_db_features
|
1264
|
-
)
|
1265
|
-
and any(index.include for index in cls._meta.indexes)
|
1266
|
-
):
|
1253
|
+
if not (
|
1254
|
+
db_connection.features.supports_covering_indexes
|
1255
|
+
or "supports_covering_indexes" in cls._meta.required_db_features
|
1256
|
+
) and any(index.include for index in cls._meta.indexes):
|
1267
1257
|
errors.append(
|
1268
|
-
|
1269
|
-
f"{db_connection.display_name} does not support indexes with non-key columns."
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
),
|
1258
|
+
PreflightResult(
|
1259
|
+
fix=f"{db_connection.display_name} does not support indexes with non-key columns. "
|
1260
|
+
"Non-key columns will be ignored. Silence this "
|
1261
|
+
"warning if you don't care about it.",
|
1262
|
+
warning=True,
|
1274
1263
|
obj=cls,
|
1275
|
-
id="models.
|
1264
|
+
id="models.index_non_key_columns_ignored",
|
1276
1265
|
)
|
1277
1266
|
)
|
1278
|
-
if (
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
or "supports_expression_indexes" in cls._meta.required_db_features
|
1283
|
-
)
|
1284
|
-
and any(index.contains_expressions for index in cls._meta.indexes)
|
1285
|
-
):
|
1267
|
+
if not (
|
1268
|
+
db_connection.features.supports_expression_indexes
|
1269
|
+
or "supports_expression_indexes" in cls._meta.required_db_features
|
1270
|
+
) and any(index.contains_expressions for index in cls._meta.indexes):
|
1286
1271
|
errors.append(
|
1287
|
-
|
1288
|
-
f"{db_connection.display_name} does not support indexes on expressions."
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
),
|
1272
|
+
PreflightResult(
|
1273
|
+
fix=f"{db_connection.display_name} does not support indexes on expressions. "
|
1274
|
+
"An index won't be created. Silence this warning "
|
1275
|
+
"if you don't care about it.",
|
1276
|
+
warning=True,
|
1293
1277
|
obj=cls,
|
1294
|
-
id="models.
|
1278
|
+
id="models.index_on_foreign_key",
|
1295
1279
|
)
|
1296
1280
|
)
|
1297
1281
|
fields = [
|
@@ -1320,30 +1304,29 @@ class Model(metaclass=ModelBase):
|
|
1320
1304
|
field = forward_fields_map[field_name]
|
1321
1305
|
except KeyError:
|
1322
1306
|
errors.append(
|
1323
|
-
|
1324
|
-
f"'{option}' refers to the nonexistent field '{field_name}'.",
|
1307
|
+
PreflightResult(
|
1308
|
+
fix=f"'{option}' refers to the nonexistent field '{field_name}'.",
|
1325
1309
|
obj=cls,
|
1326
|
-
id="models.
|
1310
|
+
id="models.nonexistent_field_reference",
|
1327
1311
|
)
|
1328
1312
|
)
|
1329
1313
|
else:
|
1330
1314
|
if isinstance(field.remote_field, models.ManyToManyRel):
|
1331
1315
|
errors.append(
|
1332
|
-
|
1333
|
-
f"'{option}' refers to a ManyToManyField '{field_name}', but "
|
1316
|
+
PreflightResult(
|
1317
|
+
fix=f"'{option}' refers to a ManyToManyField '{field_name}', but "
|
1334
1318
|
f"ManyToManyFields are not permitted in '{option}'.",
|
1335
1319
|
obj=cls,
|
1336
|
-
id="models.
|
1320
|
+
id="models.m2m_field_in_meta_option",
|
1337
1321
|
)
|
1338
1322
|
)
|
1339
1323
|
elif field not in cls._meta.local_fields:
|
1340
1324
|
errors.append(
|
1341
|
-
|
1342
|
-
f"'{option}' refers to field '{field_name}' which is not local to model "
|
1343
|
-
f"'{cls._meta.object_name}'.",
|
1344
|
-
hint="This issue may be caused by multi-table inheritance.",
|
1325
|
+
PreflightResult(
|
1326
|
+
fix=f"'{option}' refers to field '{field_name}' which is not local to model "
|
1327
|
+
f"'{cls._meta.object_name}'. This issue may be caused by multi-table inheritance.",
|
1345
1328
|
obj=cls,
|
1346
|
-
id="models.
|
1329
|
+
id="models.non_local_field_reference",
|
1347
1330
|
)
|
1348
1331
|
)
|
1349
1332
|
return errors
|
@@ -1360,11 +1343,11 @@ class Model(metaclass=ModelBase):
|
|
1360
1343
|
|
1361
1344
|
if not isinstance(cls._meta.ordering, list | tuple):
|
1362
1345
|
return [
|
1363
|
-
|
1364
|
-
"'ordering' must be a tuple or list (even if you want to order by "
|
1346
|
+
PreflightResult(
|
1347
|
+
fix="'ordering' must be a tuple or list (even if you want to order by "
|
1365
1348
|
"only one field).",
|
1366
1349
|
obj=cls,
|
1367
|
-
id="models.
|
1350
|
+
id="models.ordering_not_tuple_or_list",
|
1368
1351
|
)
|
1369
1352
|
]
|
1370
1353
|
|
@@ -1403,11 +1386,11 @@ class Model(metaclass=ModelBase):
|
|
1403
1386
|
fld.get_transform(part) is None and fld.get_lookup(part) is None
|
1404
1387
|
):
|
1405
1388
|
errors.append(
|
1406
|
-
|
1407
|
-
"'ordering' refers to the nonexistent field, "
|
1389
|
+
PreflightResult(
|
1390
|
+
fix="'ordering' refers to the nonexistent field, "
|
1408
1391
|
f"related field, or lookup '{field}'.",
|
1409
1392
|
obj=cls,
|
1410
|
-
id="models.
|
1393
|
+
id="models.ordering_nonexistent_field",
|
1411
1394
|
)
|
1412
1395
|
)
|
1413
1396
|
|
@@ -1430,23 +1413,21 @@ class Model(metaclass=ModelBase):
|
|
1430
1413
|
|
1431
1414
|
for invalid_field in invalid_fields:
|
1432
1415
|
errors.append(
|
1433
|
-
|
1434
|
-
"'ordering' refers to the nonexistent field, related "
|
1416
|
+
PreflightResult(
|
1417
|
+
fix="'ordering' refers to the nonexistent field, related "
|
1435
1418
|
f"field, or lookup '{invalid_field}'.",
|
1436
1419
|
obj=cls,
|
1437
|
-
id="models.
|
1420
|
+
id="models.ordering_nonexistent_field",
|
1438
1421
|
)
|
1439
1422
|
)
|
1440
1423
|
return errors
|
1441
1424
|
|
1442
1425
|
@classmethod
|
1443
|
-
def _check_long_column_names(cls
|
1426
|
+
def _check_long_column_names(cls):
|
1444
1427
|
"""
|
1445
1428
|
Check that any auto-generated column names are shorter than the limits
|
1446
1429
|
for each database in which the model will be created.
|
1447
1430
|
"""
|
1448
|
-
if not database:
|
1449
|
-
return []
|
1450
1431
|
errors = []
|
1451
1432
|
allowed_len = None
|
1452
1433
|
|
@@ -1468,12 +1449,12 @@ class Model(metaclass=ModelBase):
|
|
1468
1449
|
and len(column_name) > allowed_len
|
1469
1450
|
):
|
1470
1451
|
errors.append(
|
1471
|
-
|
1472
|
-
f'Autogenerated column name too long for field "{column_name}". '
|
1473
|
-
f'Maximum length is "{allowed_len}" for the database.'
|
1474
|
-
|
1452
|
+
PreflightResult(
|
1453
|
+
fix=f'Autogenerated column name too long for field "{column_name}". '
|
1454
|
+
f'Maximum length is "{allowed_len}" for the database. '
|
1455
|
+
"Set the column name manually using 'db_column'.",
|
1475
1456
|
obj=cls,
|
1476
|
-
id="models.
|
1457
|
+
id="models.autogenerated_column_name_too_long",
|
1477
1458
|
)
|
1478
1459
|
)
|
1479
1460
|
|
@@ -1492,15 +1473,13 @@ class Model(metaclass=ModelBase):
|
|
1492
1473
|
and len(rel_name) > allowed_len
|
1493
1474
|
):
|
1494
1475
|
errors.append(
|
1495
|
-
|
1496
|
-
"Autogenerated column name too long for M2M field "
|
1497
|
-
f'"{rel_name}". Maximum length is "{allowed_len}" for the database.'
|
1498
|
-
|
1499
|
-
|
1500
|
-
"M2M and then set column_name using 'db_column'."
|
1501
|
-
),
|
1476
|
+
PreflightResult(
|
1477
|
+
fix="Autogenerated column name too long for M2M field "
|
1478
|
+
f'"{rel_name}". Maximum length is "{allowed_len}" for the database. '
|
1479
|
+
"Use 'through' to create a separate model for "
|
1480
|
+
"M2M and then set column_name using 'db_column'.",
|
1502
1481
|
obj=cls,
|
1503
|
-
id="models.
|
1482
|
+
id="models.m2m_column_name_too_long",
|
1504
1483
|
)
|
1505
1484
|
)
|
1506
1485
|
|
@@ -1523,106 +1502,100 @@ class Model(metaclass=ModelBase):
|
|
1523
1502
|
yield from cls._get_expr_references(src_expr)
|
1524
1503
|
|
1525
1504
|
@classmethod
|
1526
|
-
def _check_constraints(cls
|
1505
|
+
def _check_constraints(cls):
|
1527
1506
|
errors = []
|
1528
|
-
if
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
)
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
obj=cls,
|
1544
|
-
id="models.W027",
|
1545
|
-
)
|
1507
|
+
if not (
|
1508
|
+
db_connection.features.supports_table_check_constraints
|
1509
|
+
or "supports_table_check_constraints" in cls._meta.required_db_features
|
1510
|
+
) and any(
|
1511
|
+
isinstance(constraint, CheckConstraint)
|
1512
|
+
for constraint in cls._meta.constraints
|
1513
|
+
):
|
1514
|
+
errors.append(
|
1515
|
+
PreflightResult(
|
1516
|
+
fix=f"{db_connection.display_name} does not support check constraints. "
|
1517
|
+
"A constraint won't be created. Silence this "
|
1518
|
+
"warning if you don't care about it.",
|
1519
|
+
obj=cls,
|
1520
|
+
id="models.constraint_on_non_db_field",
|
1521
|
+
warning=True,
|
1546
1522
|
)
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1555
|
-
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1564
|
-
|
1565
|
-
)
|
1523
|
+
)
|
1524
|
+
|
1525
|
+
if not (
|
1526
|
+
db_connection.features.supports_partial_indexes
|
1527
|
+
or "supports_partial_indexes" in cls._meta.required_db_features
|
1528
|
+
) and any(
|
1529
|
+
isinstance(constraint, UniqueConstraint)
|
1530
|
+
and constraint.condition is not None
|
1531
|
+
for constraint in cls._meta.constraints
|
1532
|
+
):
|
1533
|
+
errors.append(
|
1534
|
+
PreflightResult(
|
1535
|
+
fix=f"{db_connection.display_name} does not support unique constraints with "
|
1536
|
+
"conditions. A constraint won't be created. Silence this "
|
1537
|
+
"warning if you don't care about it.",
|
1538
|
+
obj=cls,
|
1539
|
+
id="models.constraint_on_virtual_field",
|
1540
|
+
warning=True,
|
1566
1541
|
)
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1585
|
-
|
1542
|
+
)
|
1543
|
+
|
1544
|
+
if not (
|
1545
|
+
db_connection.features.supports_deferrable_unique_constraints
|
1546
|
+
or "supports_deferrable_unique_constraints"
|
1547
|
+
in cls._meta.required_db_features
|
1548
|
+
) and any(
|
1549
|
+
isinstance(constraint, UniqueConstraint)
|
1550
|
+
and constraint.deferrable is not None
|
1551
|
+
for constraint in cls._meta.constraints
|
1552
|
+
):
|
1553
|
+
errors.append(
|
1554
|
+
PreflightResult(
|
1555
|
+
fix=f"{db_connection.display_name} does not support deferrable unique constraints. "
|
1556
|
+
"A constraint won't be created. Silence this "
|
1557
|
+
"warning if you don't care about it.",
|
1558
|
+
obj=cls,
|
1559
|
+
id="models.constraint_on_foreign_key",
|
1560
|
+
warning=True,
|
1586
1561
|
)
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
)
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
1604
|
-
)
|
1562
|
+
)
|
1563
|
+
|
1564
|
+
if not (
|
1565
|
+
db_connection.features.supports_covering_indexes
|
1566
|
+
or "supports_covering_indexes" in cls._meta.required_db_features
|
1567
|
+
) and any(
|
1568
|
+
isinstance(constraint, UniqueConstraint) and constraint.include
|
1569
|
+
for constraint in cls._meta.constraints
|
1570
|
+
):
|
1571
|
+
errors.append(
|
1572
|
+
PreflightResult(
|
1573
|
+
fix=f"{db_connection.display_name} does not support unique constraints with non-key "
|
1574
|
+
"columns. A constraint won't be created. Silence this "
|
1575
|
+
"warning if you don't care about it.",
|
1576
|
+
obj=cls,
|
1577
|
+
id="models.constraint_on_m2m_field",
|
1578
|
+
warning=True,
|
1605
1579
|
)
|
1606
|
-
|
1607
|
-
|
1608
|
-
|
1609
|
-
|
1610
|
-
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
1618
|
-
|
1619
|
-
|
1620
|
-
|
1621
|
-
|
1622
|
-
|
1623
|
-
id="models.W044",
|
1624
|
-
)
|
1580
|
+
)
|
1581
|
+
|
1582
|
+
if not (
|
1583
|
+
db_connection.features.supports_expression_indexes
|
1584
|
+
or "supports_expression_indexes" in cls._meta.required_db_features
|
1585
|
+
) and any(
|
1586
|
+
isinstance(constraint, UniqueConstraint) and constraint.contains_expressions
|
1587
|
+
for constraint in cls._meta.constraints
|
1588
|
+
):
|
1589
|
+
errors.append(
|
1590
|
+
PreflightResult(
|
1591
|
+
fix=f"{db_connection.display_name} does not support unique constraints on "
|
1592
|
+
"expressions. A constraint won't be created. Silence this "
|
1593
|
+
"warning if you don't care about it.",
|
1594
|
+
obj=cls,
|
1595
|
+
id="models.constraint_on_self_referencing_fk",
|
1596
|
+
warning=True,
|
1625
1597
|
)
|
1598
|
+
)
|
1626
1599
|
fields = set(
|
1627
1600
|
chain.from_iterable(
|
1628
1601
|
(*constraint.fields, *constraint.include)
|
@@ -1657,15 +1630,14 @@ class Model(metaclass=ModelBase):
|
|
1657
1630
|
isinstance(expr, RawSQL) for expr in constraint.check.flatten()
|
1658
1631
|
):
|
1659
1632
|
errors.append(
|
1660
|
-
|
1661
|
-
f"Check constraint {constraint.name!r} contains "
|
1633
|
+
PreflightResult(
|
1634
|
+
fix=f"Check constraint {constraint.name!r} contains "
|
1662
1635
|
f"RawSQL() expression and won't be validated "
|
1663
|
-
f"during the model full_clean()."
|
1664
|
-
|
1665
|
-
|
1666
|
-
),
|
1636
|
+
f"during the model full_clean(). "
|
1637
|
+
"Silence this warning if you don't care about it.",
|
1638
|
+
warning=True,
|
1667
1639
|
obj=cls,
|
1668
|
-
id="models.
|
1640
|
+
id="models.constraint_name_collision_autogenerated",
|
1669
1641
|
),
|
1670
1642
|
)
|
1671
1643
|
for field_name, *lookups in references:
|
@@ -1688,10 +1660,10 @@ class Model(metaclass=ModelBase):
|
|
1688
1660
|
and field.get_lookup(first_lookup) is None
|
1689
1661
|
):
|
1690
1662
|
errors.append(
|
1691
|
-
|
1692
|
-
f"'constraints' refers to the joined field '{LOOKUP_SEP.join([field_name] + lookups)}'.",
|
1663
|
+
PreflightResult(
|
1664
|
+
fix=f"'constraints' refers to the joined field '{LOOKUP_SEP.join([field_name] + lookups)}'.",
|
1693
1665
|
obj=cls,
|
1694
|
-
id="models.
|
1666
|
+
id="models.constraint_refers_to_joined_field",
|
1695
1667
|
)
|
1696
1668
|
)
|
1697
1669
|
errors.extend(cls._check_local_fields(fields, "constraints"))
|