Contraintes de bases de données spécifiques à PostgreSQLLien vers cette rubrique

PostgreSQL offre des contraintes d’intégrité de données supplémentaires dans le module django.contrib.postgres.constraints. Elles sont ajoutées dans l’option Meta.constraints des modèles.

ExclusionConstraintLien vers cette rubrique

class ExclusionConstraint(*, name, expressions, index_type=None, condition=None)Lien vers cette définition

Crée une contrainte d’exclusion dans la base de données. En interne, PostgreSQL implémente les contraintes d’exclusion par des index. Le type d’index par défaut est GiST. Pour les utiliser, vous devez activer l’extension btree_gist dans PostgreSQL. Vous pouvez installer l’extension par une opération de migration BtreeGistExtension.

Si vous essayez d’insérer une nouvelle ligne qui entre en conflit avec une ligne existante, une erreur IntegrityError se produit. De même lorsqu’un conflit survient lors d’une mise à jour.

nameLien vers cette rubrique

ExclusionConstraint.nameLien vers cette définition

Le nom de la contrainte.

expressionsLien vers cette rubrique

ExclusionConstraint.expressionsLien vers cette définition

Un itérable de tuples binaires. Le premier élément est une expression ou une chaîne. Le second élément est un opérateur SQL sous forme de chaîne. Pour éviter les erreurs syntaxiques, vous pouvez utiliser RangeOperators qui fait correspondre les opérateurs à des chaînes. Par exemple

Code
expressions=[
    ('timespan', RangeOperators.ADJACENT_TO),
    (F('room'), RangeOperators.EQUAL),
]

index_typeLien vers cette rubrique

ExclusionConstraint.index_typeLien vers cette définition

Le type d’index de la contrainte. La valeurs admises sont GIST ou SPGIST. Les correspondances ne tiennent pas compte de la casse. En cas d’absence, le type d’index par défaut est GIST.

conditionLien vers cette rubrique

ExclusionConstraint.conditionLien vers cette définition

Un objet Q qui indique la condition de restriction d’une contrainte à un sous-ensemble de lignes. Par exemple, condition=Q(annule=False).

Ces conditions sont soumises aux mêmes restrictions de base de données que django.db.models.Index.condition.

ExemplesLien vers cette rubrique

L’exemple suivant évite les chevauchements de réservations d’une même salle, sans tenir compte des réservations annulées

Code
from django.contrib.postgres.constraints import ExclusionConstraint
from django.contrib.postgres.fields import DateTimeRangeField, RangeOperators
from django.db import models
from django.db.models import Q

class Room(models.Model):
    number = models.IntegerField()


class Reservation(models.Model):
    room = models.ForeignKey('Room', on_delete=models.CASCADE)
    timespan = DateTimeRangeField()
    cancelled = models.BooleanField(default=False)

    class Meta:
        constraints = [
            ExclusionConstraint(
                name='exclude_overlapping_reservations',
                expressions=[
                    ('timespan', RangeOperators.OVERLAPS),
                    ('room', RangeOperators.EQUAL),
                ],
                condition=Q(cancelled=False),
            ),
        ]

Dans le cas où un modèle définit un intervalle avec deux champs, au lieu des types d’intervalle natifs de PostgreSQL, vous devriez écrire une expression qui utilise la fonction équivalente (par ex. TsTzRange()), et utiliser les délimiteurs du champ. La plupart du temps, les délimiteurs seront '[)', ce qui signifie que la limite inférieure est inclusive et la limite supérieure exclusive. Vous pouvez utiliser RangeBoundary qui fournit des correspondances d’expression pour les limites d’intervalle. Par exemple

Code
from django.contrib.postgres.constraints import ExclusionConstraint
from django.contrib.postgres.fields import (
    DateTimeRangeField,
    RangeBoundary,
    RangeOperators,
)
from django.db import models
from django.db.models import Func, Q


class TsTzRange(Func):
    function = 'TSTZRANGE'
    output_field = DateTimeRangeField()


class Reservation(models.Model):
    room = models.ForeignKey('Room', on_delete=models.CASCADE)
    start = models.DateTimeField()
    end = models.DateTimeField()
    cancelled = models.BooleanField(default=False)

    class Meta:
        constraints = [
            ExclusionConstraint(
                name='exclude_overlapping_reservations',
                expressions=(
                    (TsTzRange('start', 'end', RangeBoundary()), RangeOperators.OVERLAPS),
                    ('room', RangeOperators.EQUAL),
                ),
                condition=Q(cancelled=False),
            ),
        ]