From 667105877e6723c6985399803a364848891513cc Mon Sep 17 00:00:00 2001 From: Gagaro Date: Mon, 31 Jan 2022 16:04:13 +0100 Subject: Fixed #30581 -- Added support for Meta.constraints validation. Thanks Simon Charette, Keryn Knight, and Mariusz Felisiak for reviews. --- tests/postgres_tests/test_constraints.py | 101 +++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 26 deletions(-) (limited to 'tests/postgres_tests') diff --git a/tests/postgres_tests/test_constraints.py b/tests/postgres_tests/test_constraints.py index 377af41042..b8e53eb4a0 100644 --- a/tests/postgres_tests/test_constraints.py +++ b/tests/postgres_tests/test_constraints.py @@ -2,6 +2,7 @@ import datetime from unittest import mock from django.contrib.postgres.indexes import OpClass +from django.core.exceptions import ValidationError from django.db import IntegrityError, NotSupportedError, connection, transaction from django.db.models import ( CheckConstraint, @@ -612,18 +613,26 @@ class ExclusionConstraintTests(PostgreSQLTestCase): timezone.datetime(2018, 6, 28), timezone.datetime(2018, 6, 29), ] - HotelReservation.objects.create( + reservation = HotelReservation.objects.create( datespan=DateRange(datetimes[0].date(), datetimes[1].date()), start=datetimes[0], end=datetimes[1], room=room102, ) + constraint.validate(HotelReservation, reservation) HotelReservation.objects.create( datespan=DateRange(datetimes[1].date(), datetimes[3].date()), start=datetimes[1], end=datetimes[3], room=room102, ) + HotelReservation.objects.create( + datespan=DateRange(datetimes[3].date(), datetimes[4].date()), + start=datetimes[3], + end=datetimes[4], + room=room102, + cancelled=True, + ) # Overlap dates. with self.assertRaises(IntegrityError), transaction.atomic(): reservation = HotelReservation( @@ -632,33 +641,58 @@ class ExclusionConstraintTests(PostgreSQLTestCase): end=datetimes[2], room=room102, ) + msg = f"Constraint “{constraint.name}” is violated." + with self.assertRaisesMessage(ValidationError, msg): + constraint.validate(HotelReservation, reservation) reservation.save() # Valid range. - HotelReservation.objects.bulk_create( - [ - # Other room. - HotelReservation( - datespan=(datetimes[1].date(), datetimes[2].date()), - start=datetimes[1], - end=datetimes[2], - room=room101, - ), - # Cancelled reservation. - HotelReservation( - datespan=(datetimes[1].date(), datetimes[1].date()), - start=datetimes[1], - end=datetimes[2], - room=room102, - cancelled=True, - ), - # Other adjacent dates. - HotelReservation( - datespan=(datetimes[3].date(), datetimes[4].date()), - start=datetimes[3], - end=datetimes[4], - room=room102, - ), - ] + other_valid_reservations = [ + # Other room. + HotelReservation( + datespan=(datetimes[1].date(), datetimes[2].date()), + start=datetimes[1], + end=datetimes[2], + room=room101, + ), + # Cancelled reservation. + HotelReservation( + datespan=(datetimes[1].date(), datetimes[1].date()), + start=datetimes[1], + end=datetimes[2], + room=room102, + cancelled=True, + ), + # Other adjacent dates. + HotelReservation( + datespan=(datetimes[3].date(), datetimes[4].date()), + start=datetimes[3], + end=datetimes[4], + room=room102, + ), + ] + for reservation in other_valid_reservations: + constraint.validate(HotelReservation, reservation) + HotelReservation.objects.bulk_create(other_valid_reservations) + # Excluded fields. + constraint.validate( + HotelReservation, + HotelReservation( + datespan=(datetimes[1].date(), datetimes[2].date()), + start=datetimes[1], + end=datetimes[2], + room=room102, + ), + exclude={"room"}, + ) + constraint.validate( + HotelReservation, + HotelReservation( + datespan=(datetimes[1].date(), datetimes[2].date()), + start=datetimes[1], + end=datetimes[2], + room=room102, + ), + exclude={"datespan", "start", "end", "room"}, ) @ignore_warnings(category=RemovedInDjango50Warning) @@ -731,6 +765,21 @@ class ExclusionConstraintTests(PostgreSQLTestCase): constraint_name, self.get_constraints(RangesModel._meta.db_table) ) + def test_validate_range_adjacent(self): + constraint = ExclusionConstraint( + name="ints_adjacent", + expressions=[("ints", RangeOperators.ADJACENT_TO)], + violation_error_message="Custom error message.", + ) + range_obj = RangesModel.objects.create(ints=(20, 50)) + constraint.validate(RangesModel, range_obj) + msg = "Custom error message." + with self.assertRaisesMessage(ValidationError, msg): + constraint.validate(RangesModel, RangesModel(ints=(10, 20))) + constraint.validate(RangesModel, RangesModel(ints=(10, 19))) + constraint.validate(RangesModel, RangesModel(ints=(51, 60))) + constraint.validate(RangesModel, RangesModel(ints=(10, 20)), exclude={"ints"}) + def test_expressions_with_params(self): constraint_name = "scene_left_equal" self.assertNotIn(constraint_name, self.get_constraints(Scene._meta.db_table)) -- cgit v1.2.1