diff --git a/django/contrib/postgres/fields/ranges.py b/django/contrib/postgres/fields/ranges.py index f0a2a58936..6230b2f887 100644 --- a/django/contrib/postgres/fields/ranges.py +++ b/django/contrib/postgres/fields/ranges.py @@ -78,6 +78,9 @@ class RangeField(models.Field): def _choices_is_value(cls, value): return isinstance(value, (list, tuple)) or super()._choices_is_value(value) + def get_placeholder(self, value, compiler, connection): + return "%s::{}".format(self.db_type(connection)) + def get_prep_value(self, value): if value is None: return None diff --git a/docs/releases/4.1.1.txt b/docs/releases/4.1.1.txt index dbac1ff926..55c0b7ed08 100644 --- a/docs/releases/4.1.1.txt +++ b/docs/releases/4.1.1.txt @@ -32,3 +32,6 @@ Bugfixes * Fixed a bug in Django 4.1 that caused a crash of model validation on ``UniqueConstraint()`` with field names in ``expressions`` (:ticket:`33902`). + +* Fixed a bug in Django 4.1 that caused an incorrect validation of + ``CheckConstraint()`` with range fields on PostgreSQL (:ticket:`33905`). diff --git a/tests/postgres_tests/test_constraints.py b/tests/postgres_tests/test_constraints.py index b3822e5805..a6f7b5be85 100644 --- a/tests/postgres_tests/test_constraints.py +++ b/tests/postgres_tests/test_constraints.py @@ -123,6 +123,41 @@ class SchemaTests(PostgreSQLTestCase): timestamps_inner=(datetime_1, datetime_2), ) + def test_check_constraint_range_contains(self): + constraint = CheckConstraint( + check=Q(ints__contains=(1, 5)), + name="ints_contains", + ) + msg = f"Constraint “{constraint.name}” is violated." + with self.assertRaisesMessage(ValidationError, msg): + constraint.validate(RangesModel, RangesModel(ints=(6, 10))) + + def test_check_constraint_range_lower_upper(self): + constraint = CheckConstraint( + check=Q(ints__startswith__gte=0) & Q(ints__endswith__lte=99), + name="ints_range_lower_upper", + ) + msg = f"Constraint “{constraint.name}” is violated." + with self.assertRaisesMessage(ValidationError, msg): + constraint.validate(RangesModel, RangesModel(ints=(-1, 20))) + with self.assertRaisesMessage(ValidationError, msg): + constraint.validate(RangesModel, RangesModel(ints=(0, 100))) + constraint.validate(RangesModel, RangesModel(ints=(0, 99))) + + def test_check_constraint_range_lower_with_nulls(self): + constraint = CheckConstraint( + check=Q(ints__isnull=True) | Q(ints__startswith__gte=0), + name="ints_optional_positive_range", + ) + constraint.validate(RangesModel, RangesModel()) + constraint = CheckConstraint( + check=Q(ints__startswith__gte=0), + name="ints_positive_range", + ) + msg = f"Constraint “{constraint.name}” is violated." + with self.assertRaisesMessage(ValidationError, msg): + constraint.validate(RangesModel, RangesModel()) + def test_opclass(self): constraint = UniqueConstraint( name="test_opclass",