plain.models 0.33.1__py3-none-any.whl → 0.34.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. plain/models/CHANGELOG.md +27 -0
  2. plain/models/README.md +8 -10
  3. plain/models/__init__.py +2 -6
  4. plain/models/backends/base/base.py +10 -18
  5. plain/models/backends/base/creation.py +3 -4
  6. plain/models/backends/base/introspection.py +2 -3
  7. plain/models/backends/base/schema.py +3 -9
  8. plain/models/backends/mysql/validation.py +1 -1
  9. plain/models/backends/postgresql/base.py +15 -23
  10. plain/models/backends/postgresql/schema.py +0 -2
  11. plain/models/backends/sqlite3/base.py +1 -1
  12. plain/models/backends/sqlite3/creation.py +2 -2
  13. plain/models/backends/sqlite3/features.py +1 -1
  14. plain/models/backends/sqlite3/schema.py +1 -1
  15. plain/models/backends/utils.py +2 -6
  16. plain/models/backups/core.py +15 -22
  17. plain/models/base.py +179 -225
  18. plain/models/cli.py +25 -62
  19. plain/models/connections.py +48 -165
  20. plain/models/constraints.py +10 -10
  21. plain/models/db.py +7 -15
  22. plain/models/default_settings.py +13 -20
  23. plain/models/deletion.py +14 -16
  24. plain/models/expressions.py +7 -10
  25. plain/models/fields/__init__.py +56 -76
  26. plain/models/fields/json.py +9 -12
  27. plain/models/fields/related.py +5 -17
  28. plain/models/fields/related_descriptors.py +43 -95
  29. plain/models/forms.py +2 -4
  30. plain/models/indexes.py +2 -3
  31. plain/models/lookups.py +0 -7
  32. plain/models/manager.py +1 -14
  33. plain/models/migrations/executor.py +0 -16
  34. plain/models/migrations/loader.py +1 -1
  35. plain/models/migrations/migration.py +1 -1
  36. plain/models/migrations/operations/base.py +4 -11
  37. plain/models/migrations/operations/fields.py +4 -4
  38. plain/models/migrations/operations/models.py +10 -10
  39. plain/models/migrations/operations/special.py +6 -14
  40. plain/models/migrations/recorder.py +1 -1
  41. plain/models/options.py +4 -7
  42. plain/models/preflight.py +25 -44
  43. plain/models/query.py +47 -102
  44. plain/models/query_utils.py +4 -4
  45. plain/models/sql/compiler.py +7 -11
  46. plain/models/sql/query.py +32 -42
  47. plain/models/sql/subqueries.py +6 -8
  48. plain/models/sql/where.py +1 -1
  49. plain/models/test/pytest.py +21 -32
  50. plain/models/test/utils.py +7 -143
  51. plain/models/transaction.py +66 -164
  52. {plain_models-0.33.1.dist-info → plain_models-0.34.1.dist-info}/METADATA +9 -11
  53. {plain_models-0.33.1.dist-info → plain_models-0.34.1.dist-info}/RECORD +56 -55
  54. {plain_models-0.33.1.dist-info → plain_models-0.34.1.dist-info}/WHEEL +0 -0
  55. {plain_models-0.33.1.dist-info → plain_models-0.34.1.dist-info}/entry_points.txt +0 -0
  56. {plain_models-0.33.1.dist-info → plain_models-0.34.1.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
- connections,
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, db, field_names, values):
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, using=None, fields=None):
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
- hints = {"instance": self}
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 saving to the same database, and this model is deferred, then
549
- # automatically do an "update_fields" save on the loaded fields.
550
- elif not force_insert and deferred_fields and using == self._state.db:
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(using=using):
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.using(using)
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, using, pk_val, values, update_fields, forced_update
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, using, pk_val, values, update_fields, forced_update):
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, using, fields, returning_fields, raw):
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, using=None):
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
- using = using or router.db_for_write(self.__class__, instance=self)
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.using(self._state.db)
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 connections[
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, using=using)
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
- databases = kwargs.get("databases") or []
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(databases),
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(databases),
1035
+ *cls._check_indexes(database),
1062
1036
  *cls._check_ordering(),
1063
- *cls._check_constraints(databases),
1064
- *cls._check_db_table_comment(databases),
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, databases):
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
- for db in databases:
1075
- if not router.allow_migrate_model(db, cls):
1076
- continue
1077
- connection = connections[db]
1078
- if not (
1079
- connection.features.supports_comments
1080
- or "supports_comments" in cls._meta.required_db_features
1081
- ):
1082
- errors.append(
1083
- preflight.Warning(
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, databases):
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
- for db in databases:
1309
- if not router.allow_migrate_model(db, cls):
1310
- continue
1311
- connection = connections[db]
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
- ) and any(index.condition is not None for index in cls._meta.indexes):
1316
- errors.append(
1317
- preflight.Warning(
1318
- f"{connection.display_name} does not support indexes with conditions.",
1319
- hint=(
1320
- "Conditions will be ignored. Silence this warning "
1321
- "if you don't care about it."
1322
- ),
1323
- obj=cls,
1324
- id="models.W037",
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
- if not (
1328
- connection.features.supports_covering_indexes
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
- ) and any(index.include for index in cls._meta.indexes):
1331
- errors.append(
1332
- preflight.Warning(
1333
- f"{connection.display_name} does not support indexes with non-key columns.",
1334
- hint=(
1335
- "Non-key columns will be ignored. Silence this "
1336
- "warning if you don't care about it."
1337
- ),
1338
- obj=cls,
1339
- id="models.W040",
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
- if not (
1343
- connection.features.supports_expression_indexes
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
- ) and any(index.contains_expressions for index in cls._meta.indexes):
1346
- errors.append(
1347
- preflight.Warning(
1348
- f"{connection.display_name} does not support indexes on expressions.",
1349
- hint=(
1350
- "An index won't be created. Silence this warning "
1351
- "if you don't care about it."
1352
- ),
1353
- obj=cls,
1354
- id="models.W043",
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, databases):
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 databases:
1494
+ if not database:
1517
1495
  return []
1518
1496
  errors = []
1519
1497
  allowed_len = None
1520
- db_alias = None
1521
1498
 
1522
- # Find the minimum max allowed length among all specified db_aliases.
1523
- for db in databases:
1524
- # skip databases where the model won't be created
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 "{db_alias}".',
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 "{db_alias}".',
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, databases):
1572
+ def _check_constraints(cls, database):
1609
1573
  errors = []
1610
- for db in databases:
1611
- if not router.allow_migrate_model(db, cls):
1612
- continue
1613
- connection = connections[db]
1574
+ if database:
1614
1575
  if not (
1615
- connection.features.supports_table_check_constraints
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"{connection.display_name} does not support check constraints.",
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
- connection.features.supports_partial_indexes
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"{connection.display_name} does not support unique constraints with "
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
- connection.features.supports_deferrable_unique_constraints
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"{connection.display_name} does not support deferrable unique constraints.",
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
- connection.features.supports_covering_indexes
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"{connection.display_name} does not support unique constraints with non-key "
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
- connection.features.supports_expression_indexes
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"{connection.display_name} does not support unique constraints on "
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
- fields = set(
1712
- chain.from_iterable(
1713
- (*constraint.fields, *constraint.include)
1714
- for constraint in cls._meta.constraints
1715
- if isinstance(constraint, UniqueConstraint)
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
- references = set()
1719
- for constraint in cls._meta.constraints:
1720
- if isinstance(constraint, UniqueConstraint):
1721
- if (
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
- hasattr(field, "get_transform")
1783
- and hasattr(field, "get_lookup")
1784
- and field.get_transform(first_lookup) is None
1785
- and field.get_lookup(first_lookup) is None
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
- errors.append(
1788
- preflight.Error(
1789
- f"'constraints' refers to the joined field '{LOOKUP_SEP.join([field_name] + lookups)}'.",
1790
- obj=cls,
1791
- id="models.E041",
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
- errors.extend(cls._check_local_fields(fields, "constraints"))
1747
+ )
1748
+ errors.extend(cls._check_local_fields(fields, "constraints"))
1795
1749
  return errors
1796
1750
 
1797
1751