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/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 check(cls, **kwargs):
987
+ def preflight(cls):
988
988
  errors = []
989
989
 
990
- database = kwargs.get("database", False)
991
990
  errors += [
992
- *cls._check_fields(**kwargs),
991
+ *cls._check_fields(),
993
992
  *cls._check_m2m_through_same_relationship(),
994
- *cls._check_long_column_names(database),
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(database),
1008
+ *cls._check_indexes(),
1010
1009
  *cls._check_ordering(),
1011
- *cls._check_constraints(database),
1012
- *cls._check_db_table_comment(database),
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, database):
1019
- if not cls._meta.db_table_comment or not database:
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
- preflight.Warning(
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.W046",
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, **kwargs):
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.check(**kwargs))
1041
+ errors.extend(field.preflight(from_model=cls))
1042
1042
  for field in cls._meta.local_many_to_many:
1043
- errors.extend(field.check(from_model=cls, **kwargs))
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
- preflight.Error(
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.E003",
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
- preflight.Error(
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.E004",
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
- preflight.Error(
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.E006",
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
- preflight.Error(
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.E007",
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
- preflight.Error(
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.E023",
1159
+ id="models.model_name_underscore_bounds",
1161
1160
  )
1162
1161
  )
1163
1162
  elif LOOKUP_SEP in model_name:
1164
1163
  errors.append(
1165
- preflight.Error(
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.E024",
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
- preflight.Error(
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.E025",
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
- preflight.Error(
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.E026",
1203
+ id="models.multiple_primary_keys",
1205
1204
  )
1206
1205
  )
1207
1206
  return errors
1208
1207
 
1209
1208
  @classmethod
1210
- def _check_indexes(cls, database):
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
- preflight.Error(
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.E033",
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
- preflight.Error(
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.E034",
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
- database
1242
- and not (
1243
- db_connection.features.supports_partial_indexes
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
- preflight.Warning(
1250
- f"{db_connection.display_name} does not support indexes with conditions.",
1251
- hint=(
1252
- "Conditions will be ignored. Silence this warning "
1253
- "if you don't care about it."
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.W037",
1250
+ id="models.index_conditions_ignored",
1257
1251
  )
1258
1252
  )
1259
- if (
1260
- database
1261
- and not (
1262
- db_connection.features.supports_covering_indexes
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
- preflight.Warning(
1269
- f"{db_connection.display_name} does not support indexes with non-key columns.",
1270
- hint=(
1271
- "Non-key columns will be ignored. Silence this "
1272
- "warning if you don't care about it."
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.W040",
1264
+ id="models.index_non_key_columns_ignored",
1276
1265
  )
1277
1266
  )
1278
- if (
1279
- database
1280
- and not (
1281
- db_connection.features.supports_expression_indexes
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
- preflight.Warning(
1288
- f"{db_connection.display_name} does not support indexes on expressions.",
1289
- hint=(
1290
- "An index won't be created. Silence this warning "
1291
- "if you don't care about it."
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.W043",
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
- preflight.Error(
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.E012",
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
- preflight.Error(
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.E013",
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
- preflight.Error(
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.E016",
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
- preflight.Error(
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.E014",
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
- preflight.Error(
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.E015",
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
- preflight.Error(
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.E015",
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, database):
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
- preflight.Error(
1472
- f'Autogenerated column name too long for field "{column_name}". '
1473
- f'Maximum length is "{allowed_len}" for the database.',
1474
- hint="Set the column name manually using 'db_column'.",
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.E018",
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
- preflight.Error(
1496
- "Autogenerated column name too long for M2M field "
1497
- f'"{rel_name}". Maximum length is "{allowed_len}" for the database.',
1498
- hint=(
1499
- "Use 'through' to create a separate model for "
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.E019",
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, database):
1505
+ def _check_constraints(cls):
1527
1506
  errors = []
1528
- if database:
1529
- if not (
1530
- db_connection.features.supports_table_check_constraints
1531
- or "supports_table_check_constraints" in cls._meta.required_db_features
1532
- ) and any(
1533
- isinstance(constraint, CheckConstraint)
1534
- for constraint in cls._meta.constraints
1535
- ):
1536
- errors.append(
1537
- preflight.Warning(
1538
- f"{db_connection.display_name} does not support check constraints.",
1539
- hint=(
1540
- "A constraint won't be created. Silence this "
1541
- "warning if you don't care about it."
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
- if not (
1548
- db_connection.features.supports_partial_indexes
1549
- or "supports_partial_indexes" in cls._meta.required_db_features
1550
- ) and any(
1551
- isinstance(constraint, UniqueConstraint)
1552
- and constraint.condition is not None
1553
- for constraint in cls._meta.constraints
1554
- ):
1555
- errors.append(
1556
- preflight.Warning(
1557
- f"{db_connection.display_name} does not support unique constraints with "
1558
- "conditions.",
1559
- hint=(
1560
- "A constraint won't be created. Silence this "
1561
- "warning if you don't care about it."
1562
- ),
1563
- obj=cls,
1564
- id="models.W036",
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
- if not (
1568
- db_connection.features.supports_deferrable_unique_constraints
1569
- or "supports_deferrable_unique_constraints"
1570
- in cls._meta.required_db_features
1571
- ) and any(
1572
- isinstance(constraint, UniqueConstraint)
1573
- and constraint.deferrable is not None
1574
- for constraint in cls._meta.constraints
1575
- ):
1576
- errors.append(
1577
- preflight.Warning(
1578
- f"{db_connection.display_name} does not support deferrable unique constraints.",
1579
- hint=(
1580
- "A constraint won't be created. Silence this "
1581
- "warning if you don't care about it."
1582
- ),
1583
- obj=cls,
1584
- id="models.W038",
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
- if not (
1588
- db_connection.features.supports_covering_indexes
1589
- or "supports_covering_indexes" in cls._meta.required_db_features
1590
- ) and any(
1591
- isinstance(constraint, UniqueConstraint) and constraint.include
1592
- for constraint in cls._meta.constraints
1593
- ):
1594
- errors.append(
1595
- preflight.Warning(
1596
- f"{db_connection.display_name} does not support unique constraints with non-key "
1597
- "columns.",
1598
- hint=(
1599
- "A constraint won't be created. Silence this "
1600
- "warning if you don't care about it."
1601
- ),
1602
- obj=cls,
1603
- id="models.W039",
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
- if not (
1607
- db_connection.features.supports_expression_indexes
1608
- or "supports_expression_indexes" in cls._meta.required_db_features
1609
- ) and any(
1610
- isinstance(constraint, UniqueConstraint)
1611
- and constraint.contains_expressions
1612
- for constraint in cls._meta.constraints
1613
- ):
1614
- errors.append(
1615
- preflight.Warning(
1616
- f"{db_connection.display_name} does not support unique constraints on "
1617
- "expressions.",
1618
- hint=(
1619
- "A constraint won't be created. Silence this "
1620
- "warning if you don't care about it."
1621
- ),
1622
- obj=cls,
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
- preflight.Warning(
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
- hint=(
1665
- "Silence this warning if you don't care about it."
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.W045",
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
- preflight.Error(
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.E041",
1666
+ id="models.constraint_refers_to_joined_field",
1695
1667
  )
1696
1668
  )
1697
1669
  errors.extend(cls._check_local_fields(fields, "constraints"))