Restricciones de base de datos específicas de PostgreSQL

PostgreSQL admite restricciones de integridad de datos adicionales disponibles desde el módulo django.contrib.postgres.constraints. Se agregan en la opción Meta.constraints del modelo.

ExclusionConstraint

class ExclusionConstraint(*, name, expressions, index_type=None, condition=None, deferrable=None, include=None, violation_error_code=None, violation_error_message=None)[fuente]

Crea una restricción de exclusión en la base de datos. Internamente, PostgreSQL implementa las restricciones de exclusión utilizando índices. El tipo de índice por defecto es GiST. Para utilizarlos, debes activar la extensión btree_gist en PostgreSQL. Puedes instalarla utilizando la operación de migración BtreeGistExtension.

Si intentas insertar una nueva fila que conflictúa con una fila existente, se levanta un error de integridad:exc:~django.db.IntegrityError. De manera similar, cuando actualizas y el conflicto con una fila existente.

Los exclusion constraints se verifican durante la validación del modelo.

nombre

ExclusionConstraint.name

Ver BaseConstraint.name.

expressiones

ExclusionConstraint.expressions

Una secuencia iterable de 2-tuplas. El primer elemento es una expresión o cadena de caracteres. El segundo elemento es un operador SQL representado como una cadena de caracteres. Para evitar errores de escritura, puedes utilizar RangeOperators que mapea los operadores con cadenas de caracteres. Por ejemplo:

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

Restricciones sobre operadores.

Solo se pueden usar operadores comutativos en exclusion constraints.

La expresión OpClass() se puede utilizar para especificar una clase de operador personalizada para las expresiones del constraint. Por ejemplo:

expressions = [
    (OpClass("circle", name="circle_ops"), RangeOperators.OVERLAPS),
]

crea un exclusion constraint sobre circle utilizando circle_ops.

index_type

ExclusionConstraint.index_type

El tipo de índice del constraint. Los valores aceptados son GIST o SPGIST. La coincidencia es insensible a mayúsculas y minúsculas. Si no se proporciona, el tipo de índice por defecto es GIST.

condición

ExclusionConstraint.condition

Un objeto Q que especifica la condición para restringir una restricción a un subconjunto de filas. Por ejemplo, condición=Q(cancelled=False).

Estas condiciones tienen las mismas restricciones de base de datos que django.db.models.Index.condition.

deferible

ExclusionConstraint.deferrable

Establezca este parámetro para crear una exclusión deferrable. Los valores aceptados son Deferrable.DEFERRED o Deferrable.IMMEDIATE. Por ejemplo:

from django.contrib.postgres.constraints import ExclusionConstraint
from django.contrib.postgres.fields import RangeOperators
from django.db.models import Deferrable

ExclusionConstraint(
    name="exclude_overlapping_deferred",
    expressions=[
        ("timespan", RangeOperators.OVERLAPS),
    ],
    deferrable=Deferrable.DEFERRED,
)

Por defecto, las restricciones no se retrasan. Una restricción diferida no se aplicará hasta el final de la transacción. Una restricción inmediata se aplicará inmediatamente después de cada comando.

Advertencia

Las exclusión deferrables pueden provocar una penalización en rendimiento <https://www.postgresql.org/docs/current/sql-createtable.html#id-1.9.3.85.9.4>`_.

incluye

ExclusionConstraint.include

Una lista o tupla de los nombres de los campos a incluir en la exclusión cubierta como columnas no clave. Esto permite el uso de escaneos índice solo para consultas que seleccionan solo campos incluidos (include) y filtran solo por campos indexados (expressions).

incluye se admite para índices GiST. PostgreSQL 14+ también admite incluye para SP-GiST índices.

código_de_error_violación

ExclusionConstraint.violation_error_code

El código de error utilizado cuando se levanta ValidationError durante la validación del modelo. Por defecto, es None.

mensaje_de_error_violación

El mensaje de error utilizado cuando se levanta ValidationError durante la validación del modelo. Por defecto, es BaseConstraint.mensaje_de_error_violación.

Ejemplos

El siguiente ejemplo restringe las reservas que se superponen en la misma habitación, sin tener en cuenta las reservas canceladas:

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),
            ),
        ]

En caso de que tu modelo defina un rango utilizando dos campos, en lugar de los tipos nativos de PostgreSQL para rangos, debes escribir una expresión que utilice la función equivalente (por ejemplo TsTzRange()), y utiliza los delimitadores para el campo. La mayoría de las veces, los delimitadores serán '[)', lo que significa que el límite inferior es inclusivo y el límite superior es exclusivo. Puedes utilizar la RangeBoundary que proporciona una expresión de mapeo para los límites del rango <https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-INCLUSIVITY>`_. Por ejemplo:

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),
            ),
        ]