Hello there, Partial unique constraints are currently not supported during validation for reasons described in this ticket[0].
For example (inspired by this Github comment[1]), if you define the following model class Article(models.Model): slug = models.CharField(max_length=100) deleted_at = models.DateTimeField(null=True) class Meta: constraints = [ UniqueConstraint('slug', condition=Q(deleted_at=None), name='unique_slug'), ] Then validate_unique must perform the following query to determine if the constraint is violated SELECT NOT (%(deleted_at)s IS NULL) OR NOT EXISTS(SELECT 1 FROM article WHERE NOT id = %(id)s AND slug = %(slug)s AND deleted_at IS NULL) In other words, the validation of a partial unique constraint must check that either of these conditions are true 1. The provided instance doesn't match the condition 2. There's no existing rows matching the unique constraint (excluding the current instance if it already exists) This is not something Django supports right now. In order to add proper support for this feature I believe (personal opinion here feedback is welcome) we should follow these steps: 1. Add support for Expression.check(using: str) -> bool that would translate IsNull(deleted_at, True).check('alias') into a backend compatible 'SELECT %(deleted_at)s IS NULL' query and return whether or not it passed. That would also allow the constructions of forms like (~Q(IsNull(deleted_at, True)) | ~Exists(Article.objects.exclude(pk=pk).filter(slug=slug, deleted_at=None)).check(using) 2. Add support for Constraint.validate(instance, excluded_fields) as described in [0] that would build on top of Expression.check to implement proper UniqueConstraint, CheckConstraint, and ExclusionConstraint validation and allow for third-party app (e.g. django-rest-framework which doesn't use model level validation[2]) to take advantage of this feature. For example the unique_for_(date|month|year) feature of Date(Time)?Field could be deprecated in favour of Constraint subclasses that implement as_sql to enforce SQL level constraint if available by the current backend and implement .validate to replace the special case logic we have currently in place for these options[3]. I hope this clarify the current situation. Cheers, Simon [0] https://code.djangoproject.com/ticket/30581#comment:7 [1] https://github.com/django/django/pull/10796#discussion_r244216763 [2] https://github.com/encode/django-rest-framework/issues/7173 [3] https://github.com/django/django/blob/e703b152c6148ddda1b072a4353e9a41dca87f90/django/db/models/base.py#L1062-L1084 Le mardi 1 juin 2021 à 11:18:23 UTC-4, gaga...@gmail.com a écrit : > Hi, > > I changed several models from fields using `unique=True` to using > `UniqueConstraint` with a condition in the Meta. > > As a side-effect, the uniqueness are no longer validated during cleaning > of a Form and an integrity error is raised. This is because partial unique > indexes are excluded : > > https://github.com/django/django/blob/e703b152c6148ddda1b072a4353e9a41dca87f90/django/db/models/options.py#L865-L874 > > It seems that `total_unique_constraints` is also used to check for fields > that should be unique (related fields and USERNAME_FIELD specifically). > > I tried modifying `total_unique_constraints` and the only tests which > failed were related to the above concern and > `test_total_ordering_optimization_meta_constraints` which also uses ` > total_unique_constraints`. My application works fine and the validation > error are correctly raised in my forms. > > The current behaviour of `Model.validate_unique` is also not the one I > expected as my conditional `UniqueConstraint` were not used (which caused > the integrity error). > > Am I missing something? Or should we use all constraints (including > partial) in `Model.validate_unique`? > > If this is indeed what should be done, adding an `all_unique_constraints` > next to `total_unique_constraints` and using it in `Model.validate_unique` > instead of `total_unique_constraints` would do the trick. I don't mind > opening a ticket and doing the PR if needed. > > Thanks. > -- You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-developers+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/4972f6d0-c590-473d-8571-063738baf2ccn%40googlegroups.com.