From 40261162658dd27e149f7c237af8c3c793e08ff0 Mon Sep 17 00:00:00 2001 From: Sage Abdullah Date: Mon, 25 Nov 2024 13:02:49 +0000 Subject: [PATCH] Add support for UniqueConstraint for uniqueness validation --- modelcluster/forms.py | 4 +++- tests/migrations/0013_add_log_category.py | 27 +++++++++++++++++++++++ tests/models.py | 16 ++++++++++++++ tests/tests/test_cluster_form.py | 24 +++++++++++++++++++- 4 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 tests/migrations/0013_add_log_category.py diff --git a/modelcluster/forms.py b/modelcluster/forms.py index 9dd2772..29e2e5d 100644 --- a/modelcluster/forms.py +++ b/modelcluster/forms.py @@ -134,7 +134,9 @@ def validate_unique(self): forms_to_delete = self.deleted_forms valid_forms = [form for form in self.forms if form.is_valid() and form not in forms_to_delete] for form in valid_forms: - unique_checks, date_checks = form.instance._get_unique_checks() + unique_checks, date_checks = form.instance._get_unique_checks( + include_meta_constraints=True + ) all_unique_checks.update(unique_checks) all_date_checks.update(date_checks) diff --git a/tests/migrations/0013_add_log_category.py b/tests/migrations/0013_add_log_category.py new file mode 100644 index 0000000..3f46aee --- /dev/null +++ b/tests/migrations/0013_add_log_category.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.16 on 2024-11-25 12:55 + +from django.db import migrations, models +import django.db.models.deletion +import modelcluster.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('tests', '0012_add_record_label'), + ] + + operations = [ + migrations.CreateModel( + name='LogCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=32)), + ('log', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='categories', to='tests.log')), + ], + ), + migrations.AddConstraint( + model_name='logcategory', + constraint=models.UniqueConstraint(fields=('log', 'name'), name='unique_log_category'), + ), + ] diff --git a/tests/models.py b/tests/models.py index aabd582..83b915b 100644 --- a/tests/models.py +++ b/tests/models.py @@ -157,6 +157,22 @@ def __str__(self): return "[%s] %s" % (self.time.isoformat(), self.data) +class LogCategory(models.Model): + log = ParentalKey(Log, related_name="categories", on_delete=models.CASCADE) + name = models.CharField(max_length=32) + + def __str__(self): + return self.name + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["log", "name"], + name="unique_log_category", + ) + ] + + class Document(ClusterableModel): title = models.CharField(max_length=255) file = models.FileField(upload_to='documents') diff --git a/tests/tests/test_cluster_form.py b/tests/tests/test_cluster_form.py index 86fee7d..c0e93d9 100644 --- a/tests/tests/test_cluster_form.py +++ b/tests/tests/test_cluster_form.py @@ -5,7 +5,7 @@ from django import VERSION as DJANGO_VERSION from django.core.exceptions import ValidationError from django.test import TestCase -from tests.models import Band, BandMember, Album, Restaurant, Article, Author, Document, Gallery, Song +from tests.models import Band, BandMember, Album, Log, Restaurant, Article, Author, Document, Gallery, Song from modelcluster.forms import ClusterForm from django.forms import Textarea, CharField from django.forms.widgets import TextInput, FileInput @@ -770,6 +770,28 @@ class Meta: }) self.assertFalse(form.is_valid()) + def test_unique_constraint(self): + class LogForm(ClusterForm): + class Meta: + model = Log + fields = ["data"] + formsets = ["categories"] + + form = LogForm({ + "data": "User signed in", + + "categories-TOTAL_FORMS": 2, + "categories-INITIAL_FORMS": 0, + "categories-MAX_NUM_FORMS": 1000, + + "categories-0-name": "user", + "categories-0-id": "", + + "categories-1-name": "user", + "categories-1-id": "", + }) + self.assertFalse(form.is_valid()) + class FormWithM2MTest(TestCase): def setUp(self):