diff options
| author | Clifford Gama <cliffygamy@gmail.com> | 2025-03-08 15:46:58 +0200 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2025-03-12 09:16:15 +0100 |
| commit | 0ebea6e5c07485a36862e9b6e2be18d1694ad2c5 (patch) | |
| tree | 6ece342336bea4fbdd74cd29a51d9a071901fc82 /tests | |
| parent | 5183f7c287a9a5d61ca1103b55166cda52d9c647 (diff) | |
Fixed #35676 -- Made BaseModelForm validate constraints that reference an InlineForeignKeyField.
Co-authored-by: Simon Charette <charette.s@gmail.com>
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/inline_formsets/models.py | 5 | ||||
| -rw-r--r-- | tests/inline_formsets/tests.py | 35 | ||||
| -rw-r--r-- | tests/model_forms/models.py | 21 | ||||
| -rw-r--r-- | tests/model_forms/tests.py | 38 |
4 files changed, 99 insertions, 0 deletions
diff --git a/tests/inline_formsets/models.py b/tests/inline_formsets/models.py index f4c06e8fac..a090387c42 100644 --- a/tests/inline_formsets/models.py +++ b/tests/inline_formsets/models.py @@ -15,6 +15,11 @@ class Child(models.Model): school = models.ForeignKey(School, models.CASCADE) name = models.CharField(max_length=100) + class Meta: + constraints = [ + models.UniqueConstraint("mother", "father", name="unique_parents"), + ] + class Poet(models.Model): name = models.CharField(max_length=100) diff --git a/tests/inline_formsets/tests.py b/tests/inline_formsets/tests.py index 1ae9b3f760..0fe9766dc6 100644 --- a/tests/inline_formsets/tests.py +++ b/tests/inline_formsets/tests.py @@ -215,3 +215,38 @@ class InlineFormsetFactoryTest(TestCase): ) formset = PoemFormSet(None, instance=poet) formset.forms # Trigger form instantiation to run the assert above. + + +class InlineFormsetConstraintsValidationTests(TestCase): + def test_constraint_refs_inline_foreignkey_field(self): + """ + Constraints that reference an InlineForeignKeyField should not be + skipped from validation (#35676). + """ + ChildFormSet = inlineformset_factory( + Parent, + Child, + fk_name="mother", + fields="__all__", + extra=1, + ) + father = Parent.objects.create(name="James") + school = School.objects.create(name="Hogwarts") + mother = Parent.objects.create(name="Lily") + Child.objects.create(name="Harry", father=father, mother=mother, school=school) + data = { + "mothers_children-TOTAL_FORMS": "1", + "mothers_children-INITIAL_FORMS": "0", + "mothers_children-MIN_NUM_FORMS": "0", + "mothers_children-MAX_NUM_FORMS": "1000", + "mothers_children-0-id": "", + "mothers_children-0-father": str(father.pk), + "mothers_children-0-school": str(school.pk), + "mothers_children-0-name": "Mary", + } + formset = ChildFormSet(instance=mother, data=data, queryset=None) + self.assertFalse(formset.is_valid()) + self.assertEqual( + formset.errors, + [{"__all__": ["Constraint “unique_parents” is violated."]}], + ) diff --git a/tests/model_forms/models.py b/tests/model_forms/models.py index f9441a4c77..4111fafd69 100644 --- a/tests/model_forms/models.py +++ b/tests/model_forms/models.py @@ -521,3 +521,24 @@ class Dice(models.Model): through=NumbersToDice, limit_choices_to=models.Q(value__gte=1), ) + + +class ConstraintsModel(models.Model): + name = models.CharField(max_length=100) + category = models.CharField(max_length=50, default="uncategorized") + price = models.DecimalField(max_digits=10, decimal_places=2, default=0) + + class Meta: + constraints = [ + models.UniqueConstraint( + "name", + "category", + name="unique_name_category", + violation_error_message="This product already exists.", + ), + models.CheckConstraint( + condition=models.Q(price__gt=0), + name="price_gte_zero", + violation_error_message="Price must be greater than zero.", + ), + ] diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index a432f6ce43..837466f07f 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -40,6 +40,7 @@ from .models import ( Character, Colour, ColourfulItem, + ConstraintsModel, CustomErrorMessage, CustomFF, CustomFieldForExclusionModel, @@ -3718,3 +3719,40 @@ class ModelToDictTests(TestCase): # If data were a QuerySet, it would be reevaluated here and give "red" # instead of the original value. self.assertEqual(data, [blue]) + + +class ConstraintValidationTests(TestCase): + def test_unique_constraint_refs_excluded_field(self): + obj = ConstraintsModel.objects.create(name="product", price="1.00") + data = { + "id": "", + "name": obj.name, + "price": "1337.00", + "category": obj.category, + } + ConstraintsModelForm = modelform_factory(ConstraintsModel, fields="__all__") + ExcludeCategoryForm = modelform_factory(ConstraintsModel, exclude=["category"]) + full_form = ConstraintsModelForm(data) + exclude_category_form = ExcludeCategoryForm(data) + self.assertTrue(exclude_category_form.is_valid()) + self.assertFalse(full_form.is_valid()) + self.assertEqual( + full_form.errors, {"__all__": ["This product already exists."]} + ) + + def test_check_constraint_refs_excluded_field(self): + data = { + "id": "", + "name": "priceless", + "price": "0.00", + "category": "category 1", + } + ConstraintsModelForm = modelform_factory(ConstraintsModel, fields="__all__") + ExcludePriceForm = modelform_factory(ConstraintsModel, exclude=["price"]) + full_form = ConstraintsModelForm(data) + exclude_price_form = ExcludePriceForm(data) + self.assertTrue(exclude_price_form.is_valid()) + self.assertFalse(full_form.is_valid()) + self.assertEqual( + full_form.errors, {"__all__": ["Price must be greater than zero."]} + ) |
