123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470 |
- from unittest import mock
- from django.core.exceptions import ValidationError
- from django.db import IntegrityError, connection, models
- from django.db.models import F
- from django.db.models.constraints import BaseConstraint, UniqueConstraint
- from django.db.models.functions import Abs, Lower, Sqrt, Upper
- from django.db.transaction import atomic
- from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature
- from django.test.utils import ignore_warnings
- from django.utils.deprecation import RemovedInDjango60Warning
- from .models import (
- ChildModel,
- ChildUniqueConstraintProduct,
- GeneratedFieldStoredProduct,
- GeneratedFieldVirtualProduct,
- JSONFieldModel,
- ModelWithDatabaseDefault,
- Product,
- UniqueConstraintConditionProduct,
- UniqueConstraintDeferrable,
- UniqueConstraintInclude,
- UniqueConstraintProduct,
- )
- def get_constraints(table):
- with connection.cursor() as cursor:
- return connection.introspection.get_constraints(cursor, table)
- class BaseConstraintTests(SimpleTestCase):
- def test_constraint_sql(self):
- c = BaseConstraint(name="name")
- msg = "This method must be implemented by a subclass."
- with self.assertRaisesMessage(NotImplementedError, msg):
- c.constraint_sql(None, None)
- def test_contains_expressions(self):
- c = BaseConstraint(name="name")
- self.assertIs(c.contains_expressions, False)
- def test_create_sql(self):
- c = BaseConstraint(name="name")
- msg = "This method must be implemented by a subclass."
- with self.assertRaisesMessage(NotImplementedError, msg):
- c.create_sql(None, None)
- def test_remove_sql(self):
- c = BaseConstraint(name="name")
- msg = "This method must be implemented by a subclass."
- with self.assertRaisesMessage(NotImplementedError, msg):
- c.remove_sql(None, None)
- def test_validate(self):
- c = BaseConstraint(name="name")
- msg = "This method must be implemented by a subclass."
- with self.assertRaisesMessage(NotImplementedError, msg):
- c.validate(None, None)
- def test_default_violation_error_message(self):
- c = BaseConstraint(name="name")
- self.assertEqual(
- c.get_violation_error_message(), "Constraint “name” is violated."
- )
- def test_custom_violation_error_message(self):
- c = BaseConstraint(
- name="base_name", violation_error_message="custom %(name)s message"
- )
- self.assertEqual(c.get_violation_error_message(), "custom base_name message")
- def test_custom_violation_error_message_clone(self):
- constraint = BaseConstraint(
- name="base_name",
- violation_error_message="custom %(name)s message",
- ).clone()
- self.assertEqual(
- constraint.get_violation_error_message(),
- "custom base_name message",
- )
- def test_custom_violation_code_message(self):
- c = BaseConstraint(name="base_name", violation_error_code="custom_code")
- self.assertEqual(c.violation_error_code, "custom_code")
- def test_deconstruction(self):
- constraint = BaseConstraint(
- name="base_name",
- violation_error_message="custom %(name)s message",
- violation_error_code="custom_code",
- )
- path, args, kwargs = constraint.deconstruct()
- self.assertEqual(path, "django.db.models.BaseConstraint")
- self.assertEqual(args, ())
- self.assertEqual(
- kwargs,
- {
- "name": "base_name",
- "violation_error_message": "custom %(name)s message",
- "violation_error_code": "custom_code",
- },
- )
- def test_deprecation(self):
- msg = "Passing positional arguments to BaseConstraint is deprecated."
- with self.assertRaisesMessage(RemovedInDjango60Warning, msg):
- BaseConstraint("name", "violation error message")
- def test_name_required(self):
- msg = (
- "BaseConstraint.__init__() missing 1 required keyword-only argument: 'name'"
- )
- with self.assertRaisesMessage(TypeError, msg):
- BaseConstraint()
- @ignore_warnings(category=RemovedInDjango60Warning)
- def test_positional_arguments(self):
- c = BaseConstraint("name", "custom %(name)s message")
- self.assertEqual(c.get_violation_error_message(), "custom name message")
- class CheckConstraintTests(TestCase):
- def test_eq(self):
- check1 = models.Q(price__gt=models.F("discounted_price"))
- check2 = models.Q(price__lt=models.F("discounted_price"))
- self.assertEqual(
- models.CheckConstraint(condition=check1, name="price"),
- models.CheckConstraint(condition=check1, name="price"),
- )
- self.assertEqual(
- models.CheckConstraint(condition=check1, name="price"), mock.ANY
- )
- self.assertNotEqual(
- models.CheckConstraint(condition=check1, name="price"),
- models.CheckConstraint(condition=check1, name="price2"),
- )
- self.assertNotEqual(
- models.CheckConstraint(condition=check1, name="price"),
- models.CheckConstraint(condition=check2, name="price"),
- )
- self.assertNotEqual(models.CheckConstraint(condition=check1, name="price"), 1)
- self.assertNotEqual(
- models.CheckConstraint(condition=check1, name="price"),
- models.CheckConstraint(
- condition=check1, name="price", violation_error_message="custom error"
- ),
- )
- self.assertNotEqual(
- models.CheckConstraint(
- condition=check1, name="price", violation_error_message="custom error"
- ),
- models.CheckConstraint(
- condition=check1,
- name="price",
- violation_error_message="other custom error",
- ),
- )
- self.assertEqual(
- models.CheckConstraint(
- condition=check1, name="price", violation_error_message="custom error"
- ),
- models.CheckConstraint(
- condition=check1, name="price", violation_error_message="custom error"
- ),
- )
- self.assertNotEqual(
- models.CheckConstraint(condition=check1, name="price"),
- models.CheckConstraint(
- condition=check1, name="price", violation_error_code="custom_code"
- ),
- )
- self.assertEqual(
- models.CheckConstraint(
- condition=check1, name="price", violation_error_code="custom_code"
- ),
- models.CheckConstraint(
- condition=check1, name="price", violation_error_code="custom_code"
- ),
- )
- def test_repr(self):
- constraint = models.CheckConstraint(
- condition=models.Q(price__gt=models.F("discounted_price")),
- name="price_gt_discounted_price",
- )
- self.assertEqual(
- repr(constraint),
- "<CheckConstraint: condition=(AND: ('price__gt', F(discounted_price))) "
- "name='price_gt_discounted_price'>",
- )
- def test_repr_with_violation_error_message(self):
- constraint = models.CheckConstraint(
- condition=models.Q(price__lt=1),
- name="price_lt_one",
- violation_error_message="More than 1",
- )
- self.assertEqual(
- repr(constraint),
- "<CheckConstraint: condition=(AND: ('price__lt', 1)) name='price_lt_one' "
- "violation_error_message='More than 1'>",
- )
- def test_repr_with_violation_error_code(self):
- constraint = models.CheckConstraint(
- condition=models.Q(price__lt=1),
- name="price_lt_one",
- violation_error_code="more_than_one",
- )
- self.assertEqual(
- repr(constraint),
- "<CheckConstraint: condition=(AND: ('price__lt', 1)) name='price_lt_one' "
- "violation_error_code='more_than_one'>",
- )
- def test_invalid_check_types(self):
- msg = "CheckConstraint.condition must be a Q instance or boolean expression."
- with self.assertRaisesMessage(TypeError, msg):
- models.CheckConstraint(condition=models.F("discounted_price"), name="check")
- def test_deconstruction(self):
- check = models.Q(price__gt=models.F("discounted_price"))
- name = "price_gt_discounted_price"
- constraint = models.CheckConstraint(condition=check, name=name)
- path, args, kwargs = constraint.deconstruct()
- self.assertEqual(path, "django.db.models.CheckConstraint")
- self.assertEqual(args, ())
- self.assertEqual(kwargs, {"condition": check, "name": name})
- @skipUnlessDBFeature("supports_table_check_constraints")
- def test_database_constraint(self):
- Product.objects.create(price=10, discounted_price=5)
- with self.assertRaises(IntegrityError):
- Product.objects.create(price=10, discounted_price=20)
- @skipUnlessDBFeature("supports_table_check_constraints")
- def test_database_constraint_unicode(self):
- Product.objects.create(price=10, discounted_price=5, unit="μg/mL")
- with self.assertRaises(IntegrityError):
- Product.objects.create(price=10, discounted_price=7, unit="l")
- @skipUnlessDBFeature(
- "supports_table_check_constraints", "can_introspect_check_constraints"
- )
- def test_name(self):
- constraints = get_constraints(Product._meta.db_table)
- for expected_name in (
- "price_gt_discounted_price",
- "constraints_product_price_gt_0",
- ):
- with self.subTest(expected_name):
- self.assertIn(expected_name, constraints)
- @skipUnlessDBFeature(
- "supports_table_check_constraints", "can_introspect_check_constraints"
- )
- def test_abstract_name(self):
- constraints = get_constraints(ChildModel._meta.db_table)
- self.assertIn("constraints_childmodel_adult", constraints)
- def test_validate(self):
- check = models.Q(price__gt=models.F("discounted_price"))
- constraint = models.CheckConstraint(condition=check, name="price")
- # Invalid product.
- invalid_product = Product(price=10, discounted_price=42)
- with self.assertRaises(ValidationError):
- constraint.validate(Product, invalid_product)
- with self.assertRaises(ValidationError):
- constraint.validate(Product, invalid_product, exclude={"unit"})
- # Fields used by the check constraint are excluded.
- constraint.validate(Product, invalid_product, exclude={"price"})
- constraint.validate(Product, invalid_product, exclude={"discounted_price"})
- constraint.validate(
- Product,
- invalid_product,
- exclude={"discounted_price", "price"},
- )
- # Valid product.
- constraint.validate(Product, Product(price=10, discounted_price=5))
- def test_validate_custom_error(self):
- check = models.Q(price__gt=models.F("discounted_price"))
- constraint = models.CheckConstraint(
- condition=check,
- name="price",
- violation_error_message="discount is fake",
- violation_error_code="fake_discount",
- )
- # Invalid product.
- invalid_product = Product(price=10, discounted_price=42)
- msg = "discount is fake"
- with self.assertRaisesMessage(ValidationError, msg) as cm:
- constraint.validate(Product, invalid_product)
- self.assertEqual(cm.exception.code, "fake_discount")
- def test_validate_boolean_expressions(self):
- constraint = models.CheckConstraint(
- condition=models.expressions.ExpressionWrapper(
- models.Q(price__gt=500) | models.Q(price__lt=500),
- output_field=models.BooleanField(),
- ),
- name="price_neq_500_wrap",
- )
- msg = f"Constraint “{constraint.name}” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- constraint.validate(Product, Product(price=500, discounted_price=5))
- constraint.validate(Product, Product(price=501, discounted_price=5))
- constraint.validate(Product, Product(price=499, discounted_price=5))
- def test_validate_rawsql_expressions_noop(self):
- constraint = models.CheckConstraint(
- condition=models.expressions.RawSQL(
- "price < %s OR price > %s",
- (500, 500),
- output_field=models.BooleanField(),
- ),
- name="price_neq_500_raw",
- )
- # RawSQL can not be checked and is always considered valid.
- constraint.validate(Product, Product(price=500, discounted_price=5))
- constraint.validate(Product, Product(price=501, discounted_price=5))
- constraint.validate(Product, Product(price=499, discounted_price=5))
- @skipUnlessDBFeature("supports_comparing_boolean_expr")
- def test_validate_nullable_field_with_none(self):
- # Nullable fields should be considered valid on None values.
- constraint = models.CheckConstraint(
- condition=models.Q(price__gte=0),
- name="positive_price",
- )
- constraint.validate(Product, Product())
- @skipIfDBFeature("supports_comparing_boolean_expr")
- def test_validate_nullable_field_with_isnull(self):
- constraint = models.CheckConstraint(
- condition=models.Q(price__gte=0) | models.Q(price__isnull=True),
- name="positive_price",
- )
- constraint.validate(Product, Product())
- @skipUnlessDBFeature("supports_json_field")
- def test_validate_nullable_jsonfield(self):
- is_null_constraint = models.CheckConstraint(
- condition=models.Q(data__isnull=True),
- name="nullable_data",
- )
- is_not_null_constraint = models.CheckConstraint(
- condition=models.Q(data__isnull=False),
- name="nullable_data",
- )
- is_null_constraint.validate(JSONFieldModel, JSONFieldModel(data=None))
- msg = f"Constraint “{is_null_constraint.name}” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- is_null_constraint.validate(JSONFieldModel, JSONFieldModel(data={}))
- msg = f"Constraint “{is_not_null_constraint.name}” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- is_not_null_constraint.validate(JSONFieldModel, JSONFieldModel(data=None))
- is_not_null_constraint.validate(JSONFieldModel, JSONFieldModel(data={}))
- def test_validate_pk_field(self):
- constraint_with_pk = models.CheckConstraint(
- condition=~models.Q(pk=models.F("age")),
- name="pk_not_age_check",
- )
- constraint_with_pk.validate(ChildModel, ChildModel(pk=1, age=2))
- msg = f"Constraint “{constraint_with_pk.name}” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- constraint_with_pk.validate(ChildModel, ChildModel(pk=1, age=1))
- with self.assertRaisesMessage(ValidationError, msg):
- constraint_with_pk.validate(ChildModel, ChildModel(id=1, age=1))
- constraint_with_pk.validate(ChildModel, ChildModel(pk=1, age=1), exclude={"pk"})
- @skipUnlessDBFeature("supports_json_field")
- def test_validate_jsonfield_exact(self):
- data = {"release": "5.0.2", "version": "stable"}
- json_exact_constraint = models.CheckConstraint(
- condition=models.Q(data__version="stable"),
- name="only_stable_version",
- )
- json_exact_constraint.validate(JSONFieldModel, JSONFieldModel(data=data))
- data = {"release": "5.0.2", "version": "not stable"}
- msg = f"Constraint “{json_exact_constraint.name}” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- json_exact_constraint.validate(JSONFieldModel, JSONFieldModel(data=data))
- @skipUnlessDBFeature("supports_stored_generated_columns")
- def test_validate_generated_field_stored(self):
- self.assertGeneratedFieldIsValidated(model=GeneratedFieldStoredProduct)
- @skipUnlessDBFeature("supports_virtual_generated_columns")
- def test_validate_generated_field_virtual(self):
- self.assertGeneratedFieldIsValidated(model=GeneratedFieldVirtualProduct)
- def assertGeneratedFieldIsValidated(self, model):
- constraint = models.CheckConstraint(
- condition=models.Q(rebate__range=(0, 100)), name="bounded_rebate"
- )
- constraint.validate(model, model(price=50, discounted_price=20))
- invalid_product = model(price=1200, discounted_price=500)
- msg = f"Constraint “{constraint.name}” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- constraint.validate(model, invalid_product)
- # Excluding referenced or generated fields should skip validation.
- constraint.validate(model, invalid_product, exclude={"price"})
- constraint.validate(model, invalid_product, exclude={"rebate"})
- def test_check_deprecation(self):
- msg = "CheckConstraint.check is deprecated in favor of `.condition`."
- condition = models.Q(foo="bar")
- with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
- constraint = models.CheckConstraint(name="constraint", check=condition)
- self.assertEqual(ctx.filename, __file__)
- with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
- self.assertIs(constraint.check, condition)
- self.assertEqual(ctx.filename, __file__)
- other_condition = models.Q(something="else")
- with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
- constraint.check = other_condition
- self.assertEqual(ctx.filename, __file__)
- with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
- self.assertIs(constraint.check, other_condition)
- self.assertEqual(ctx.filename, __file__)
- def test_database_default(self):
- models.CheckConstraint(
- condition=models.Q(field_with_db_default="field_with_db_default"),
- name="check_field_with_db_default",
- ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault())
- # Ensure that a check also does not silently pass with either
- # FieldError or DatabaseError when checking with a db_default.
- with self.assertRaises(ValidationError):
- models.CheckConstraint(
- condition=models.Q(
- field_with_db_default="field_with_db_default", field="field"
- ),
- name="check_field_with_db_default_2",
- ).validate(
- ModelWithDatabaseDefault, ModelWithDatabaseDefault(field="not-field")
- )
- with self.assertRaises(ValidationError):
- models.CheckConstraint(
- condition=models.Q(field_with_db_default="field_with_db_default"),
- name="check_field_with_db_default",
- ).validate(
- ModelWithDatabaseDefault,
- ModelWithDatabaseDefault(field_with_db_default="other value"),
- )
- class UniqueConstraintTests(TestCase):
- @classmethod
- def setUpTestData(cls):
- cls.p1 = UniqueConstraintProduct.objects.create(name="p1", color="red")
- cls.p2 = UniqueConstraintProduct.objects.create(name="p2")
- def test_eq(self):
- self.assertEqual(
- models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
- models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
- )
- self.assertEqual(
- models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
- mock.ANY,
- )
- self.assertNotEqual(
- models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
- models.UniqueConstraint(fields=["foo", "bar"], name="unique2"),
- )
- self.assertNotEqual(
- models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
- models.UniqueConstraint(fields=["foo", "baz"], name="unique"),
- )
- self.assertNotEqual(
- models.UniqueConstraint(fields=["foo", "bar"], name="unique"), 1
- )
- self.assertNotEqual(
- models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
- models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- violation_error_message="custom error",
- ),
- )
- self.assertNotEqual(
- models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- violation_error_message="custom error",
- ),
- models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- violation_error_message="other custom error",
- ),
- )
- self.assertEqual(
- models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- violation_error_message="custom error",
- ),
- models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- violation_error_message="custom error",
- ),
- )
- self.assertNotEqual(
- models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- violation_error_code="custom_error",
- ),
- models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- violation_error_code="other_custom_error",
- ),
- )
- self.assertEqual(
- models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- violation_error_code="custom_error",
- ),
- models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- violation_error_code="custom_error",
- ),
- )
- def test_eq_with_condition(self):
- self.assertEqual(
- models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- condition=models.Q(foo=models.F("bar")),
- ),
- models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- condition=models.Q(foo=models.F("bar")),
- ),
- )
- self.assertNotEqual(
- models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- condition=models.Q(foo=models.F("bar")),
- ),
- models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- condition=models.Q(foo=models.F("baz")),
- ),
- )
- def test_eq_with_deferrable(self):
- constraint_1 = models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- deferrable=models.Deferrable.DEFERRED,
- )
- constraint_2 = models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique",
- deferrable=models.Deferrable.IMMEDIATE,
- )
- self.assertEqual(constraint_1, constraint_1)
- self.assertNotEqual(constraint_1, constraint_2)
- def test_eq_with_include(self):
- constraint_1 = models.UniqueConstraint(
- fields=["foo", "bar"],
- name="include",
- include=["baz_1"],
- )
- constraint_2 = models.UniqueConstraint(
- fields=["foo", "bar"],
- name="include",
- include=["baz_2"],
- )
- self.assertEqual(constraint_1, constraint_1)
- self.assertNotEqual(constraint_1, constraint_2)
- def test_eq_with_opclasses(self):
- constraint_1 = models.UniqueConstraint(
- fields=["foo", "bar"],
- name="opclasses",
- opclasses=["text_pattern_ops", "varchar_pattern_ops"],
- )
- constraint_2 = models.UniqueConstraint(
- fields=["foo", "bar"],
- name="opclasses",
- opclasses=["varchar_pattern_ops", "text_pattern_ops"],
- )
- self.assertEqual(constraint_1, constraint_1)
- self.assertNotEqual(constraint_1, constraint_2)
- def test_eq_with_expressions(self):
- constraint = models.UniqueConstraint(
- Lower("title"),
- F("author"),
- name="book_func_uq",
- )
- same_constraint = models.UniqueConstraint(
- Lower("title"),
- "author",
- name="book_func_uq",
- )
- another_constraint = models.UniqueConstraint(
- Lower("title"),
- name="book_func_uq",
- )
- self.assertEqual(constraint, same_constraint)
- self.assertEqual(constraint, mock.ANY)
- self.assertNotEqual(constraint, another_constraint)
- def test_eq_with_nulls_distinct(self):
- constraint_1 = models.UniqueConstraint(
- Lower("title"),
- nulls_distinct=False,
- name="book_func_nulls_distinct_uq",
- )
- constraint_2 = models.UniqueConstraint(
- Lower("title"),
- nulls_distinct=True,
- name="book_func_nulls_distinct_uq",
- )
- constraint_3 = models.UniqueConstraint(
- Lower("title"),
- name="book_func_nulls_distinct_uq",
- )
- self.assertEqual(constraint_1, constraint_1)
- self.assertEqual(constraint_1, mock.ANY)
- self.assertNotEqual(constraint_1, constraint_2)
- self.assertNotEqual(constraint_1, constraint_3)
- self.assertNotEqual(constraint_2, constraint_3)
- def test_repr(self):
- fields = ["foo", "bar"]
- name = "unique_fields"
- constraint = models.UniqueConstraint(fields=fields, name=name)
- self.assertEqual(
- repr(constraint),
- "<UniqueConstraint: fields=('foo', 'bar') name='unique_fields'>",
- )
- def test_repr_with_condition(self):
- constraint = models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique_fields",
- condition=models.Q(foo=models.F("bar")),
- )
- self.assertEqual(
- repr(constraint),
- "<UniqueConstraint: fields=('foo', 'bar') name='unique_fields' "
- "condition=(AND: ('foo', F(bar)))>",
- )
- def test_repr_with_deferrable(self):
- constraint = models.UniqueConstraint(
- fields=["foo", "bar"],
- name="unique_fields",
- deferrable=models.Deferrable.IMMEDIATE,
- )
- self.assertEqual(
- repr(constraint),
- "<UniqueConstraint: fields=('foo', 'bar') name='unique_fields' "
- "deferrable=Deferrable.IMMEDIATE>",
- )
- def test_repr_with_include(self):
- constraint = models.UniqueConstraint(
- fields=["foo", "bar"],
- name="include_fields",
- include=["baz_1", "baz_2"],
- )
- self.assertEqual(
- repr(constraint),
- "<UniqueConstraint: fields=('foo', 'bar') name='include_fields' "
- "include=('baz_1', 'baz_2')>",
- )
- def test_repr_with_opclasses(self):
- constraint = models.UniqueConstraint(
- fields=["foo", "bar"],
- name="opclasses_fields",
- opclasses=["text_pattern_ops", "varchar_pattern_ops"],
- )
- self.assertEqual(
- repr(constraint),
- "<UniqueConstraint: fields=('foo', 'bar') name='opclasses_fields' "
- "opclasses=['text_pattern_ops', 'varchar_pattern_ops']>",
- )
- def test_repr_with_nulls_distinct(self):
- constraint = models.UniqueConstraint(
- fields=["foo", "bar"],
- name="nulls_distinct_fields",
- nulls_distinct=False,
- )
- self.assertEqual(
- repr(constraint),
- "<UniqueConstraint: fields=('foo', 'bar') name='nulls_distinct_fields' "
- "nulls_distinct=False>",
- )
- def test_repr_with_expressions(self):
- constraint = models.UniqueConstraint(
- Lower("title"),
- F("author"),
- name="book_func_uq",
- )
- self.assertEqual(
- repr(constraint),
- "<UniqueConstraint: expressions=(Lower(F(title)), F(author)) "
- "name='book_func_uq'>",
- )
- def test_repr_with_violation_error_message(self):
- constraint = models.UniqueConstraint(
- models.F("baz__lower"),
- name="unique_lower_baz",
- violation_error_message="BAZ",
- )
- self.assertEqual(
- repr(constraint),
- (
- "<UniqueConstraint: expressions=(F(baz__lower),) "
- "name='unique_lower_baz' violation_error_message='BAZ'>"
- ),
- )
- def test_repr_with_violation_error_code(self):
- constraint = models.UniqueConstraint(
- models.F("baz__lower"),
- name="unique_lower_baz",
- violation_error_code="baz",
- )
- self.assertEqual(
- repr(constraint),
- (
- "<UniqueConstraint: expressions=(F(baz__lower),) "
- "name='unique_lower_baz' violation_error_code='baz'>"
- ),
- )
- def test_deconstruction(self):
- fields = ["foo", "bar"]
- name = "unique_fields"
- constraint = models.UniqueConstraint(fields=fields, name=name)
- path, args, kwargs = constraint.deconstruct()
- self.assertEqual(path, "django.db.models.UniqueConstraint")
- self.assertEqual(args, ())
- self.assertEqual(kwargs, {"fields": tuple(fields), "name": name})
- def test_deconstruction_with_condition(self):
- fields = ["foo", "bar"]
- name = "unique_fields"
- condition = models.Q(foo=models.F("bar"))
- constraint = models.UniqueConstraint(
- fields=fields, name=name, condition=condition
- )
- path, args, kwargs = constraint.deconstruct()
- self.assertEqual(path, "django.db.models.UniqueConstraint")
- self.assertEqual(args, ())
- self.assertEqual(
- kwargs, {"fields": tuple(fields), "name": name, "condition": condition}
- )
- def test_deconstruction_with_deferrable(self):
- fields = ["foo"]
- name = "unique_fields"
- constraint = models.UniqueConstraint(
- fields=fields,
- name=name,
- deferrable=models.Deferrable.DEFERRED,
- )
- path, args, kwargs = constraint.deconstruct()
- self.assertEqual(path, "django.db.models.UniqueConstraint")
- self.assertEqual(args, ())
- self.assertEqual(
- kwargs,
- {
- "fields": tuple(fields),
- "name": name,
- "deferrable": models.Deferrable.DEFERRED,
- },
- )
- def test_deconstruction_with_include(self):
- fields = ["foo", "bar"]
- name = "unique_fields"
- include = ["baz_1", "baz_2"]
- constraint = models.UniqueConstraint(fields=fields, name=name, include=include)
- path, args, kwargs = constraint.deconstruct()
- self.assertEqual(path, "django.db.models.UniqueConstraint")
- self.assertEqual(args, ())
- self.assertEqual(
- kwargs,
- {
- "fields": tuple(fields),
- "name": name,
- "include": tuple(include),
- },
- )
- def test_deconstruction_with_opclasses(self):
- fields = ["foo", "bar"]
- name = "unique_fields"
- opclasses = ["varchar_pattern_ops", "text_pattern_ops"]
- constraint = models.UniqueConstraint(
- fields=fields, name=name, opclasses=opclasses
- )
- path, args, kwargs = constraint.deconstruct()
- self.assertEqual(path, "django.db.models.UniqueConstraint")
- self.assertEqual(args, ())
- self.assertEqual(
- kwargs,
- {
- "fields": tuple(fields),
- "name": name,
- "opclasses": opclasses,
- },
- )
- def test_deconstruction_with_nulls_distinct(self):
- fields = ["foo", "bar"]
- name = "unique_fields"
- constraint = models.UniqueConstraint(
- fields=fields, name=name, nulls_distinct=True
- )
- path, args, kwargs = constraint.deconstruct()
- self.assertEqual(path, "django.db.models.UniqueConstraint")
- self.assertEqual(args, ())
- self.assertEqual(
- kwargs,
- {
- "fields": tuple(fields),
- "name": name,
- "nulls_distinct": True,
- },
- )
- def test_deconstruction_with_expressions(self):
- name = "unique_fields"
- constraint = models.UniqueConstraint(Lower("title"), name=name)
- path, args, kwargs = constraint.deconstruct()
- self.assertEqual(path, "django.db.models.UniqueConstraint")
- self.assertEqual(args, (Lower("title"),))
- self.assertEqual(kwargs, {"name": name})
- def test_database_constraint(self):
- with self.assertRaises(IntegrityError):
- UniqueConstraintProduct.objects.create(
- name=self.p1.name, color=self.p1.color
- )
- @skipUnlessDBFeature("supports_partial_indexes")
- def test_database_constraint_with_condition(self):
- UniqueConstraintConditionProduct.objects.create(name="p1")
- UniqueConstraintConditionProduct.objects.create(name="p2")
- with self.assertRaises(IntegrityError):
- UniqueConstraintConditionProduct.objects.create(name="p1")
- def test_model_validation(self):
- msg = "Unique constraint product with this Name and Color already exists."
- with self.assertRaisesMessage(ValidationError, msg):
- UniqueConstraintProduct(
- name=self.p1.name, color=self.p1.color
- ).validate_constraints()
- @skipUnlessDBFeature("supports_partial_indexes")
- def test_model_validation_with_condition(self):
- """
- Partial unique constraints are not ignored by
- Model.validate_constraints().
- """
- obj1 = UniqueConstraintConditionProduct.objects.create(name="p1", color="red")
- obj2 = UniqueConstraintConditionProduct.objects.create(name="p2")
- UniqueConstraintConditionProduct(
- name=obj1.name, color="blue"
- ).validate_constraints()
- msg = "Constraint “name_without_color_uniq” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- UniqueConstraintConditionProduct(name=obj2.name).validate_constraints()
- def test_model_validation_constraint_no_code_error(self):
- class ValidateNoCodeErrorConstraint(UniqueConstraint):
- def validate(self, model, instance, **kwargs):
- raise ValidationError({"name": ValidationError("Already exists.")})
- class NoCodeErrorConstraintModel(models.Model):
- name = models.CharField(max_length=255)
- class Meta:
- constraints = [
- ValidateNoCodeErrorConstraint(
- Lower("name"),
- name="custom_validate_no_code_error",
- )
- ]
- msg = "{'name': ['Already exists.']}"
- with self.assertRaisesMessage(ValidationError, msg):
- NoCodeErrorConstraintModel(name="test").validate_constraints()
- def test_validate(self):
- constraint = UniqueConstraintProduct._meta.constraints[0]
- msg = "Unique constraint product with this Name and Color already exists."
- non_unique_product = UniqueConstraintProduct(
- name=self.p1.name, color=self.p1.color
- )
- with self.assertRaisesMessage(ValidationError, msg) as cm:
- constraint.validate(UniqueConstraintProduct, non_unique_product)
- self.assertEqual(cm.exception.code, "unique_together")
- # Null values are ignored.
- constraint.validate(
- UniqueConstraintProduct,
- UniqueConstraintProduct(name=self.p2.name, color=None),
- )
- # Existing instances have their existing row excluded.
- constraint.validate(UniqueConstraintProduct, self.p1)
- # Unique fields are excluded.
- constraint.validate(
- UniqueConstraintProduct,
- non_unique_product,
- exclude={"name"},
- )
- constraint.validate(
- UniqueConstraintProduct,
- non_unique_product,
- exclude={"color"},
- )
- constraint.validate(
- UniqueConstraintProduct,
- non_unique_product,
- exclude={"name", "color"},
- )
- # Validation on a child instance.
- with self.assertRaisesMessage(ValidationError, msg):
- constraint.validate(
- UniqueConstraintProduct,
- ChildUniqueConstraintProduct(name=self.p1.name, color=self.p1.color),
- )
- def test_validate_unique_custom_code_and_message(self):
- product = UniqueConstraintProduct.objects.create(
- name="test", color="red", age=42
- )
- code = "custom_code"
- message = "Custom message"
- multiple_fields_constraint = models.UniqueConstraint(
- fields=["color", "age"],
- name="color_age_uniq",
- violation_error_code=code,
- violation_error_message=message,
- )
- single_field_constraint = models.UniqueConstraint(
- fields=["color"],
- name="color_uniq",
- violation_error_code=code,
- violation_error_message=message,
- )
- with self.assertRaisesMessage(ValidationError, message) as cm:
- multiple_fields_constraint.validate(
- UniqueConstraintProduct,
- UniqueConstraintProduct(
- name="new-test", color=product.color, age=product.age
- ),
- )
- self.assertEqual(cm.exception.code, code)
- with self.assertRaisesMessage(ValidationError, message) as cm:
- single_field_constraint.validate(
- UniqueConstraintProduct,
- UniqueConstraintProduct(name="new-test", color=product.color),
- )
- self.assertEqual(cm.exception.code, code)
- @skipUnlessDBFeature("supports_table_check_constraints")
- def test_validate_fields_unattached(self):
- Product.objects.create(price=42)
- constraint = models.UniqueConstraint(fields=["price"], name="uniq_prices")
- msg = "Product with this Price already exists."
- with self.assertRaisesMessage(ValidationError, msg):
- constraint.validate(Product, Product(price=42))
- @skipUnlessDBFeature("supports_partial_indexes")
- def test_validate_condition(self):
- p1 = UniqueConstraintConditionProduct.objects.create(name="p1")
- constraint = UniqueConstraintConditionProduct._meta.constraints[0]
- msg = "Constraint “name_without_color_uniq” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- constraint.validate(
- UniqueConstraintConditionProduct,
- UniqueConstraintConditionProduct(name=p1.name, color=None),
- )
- # Values not matching condition are ignored.
- constraint.validate(
- UniqueConstraintConditionProduct,
- UniqueConstraintConditionProduct(name=p1.name, color="anything-but-none"),
- )
- # Existing instances have their existing row excluded.
- constraint.validate(UniqueConstraintConditionProduct, p1)
- # Unique field is excluded.
- constraint.validate(
- UniqueConstraintConditionProduct,
- UniqueConstraintConditionProduct(name=p1.name, color=None),
- exclude={"name"},
- )
- @skipUnlessDBFeature("supports_partial_indexes")
- def test_validate_condition_custom_error(self):
- p1 = UniqueConstraintConditionProduct.objects.create(name="p1")
- constraint = models.UniqueConstraint(
- fields=["name"],
- name="name_without_color_uniq",
- condition=models.Q(color__isnull=True),
- violation_error_code="custom_code",
- violation_error_message="Custom message",
- )
- msg = "Custom message"
- with self.assertRaisesMessage(ValidationError, msg) as cm:
- constraint.validate(
- UniqueConstraintConditionProduct,
- UniqueConstraintConditionProduct(name=p1.name, color=None),
- )
- self.assertEqual(cm.exception.code, "custom_code")
- def test_validate_expression(self):
- constraint = models.UniqueConstraint(Lower("name"), name="name_lower_uniq")
- msg = "Constraint “name_lower_uniq” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- constraint.validate(
- UniqueConstraintProduct,
- UniqueConstraintProduct(name=self.p1.name.upper()),
- )
- constraint.validate(
- UniqueConstraintProduct,
- UniqueConstraintProduct(name="another-name"),
- )
- # Existing instances have their existing row excluded.
- constraint.validate(UniqueConstraintProduct, self.p1)
- # Unique field is excluded.
- constraint.validate(
- UniqueConstraintProduct,
- UniqueConstraintProduct(name=self.p1.name.upper()),
- exclude={"name"},
- )
- def test_validate_ordered_expression(self):
- constraint = models.UniqueConstraint(
- Lower("name").desc(), name="name_lower_uniq_desc"
- )
- msg = "Constraint “name_lower_uniq_desc” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- constraint.validate(
- UniqueConstraintProduct,
- UniqueConstraintProduct(name=self.p1.name.upper()),
- )
- constraint.validate(
- UniqueConstraintProduct,
- UniqueConstraintProduct(name="another-name"),
- )
- # Existing instances have their existing row excluded.
- constraint.validate(UniqueConstraintProduct, self.p1)
- # Unique field is excluded.
- constraint.validate(
- UniqueConstraintProduct,
- UniqueConstraintProduct(name=self.p1.name.upper()),
- exclude={"name"},
- )
- def test_validate_expression_condition(self):
- constraint = models.UniqueConstraint(
- Lower("name"),
- name="name_lower_without_color_uniq",
- condition=models.Q(color__isnull=True),
- )
- non_unique_product = UniqueConstraintProduct(name=self.p2.name.upper())
- msg = "Constraint “name_lower_without_color_uniq” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- constraint.validate(UniqueConstraintProduct, non_unique_product)
- # Values not matching condition are ignored.
- constraint.validate(
- UniqueConstraintProduct,
- UniqueConstraintProduct(name=self.p1.name, color=self.p1.color),
- )
- # Existing instances have their existing row excluded.
- constraint.validate(UniqueConstraintProduct, self.p2)
- # Unique field is excluded.
- constraint.validate(
- UniqueConstraintProduct,
- non_unique_product,
- exclude={"name"},
- )
- # Field from a condition is excluded.
- constraint.validate(
- UniqueConstraintProduct,
- non_unique_product,
- exclude={"color"},
- )
- def test_validate_expression_str(self):
- constraint = models.UniqueConstraint("name", name="name_uniq")
- msg = "Constraint “name_uniq” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- constraint.validate(
- UniqueConstraintProduct,
- UniqueConstraintProduct(name=self.p1.name),
- )
- constraint.validate(
- UniqueConstraintProduct,
- UniqueConstraintProduct(name=self.p1.name),
- exclude={"name"},
- )
- @skipUnlessDBFeature("supports_stored_generated_columns")
- def test_validate_expression_generated_field_stored(self):
- self.assertGeneratedFieldWithExpressionIsValidated(
- model=GeneratedFieldStoredProduct
- )
- @skipUnlessDBFeature("supports_virtual_generated_columns")
- def test_validate_expression_generated_field_virtual(self):
- self.assertGeneratedFieldWithExpressionIsValidated(
- model=GeneratedFieldVirtualProduct
- )
- def assertGeneratedFieldWithExpressionIsValidated(self, model):
- constraint = UniqueConstraint(Sqrt("rebate"), name="unique_rebate_sqrt")
- model.objects.create(price=100, discounted_price=84)
- valid_product = model(price=100, discounted_price=75)
- constraint.validate(model, valid_product)
- invalid_product = model(price=20, discounted_price=4)
- with self.assertRaisesMessage(
- ValidationError, f"Constraint “{constraint.name}” is violated."
- ):
- constraint.validate(model, invalid_product)
- # Excluding referenced or generated fields should skip validation.
- constraint.validate(model, invalid_product, exclude={"rebate"})
- constraint.validate(model, invalid_product, exclude={"price"})
- @skipUnlessDBFeature("supports_stored_generated_columns")
- def test_validate_fields_generated_field_stored(self):
- self.assertGeneratedFieldWithFieldsIsValidated(
- model=GeneratedFieldStoredProduct
- )
- @skipUnlessDBFeature("supports_virtual_generated_columns")
- def test_validate_fields_generated_field_virtual(self):
- self.assertGeneratedFieldWithFieldsIsValidated(
- model=GeneratedFieldVirtualProduct
- )
- def assertGeneratedFieldWithFieldsIsValidated(self, model):
- constraint = models.UniqueConstraint(
- fields=["lower_name"], name="lower_name_unique"
- )
- model.objects.create(name="Box")
- constraint.validate(model, model(name="Case"))
- invalid_product = model(name="BOX")
- msg = str(invalid_product.unique_error_message(model, ["lower_name"]))
- with self.assertRaisesMessage(ValidationError, msg):
- constraint.validate(model, invalid_product)
- # Excluding referenced or generated fields should skip validation.
- constraint.validate(model, invalid_product, exclude={"lower_name"})
- constraint.validate(model, invalid_product, exclude={"name"})
- @skipUnlessDBFeature("supports_stored_generated_columns")
- def test_validate_fields_generated_field_stored_nulls_distinct(self):
- self.assertGeneratedFieldNullsDistinctIsValidated(
- model=GeneratedFieldStoredProduct
- )
- @skipUnlessDBFeature("supports_virtual_generated_columns")
- def test_validate_fields_generated_field_virtual_nulls_distinct(self):
- self.assertGeneratedFieldNullsDistinctIsValidated(
- model=GeneratedFieldVirtualProduct
- )
- def assertGeneratedFieldNullsDistinctIsValidated(self, model):
- constraint = models.UniqueConstraint(
- fields=["lower_name"],
- name="lower_name_unique_nulls_distinct",
- nulls_distinct=False,
- )
- model.objects.create(name=None)
- valid_product = model(name="Box")
- constraint.validate(model, valid_product)
- invalid_product = model(name=None)
- msg = str(invalid_product.unique_error_message(model, ["lower_name"]))
- with self.assertRaisesMessage(ValidationError, msg):
- constraint.validate(model, invalid_product)
- @skipUnlessDBFeature("supports_table_check_constraints")
- def test_validate_nullable_textfield_with_isnull_true(self):
- is_null_constraint = models.UniqueConstraint(
- "price",
- "discounted_price",
- condition=models.Q(unit__isnull=True),
- name="uniq_prices_no_unit",
- )
- is_not_null_constraint = models.UniqueConstraint(
- "price",
- "discounted_price",
- condition=models.Q(unit__isnull=False),
- name="uniq_prices_unit",
- )
- Product.objects.create(price=2, discounted_price=1)
- Product.objects.create(price=4, discounted_price=3, unit="ng/mL")
- msg = "Constraint “uniq_prices_no_unit” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- is_null_constraint.validate(
- Product, Product(price=2, discounted_price=1, unit=None)
- )
- is_null_constraint.validate(
- Product, Product(price=2, discounted_price=1, unit="ng/mL")
- )
- is_null_constraint.validate(Product, Product(price=4, discounted_price=3))
- msg = "Constraint “uniq_prices_unit” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- is_not_null_constraint.validate(
- Product,
- Product(price=4, discounted_price=3, unit="μg/mL"),
- )
- is_not_null_constraint.validate(Product, Product(price=4, discounted_price=3))
- is_not_null_constraint.validate(Product, Product(price=2, discounted_price=1))
- @skipUnlessDBFeature("supports_table_check_constraints")
- def test_validate_nulls_distinct_fields(self):
- Product.objects.create(price=42)
- constraint = models.UniqueConstraint(
- fields=["price"],
- nulls_distinct=False,
- name="uniq_prices_nulls_distinct",
- )
- constraint.validate(Product, Product(price=None))
- Product.objects.create(price=None)
- msg = "Product with this Price already exists."
- with self.assertRaisesMessage(ValidationError, msg):
- constraint.validate(Product, Product(price=None))
- @skipUnlessDBFeature("supports_table_check_constraints")
- def test_validate_nulls_distinct_expressions(self):
- Product.objects.create(price=42)
- constraint = models.UniqueConstraint(
- Abs("price"),
- nulls_distinct=False,
- name="uniq_prices_nulls_distinct",
- )
- constraint.validate(Product, Product(price=None))
- Product.objects.create(price=None)
- msg = f"Constraint “{constraint.name}” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- constraint.validate(Product, Product(price=None))
- def test_name(self):
- constraints = get_constraints(UniqueConstraintProduct._meta.db_table)
- expected_name = "name_color_uniq"
- self.assertIn(expected_name, constraints)
- def test_condition_must_be_q(self):
- with self.assertRaisesMessage(
- ValueError, "UniqueConstraint.condition must be a Q instance."
- ):
- models.UniqueConstraint(name="uniq", fields=["name"], condition="invalid")
- @skipUnlessDBFeature("supports_deferrable_unique_constraints")
- def test_initially_deferred_database_constraint(self):
- obj_1 = UniqueConstraintDeferrable.objects.create(name="p1", shelf="front")
- obj_2 = UniqueConstraintDeferrable.objects.create(name="p2", shelf="back")
- def swap():
- obj_1.name, obj_2.name = obj_2.name, obj_1.name
- obj_1.save()
- obj_2.save()
- swap()
- # Behavior can be changed with SET CONSTRAINTS.
- with self.assertRaises(IntegrityError):
- with atomic(), connection.cursor() as cursor:
- constraint_name = connection.ops.quote_name("name_init_deferred_uniq")
- cursor.execute("SET CONSTRAINTS %s IMMEDIATE" % constraint_name)
- swap()
- @skipUnlessDBFeature("supports_deferrable_unique_constraints")
- def test_initially_immediate_database_constraint(self):
- obj_1 = UniqueConstraintDeferrable.objects.create(name="p1", shelf="front")
- obj_2 = UniqueConstraintDeferrable.objects.create(name="p2", shelf="back")
- obj_1.shelf, obj_2.shelf = obj_2.shelf, obj_1.shelf
- with self.assertRaises(IntegrityError), atomic():
- obj_1.save()
- # Behavior can be changed with SET CONSTRAINTS.
- with connection.cursor() as cursor:
- constraint_name = connection.ops.quote_name("sheld_init_immediate_uniq")
- cursor.execute("SET CONSTRAINTS %s DEFERRED" % constraint_name)
- obj_1.save()
- obj_2.save()
- def test_deferrable_with_condition(self):
- message = "UniqueConstraint with conditions cannot be deferred."
- with self.assertRaisesMessage(ValueError, message):
- models.UniqueConstraint(
- fields=["name"],
- name="name_without_color_unique",
- condition=models.Q(color__isnull=True),
- deferrable=models.Deferrable.DEFERRED,
- )
- def test_deferrable_with_include(self):
- message = "UniqueConstraint with include fields cannot be deferred."
- with self.assertRaisesMessage(ValueError, message):
- models.UniqueConstraint(
- fields=["name"],
- name="name_inc_color_color_unique",
- include=["color"],
- deferrable=models.Deferrable.DEFERRED,
- )
- def test_deferrable_with_opclasses(self):
- message = "UniqueConstraint with opclasses cannot be deferred."
- with self.assertRaisesMessage(ValueError, message):
- models.UniqueConstraint(
- fields=["name"],
- name="name_text_pattern_ops_unique",
- opclasses=["text_pattern_ops"],
- deferrable=models.Deferrable.DEFERRED,
- )
- def test_deferrable_with_expressions(self):
- message = "UniqueConstraint with expressions cannot be deferred."
- with self.assertRaisesMessage(ValueError, message):
- models.UniqueConstraint(
- Lower("name"),
- name="deferred_expression_unique",
- deferrable=models.Deferrable.DEFERRED,
- )
- def test_invalid_defer_argument(self):
- message = "UniqueConstraint.deferrable must be a Deferrable instance."
- with self.assertRaisesMessage(TypeError, message):
- models.UniqueConstraint(
- fields=["name"],
- name="name_invalid",
- deferrable="invalid",
- )
- @skipUnlessDBFeature(
- "supports_table_check_constraints",
- "supports_covering_indexes",
- )
- def test_include_database_constraint(self):
- UniqueConstraintInclude.objects.create(name="p1", color="red")
- with self.assertRaises(IntegrityError):
- UniqueConstraintInclude.objects.create(name="p1", color="blue")
- def test_invalid_include_argument(self):
- msg = "UniqueConstraint.include must be a list or tuple."
- with self.assertRaisesMessage(TypeError, msg):
- models.UniqueConstraint(
- name="uniq_include",
- fields=["field"],
- include="other",
- )
- def test_invalid_opclasses_argument(self):
- msg = "UniqueConstraint.opclasses must be a list or tuple."
- with self.assertRaisesMessage(TypeError, msg):
- models.UniqueConstraint(
- name="uniq_opclasses",
- fields=["field"],
- opclasses="jsonb_path_ops",
- )
- def test_invalid_nulls_distinct_argument(self):
- msg = "UniqueConstraint.nulls_distinct must be a bool."
- with self.assertRaisesMessage(TypeError, msg):
- models.UniqueConstraint(
- name="uniq_opclasses", fields=["field"], nulls_distinct="NULLS DISTINCT"
- )
- def test_opclasses_and_fields_same_length(self):
- msg = (
- "UniqueConstraint.fields and UniqueConstraint.opclasses must have "
- "the same number of elements."
- )
- with self.assertRaisesMessage(ValueError, msg):
- models.UniqueConstraint(
- name="uniq_opclasses",
- fields=["field"],
- opclasses=["foo", "bar"],
- )
- def test_requires_field_or_expression(self):
- msg = (
- "At least one field or expression is required to define a unique "
- "constraint."
- )
- with self.assertRaisesMessage(ValueError, msg):
- models.UniqueConstraint(name="name")
- def test_expressions_and_fields_mutually_exclusive(self):
- msg = "UniqueConstraint.fields and expressions are mutually exclusive."
- with self.assertRaisesMessage(ValueError, msg):
- models.UniqueConstraint(Lower("field_1"), fields=["field_2"], name="name")
- def test_expressions_with_opclasses(self):
- msg = (
- "UniqueConstraint.opclasses cannot be used with expressions. Use "
- "django.contrib.postgres.indexes.OpClass() instead."
- )
- with self.assertRaisesMessage(ValueError, msg):
- models.UniqueConstraint(
- Lower("field"),
- name="test_func_opclass",
- opclasses=["jsonb_path_ops"],
- )
- def test_requires_name(self):
- msg = "A unique constraint must be named."
- with self.assertRaisesMessage(ValueError, msg):
- models.UniqueConstraint(fields=["field"])
- def test_database_default(self):
- models.UniqueConstraint(
- fields=["field_with_db_default"], name="unique_field_with_db_default"
- ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault())
- models.UniqueConstraint(
- Upper("field_with_db_default"),
- name="unique_field_with_db_default_expression",
- ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault())
- ModelWithDatabaseDefault.objects.create()
- msg = (
- "Model with database default with this Field with db default already "
- "exists."
- )
- with self.assertRaisesMessage(ValidationError, msg):
- models.UniqueConstraint(
- fields=["field_with_db_default"], name="unique_field_with_db_default"
- ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault())
- msg = "Constraint “unique_field_with_db_default_expression” is violated."
- with self.assertRaisesMessage(ValidationError, msg):
- models.UniqueConstraint(
- Upper("field_with_db_default"),
- name="unique_field_with_db_default_expression",
- ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault())
|