Fixed #30581 -- Added support for Meta.constraints validation.

Thanks Simon Charette, Keryn Knight, and Mariusz Felisiak for reviews.
This commit is contained in:
Gagaro 2022-01-31 16:04:13 +01:00 committed by Mariusz Felisiak
parent 441103a04d
commit 667105877e
17 changed files with 852 additions and 88 deletions

View file

@ -395,6 +395,8 @@ Models
* **models.W043**: ``<database>`` does not support indexes on expressions.
* **models.W044**: ``<database>`` does not support unique constraints on
expressions.
* **models.W045**: Check constraint ``<constraint>`` contains ``RawSQL()``
expression and won't be validated during the model ``full_clean()``.
Security
--------

View file

@ -12,7 +12,7 @@ PostgreSQL supports additional data integrity constraints available from the
``ExclusionConstraint``
=======================
.. class:: ExclusionConstraint(*, name, expressions, index_type=None, condition=None, deferrable=None, include=None, opclasses=())
.. class:: ExclusionConstraint(*, name, expressions, index_type=None, condition=None, deferrable=None, include=None, opclasses=(), violation_error_message=None)
Creates an exclusion constraint in the database. Internally, PostgreSQL
implements exclusion constraints using indexes. The default index type is
@ -27,6 +27,14 @@ PostgreSQL supports additional data integrity constraints available from the
:exc:`~django.db.IntegrityError` is raised. Similarly, when update
conflicts with an existing row.
Exclusion constraints are checked during the :ref:`model validation
<validating-objects>`.
.. versionchanged:: 4.1
In older versions, exclusion constraints were not checked during model
validation.
``name``
--------
@ -165,6 +173,15 @@ creates an exclusion constraint on ``circle`` using ``circle_ops``.
:class:`OpClass() <django.contrib.postgres.indexes.OpClass>` in
:attr:`~ExclusionConstraint.expressions`.
``violation_error_message``
---------------------------
.. versionadded:: 4.1
The error message used when ``ValidationError`` is raised during
:ref:`model validation <validating-objects>`. Defaults to
:attr:`.BaseConstraint.violation_error_message`.
Examples
--------

View file

@ -31,24 +31,21 @@ option.
.. admonition:: Validation of Constraints
In general constraints are **not** checked during ``full_clean()``, and do
not raise ``ValidationError``\s. Rather you'll get a database integrity
error on ``save()``. ``UniqueConstraint``\s without a
:attr:`~UniqueConstraint.condition` (i.e. non-partial unique constraints)
and :attr:`~UniqueConstraint.expressions` (i.e. non-functional unique
constraints) are different in this regard, in that they leverage the
existing ``validate_unique()`` logic, and thus enable two-stage validation.
In addition to ``IntegrityError`` on ``save()``, ``ValidationError`` is
also raised during model validation when the ``UniqueConstraint`` is
violated.
Constraints are checked during the :ref:`model validation
<validating-objects>`.
.. versionchanged:: 4.1
In older versions, constraints were not checked during model validation.
``BaseConstraint``
==================
.. class:: BaseConstraint(name)
.. class:: BaseConstraint(name, violation_error_message=None)
Base class for all constraints. Subclasses must implement
``constraint_sql()``, ``create_sql()``, and ``remove_sql()`` methods.
``constraint_sql()``, ``create_sql()``, ``remove_sql()`` and
``validate()`` methods.
All constraints have the following parameters in common:
@ -60,10 +57,37 @@ All constraints have the following parameters in common:
The name of the constraint. You must always specify a unique name for the
constraint.
``violation_error_message``
---------------------------
.. versionadded:: 4.1
.. attribute:: BaseConstraint.violation_error_message
The error message used when ``ValidationError`` is raised during
:ref:`model validation <validating-objects>`. Defaults to
``"Constraint “%(name)s” is violated."``.
``validate()``
--------------
.. versionadded:: 4.1
.. method:: BaseConstraint.validate(model, instance, exclude=None, using=DEFAULT_DB_ALIAS)
Validates that the constraint, defined on ``model``, is respected on the
``instance``. This will do a query on the database to ensure that the
constraint is respected. If fields in the ``exclude`` list are needed to
validate the constraint, the constraint is ignored.
Raise a ``ValidationError`` if the constraint is violated.
This method must be implemented by a subclass.
``CheckConstraint``
===================
.. class:: CheckConstraint(*, check, name)
.. class:: CheckConstraint(*, check, name, violation_error_message=None)
Creates a check constraint in the database.
@ -78,10 +102,14 @@ specifies the check you want the constraint to enforce.
For example, ``CheckConstraint(check=Q(age__gte=18), name='age_gte_18')``
ensures the age field is never less than 18.
.. versionchanged:: 4.1
The ``violation_error_message`` argument was added.
``UniqueConstraint``
====================
.. class:: UniqueConstraint(*expressions, fields=(), name=None, condition=None, deferrable=None, include=None, opclasses=())
.. class:: UniqueConstraint(*expressions, fields=(), name=None, condition=None, deferrable=None, include=None, opclasses=(), violation_error_message=None)
Creates a unique constraint in the database.
@ -203,3 +231,21 @@ For example::
creates a unique index on ``username`` using ``varchar_pattern_ops``.
``opclasses`` are ignored for databases besides PostgreSQL.
``violation_error_message``
---------------------------
.. versionadded:: 4.1
.. attribute:: UniqueConstraint.violation_error_message
The error message used when ``ValidationError`` is raised during
:ref:`model validation <validating-objects>`. Defaults to
:attr:`.BaseConstraint.violation_error_message`.
This message is *not used* for :class:`UniqueConstraint`\s with
:attr:`~UniqueConstraint.fields` and without a
:attr:`~UniqueConstraint.condition`. Such :class:`~UniqueConstraint`\s show the
same message as constraints defined with
:attr:`.Field.unique` or in
:attr:`Meta.unique_together <django.db.models.Options.constraints>`.

View file

@ -198,9 +198,10 @@ There are three steps involved in validating a model:
1. Validate the model fields - :meth:`Model.clean_fields()`
2. Validate the model as a whole - :meth:`Model.clean()`
3. Validate the field uniqueness - :meth:`Model.validate_unique()`
4. Validate the constraints - :meth:`Model.validate_constraints`
All three steps are performed when you call a model's
:meth:`~Model.full_clean()` method.
All four steps are performed when you call a model's :meth:`~Model.full_clean`
method.
When you use a :class:`~django.forms.ModelForm`, the call to
:meth:`~django.forms.Form.is_valid()` will perform these validation steps for
@ -210,12 +211,18 @@ need to call a model's :meth:`~Model.full_clean()` method if you plan to handle
validation errors yourself, or if you have excluded fields from the
:class:`~django.forms.ModelForm` that require validation.
.. method:: Model.full_clean(exclude=None, validate_unique=True)
.. versionchanged:: 4.1
This method calls :meth:`Model.clean_fields()`, :meth:`Model.clean()`, and
:meth:`Model.validate_unique()` (if ``validate_unique`` is ``True``), in that
order and raises a :exc:`~django.core.exceptions.ValidationError` that has a
``message_dict`` attribute containing errors from all three stages.
In older versions, constraints were not checked during the model
validation.
.. method:: Model.full_clean(exclude=None, validate_unique=True, validate_constraints=True)
This method calls :meth:`Model.clean_fields()`, :meth:`Model.clean()`,
:meth:`Model.validate_unique()` (if ``validate_unique`` is ``True``), and
:meth:`Model.validate_constraints()` (if ``validate_constraints`` is ``True``)
in that order and raises a :exc:`~django.core.exceptions.ValidationError` that
has a ``message_dict`` attribute containing errors from all four stages.
The optional ``exclude`` argument can be used to provide a list of field names
that can be excluded from validation and cleaning.
@ -238,6 +245,10 @@ models. For example::
The first step ``full_clean()`` performs is to clean each individual field.
.. versionchanged:: 4.1
The ``validate_constraints`` argument was added.
.. method:: Model.clean_fields(exclude=None)
This method will validate all fields on your model. The optional ``exclude``
@ -306,7 +317,7 @@ pass a dictionary mapping field names to errors::
'pub_date': ValidationError(_('Invalid date.'), code='invalid'),
})
Finally, ``full_clean()`` will check any unique constraints on your model.
Then, ``full_clean()`` will check unique constraints on your model.
.. admonition:: How to raise field-specific validation errors if those fields don't appear in a ``ModelForm``
@ -339,16 +350,40 @@ Finally, ``full_clean()`` will check any unique constraints on your model.
.. method:: Model.validate_unique(exclude=None)
This method is similar to :meth:`~Model.clean_fields`, but validates all
uniqueness constraints on your model instead of individual field values. The
optional ``exclude`` argument allows you to provide a list of field names to
exclude from validation. It will raise a
This method is similar to :meth:`~Model.clean_fields`, but validates
uniqueness constraints defined via :attr:`.Field.unique`,
:attr:`.Field.unique_for_date`, :attr:`.Field.unique_for_month`,
:attr:`.Field.unique_for_year`, or :attr:`Meta.unique_together
<django.db.models.Options.unique_together>` on your model instead of individual
field values. The optional ``exclude`` argument allows you to provide a list of
field names to exclude from validation. It will raise a
:exc:`~django.core.exceptions.ValidationError` if any fields fail validation.
:class:`~django.db.models.UniqueConstraint`\s defined in the
:attr:`Meta.constraints <django.db.models.Options.constraints>` are validated
by :meth:`Model.validate_constraints`.
Note that if you provide an ``exclude`` argument to ``validate_unique()``, any
:attr:`~django.db.models.Options.unique_together` constraint involving one of
the fields you provided will not be checked.
Finally, ``full_clean()`` will check any other constraints on your model.
.. versionchanged:: 4.1
In older versions, :class:`~django.db.models.UniqueConstraint`\s were
validated by ``validate_unique()``.
.. method:: Model.validate_constraints(exclude=None)
.. versionadded:: 4.1
This method validates all constraints defined in
:attr:`Meta.constraints <django.db.models.Options.constraints>`. The
optional ``exclude`` argument allows you to provide a list of field names to
exclude from validation. It will raise a
:exc:`~django.core.exceptions.ValidationError` if any constraints fail
validation.
Saving objects
==============