**Executive summary**: In general they don't. Instead you get an
`IntegrityError` on `save()`. Except, `UniqueConstraint` is able to tie-in
to the existing `validate_unique()` logic, so you will get a
`ValidationError` on `full_clean()`.
It would be **nice** if all constraints could (in principle) be used for
Python-level validation, in addition to setting up the database
constraint. (We might image `ModelForm`, say, being able to extract
correct validation logic from the model definition, and so on.)
Initial thought is that Python validators could be inferred from `Q`
objects:
{{{
CheckConstraint(check=Q(age__gte=18), name='age_gte_18')
}}}
Looks like it maps to a simple `lambda age: age > 18`, for example.
More complex examples will cause issues. Discussion:
* [https://github.com/django/django/pull/10796#discussion_r244216763 PR
#10796 (comment)]
* [https://github.com/django/django/pull/388#issuecomment-8863209 PR #388
(comment)]
So we **might** be able to do **some** auto-generation but first goal here
should (I guess) be an optional hook on `BaseConstraint` that **if
implemented** can be picked up for use in validation. (Or similar.)
I'll create this as Accepted, since existing discussions imply that.
--
Ticket URL: <https://code.djangoproject.com/ticket/30581>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
Comment (by James Timmins):
@carltongibson Is this something I can take on? Or do we need more design
discussions?
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:1>
Comment (by Carlton Gibson):
Well, feel free, but read those comments. :)
If it were me, I’d come up with a half idea and then post to the
DevelopersMailingList
to get input from others, before coding too much... — but yes, it just
needs someone to try I’d guess.
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:2>
Comment (by James Timmins):
Ok, I appreciate advice! I'll put together some thoughts and get feedback
before assigning to myself or writing too much code. Gracias.
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:3>
Comment (by Simon Charette):
FWIW I think the validation should be pushed to the database as much as
possible. Trying to emulate constraint criterias in Python will hard if
not impossible to implement in a correct way for all backends.
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:4>
* owner: nobody => Sanskar Jaiswal
* status: new => assigned
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:5>
Comment (by Sanskar Jaiswal):
I went through the previous pull request comments. As far as I could
understand, generating some sort of validation from a Q object seems
complicated, and there would arise a lot of issues. Does anyone have any
recommendations?
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:6>
* cc: Simon Charette (added)
Comment:
I suggest you have a look at
[https://github.com/django/django/blob/5dabb6002ed773c150da4bd3aee756df75d218c2/django/db/models/base.py#L997-L1144
the existing] `Model.validate_unique` logic and draft an API that would
allow users to define their own constraints with custom validation.
The interface should be flexible enough to implement concepts such as
`unique_for_date`
([https://docs.djangoproject.com/en/3.0/ref/models/fields/#unique-for-date
and friends]) and
[https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/constraints/#exclusionconstraint
exclusion constraints] in a way that is similar to how `Field.unique=True`
and `Model._meta.unique_together` are handled right now.
I believe that we should aim for a setup where all of the current
''unique'' validation logic is deferred to `UniqueConstraint` by making
`Field.unique=True` and `unique_together` result in
`UniqueConstraint.auto_created = True` entries in
`Model._meta.constraints`. These `auto_created` constraints entries would
be ignored by migrations, just like `ManyToManyField` auto-created
intermediary models are, and should prove that the constraint enforcing
mechanism is flexible enough to cover all of the current use cases
`validate_unique` currently has.
I order to achieve that we'll likely want to define a new
`Constraint.enforce` (or `validate`) method that accepts an optional
existing model instance and a set of excluded fields that would raise on
violation. It would then be the responsibility of the subclass to
implement or not this function (e.g. we could skip it on
`UniqueConstraint` with a `condition` at first as they are more complex to
implement).
I hope this gives you a better idea of a plan to tackle this issue. Happy
to move the discussion to the mailing list to gather more feedback if
deemed more appropriate, that's just the way I envisioned it.
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:7>
Comment (by Sanskar Jaiswal):
Thanks for this informative reply. I’ll be sure to look into these and
come up with some sort of a plan, to be sent to the mailing list. I am
quite new (this is my first ticket), so I hope you guys will bear with me
if I say something stupid.
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:8>
Comment (by Sanskar Jaiswal):
I have gone through the issue and the related source code. I think I have
a fair idea of what's causing the issue. What I dont understand is that
what this ticket aims to achieve. Is there supposed to be a solution, to
raise an error upon `CheckConstraint`? Or is it supposed to be a custom
validation method in `BaseConstraint`, which lets users help define custom
validation? What exactly is the `Constraint.enforce` method supposed to
do? It would be nice if I could get a bit more help with some examples. I
am sorry, that I am having hard time understanding this, as I am a
beginner.
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:9>
Comment (by Simon Charette):
Sanskar Jaiswal, to be honest I think comment:7 provides a pretty good
picture of what ought to be done without forcing an implementation over
the other.
This ticket might be bit too tricky for a first as it requires good
knowledge of the ORM, model validation, and database constraints. I
suggest you have a look at other ones if you don't feel comfortable
handling this one right now.
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:10>
Comment (by Simon Charette):
FWIW I started exploring the above idea and published
[https://github.com/django/django/compare/master...charettes:constraints-
validation my changes] which pass most of the test suite but still require
a bit of polishing.
In the light of the current barrage of issues related to checks that are
not `UniqueConstraint` aware I think the `Constraint.auto_created`
creation by `Field.unique` and `Model.Meta.unique_together` has merits as
it would allow all of the ''unicity'' checks to be performed against
`_meta.constraints` (or the newly added `total_unique_constraints`)
instead of combining the three APIs all over the place.
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:11>
* cc: Adam (Chainz) Johnson (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:12>
* cc: Ian Foote (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:13>
* cc: Fabio Caritas Barrionuevo da Luz (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:14>
* cc: Gagaro (added)
* owner: Sanskar Jaiswal => Gagaro
Comment:
This issue is being discussed on the [mailing
list](https://groups.google.com/g/django-
developers/c/iiOnxAn3vy4/m/ZA2aPyWoBQAJ) and implementation has started.
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:15>
* needs_better_patch: 0 => 1
* has_patch: 0 => 1
* version: 2.2 => 4.0
* needs_tests: 0 => 1
* needs_docs: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:16>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"bf524d229f3c1008f41450e2750b85395aa75fe6" bf524d22]:
{{{
#!CommitTicketReference repository=""
revision="bf524d229f3c1008f41450e2750b85395aa75fe6"
Refs #30581 -- Allowed sql.Query to be used without model.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:17>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"7325d291524827806feed271a077ea23e2b1b21f" 7325d291]:
{{{
#!CommitTicketReference repository=""
revision="7325d291524827806feed271a077ea23e2b1b21f"
Refs #30581 -- Fixed DatabaseFeatures.bare_select_suffix on MySQL < 8 and
MariaDB < 10.4.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:18>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"9d04711261156c487c6085171398070ea3df8122" 9d047112]:
{{{
#!CommitTicketReference repository=""
revision="9d04711261156c487c6085171398070ea3df8122"
Refs #30581 -- Added Q.flatten().
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:19>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"5d91dc8ee3ff7e2230cfacf41005f61b8efbf057" 5d91dc8e]:
{{{
#!CommitTicketReference repository=""
revision="5d91dc8ee3ff7e2230cfacf41005f61b8efbf057"
Refs #30581 -- Added Q.check() hook.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:20>
Comment (by GitHub <noreply@…>):
In [changeset:"27b07a3246bc033cd9ded01238c6dc64731cce35" 27b07a32]:
{{{
#!CommitTicketReference repository=""
revision="27b07a3246bc033cd9ded01238c6dc64731cce35"
Refs #30581 -- Moved CheckConstraint tests for conditional expressions to
migrations.test_operations.
This allows avoiding warning in tests about using RawSQL in
CheckConstraints.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:21>
* needs_docs: 1 => 0
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:22>
* needs_better_patch: 1 => 0
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:23>
* needs_tests: 1 => 0
* stage: Accepted => Ready for checkin
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:24>
* status: assigned => closed
* resolution: => fixed
Comment:
In [changeset:"667105877e6723c6985399803a364848891513cc" 66710587]:
{{{
#!CommitTicketReference repository=""
revision="667105877e6723c6985399803a364848891513cc"
Fixed #30581 -- Added support for Meta.constraints validation.
Thanks Simon Charette, Keryn Knight, and Mariusz Felisiak for reviews.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:25>
Comment (by Carlton Gibson <carlton@…>):
In [changeset:"ce7321932d07b7bf9e6be77b9865c5058d9c1e4d" ce732193]:
{{{
#!CommitTicketReference repository=""
revision="ce7321932d07b7bf9e6be77b9865c5058d9c1e4d"
Refs #30581 -- Updated count of steps in model validation docs.
Follow-up to 667105877e6723c6985399803a364848891513cc.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:26>
Comment (by Carlton Gibson <carlton.gibson@…>):
In [changeset:"daf83303d14ea9b79ad277475cbf018076776f25" daf83303]:
{{{
#!CommitTicketReference repository=""
revision="daf83303d14ea9b79ad277475cbf018076776f25"
[4.1.x] Refs #30581 -- Updated count of steps in model validation docs.
Follow-up to 667105877e6723c6985399803a364848891513cc.
Backport of ce7321932d07b7bf9e6be77b9865c5058d9c1e4d from main
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:27>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"35911078fa40eb35859832987fedada76963c01e" 35911078]:
{{{
#!CommitTicketReference repository=""
revision="35911078fa40eb35859832987fedada76963c01e"
Replaced Expression.replace_references() with .replace_expressions().
The latter allows for more generic use cases beyond the currently
limited ones constraints validation has.
Refs #28333, #30581.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/30581#comment:28>