web-dev-qa-db-fra.com

Créer une contrainte de base de données via Django

J'ai un modèle Django qui ressemble à ceci:

class Dummy(models.Model):
    ...
    system = models.CharField(max_length=16)

Je veux que system ne soit jamais vide ou contienne des espaces.

Je sais comment utiliser les validateurs dans Django.

Mais j'appliquerais cela au niveau de la base de données.

Quelle est la manière la plus simple et similaire à Django de créer une contrainte DB pour cela?

J'utilise PostgreSQL et je n'ai pas besoin de prendre en charge une autre base de données.

12
guettli

Premier problème: création d'une contrainte de base de données via Django

UNE) Il semble que Django n'a pas encore cette capacité intégrée. Il y a un open de 9 ans ticket pour cela, mais je ne retiendrais pas mon souffle pour quelque chose qui se passe depuis si longtemps.

Edit: Depuis la version 2.2 (avril 2019), Django prend en charge le niveau de la base de données vérifier les contraintes .

B) Vous pourriez regarder dans le paquet Django-db-contraintes , à travers lequel vous pouvez définir des contraintes dans le modèle Meta. Je n'ai pas testé ce paquet, donc je ne sais pas à quel point il est vraiment utile.

# example using this package
class Meta:
    db_constraints = {
        'price_above_zero': 'check (price > 0)',
    }

Deuxième problème: le champ system ne doit jamais être vide ni contenir d'espaces blancs

Maintenant, nous aurions besoin de construire la contrainte check dans la syntaxe postgres pour accomplir cela. Je suis venu avec ces options:

  1. Vérifiez si la longueur de system est différente après la suppression des espaces blancs. En utilisant des idées de cette réponse , vous pouvez essayer:

    /* this check should only pass if `system` contains no
     * whitespaces (`\s` also detects new lines)
     */
    check ( length(system) = length(regexp_replace(system, '\s', '', 'g')) )
    
  2. Vérifiez si le nombre d'espaces est de 0. Pour cela, vous pouvez nous regexp_matches:

    /* this check should only pass if `system` contains no
     * whitespaces (`\s` also detects new lines)
     */
    check ( length(regexp_matches(system, '\s', 'g')) = 0 )
    

    Notez que la fonction length ne peut pas être utilisée avec regexp_matches Car cette dernière renvoie un set of text[] (ensemble de tableaux), mais je n'ai pas pu trouver la fonction appropriée pour compter les éléments de cet ensemble pour le moment.


Enfin, en réunissant les deux problèmes précédents , votre approche pourrait ressembler à ceci:

class Dummy(models.Model):
    # this already sets NOT NULL to the field in the database
    system = models.CharField(max_length=16)

    class Meta:
        db_constraints = {
            'system_no_spaces': 'check ( length(system) > 0 AND length(system) = length(regexp_replace(system, "\s", "", "g")) )',
        }

Cela vérifie que la valeur des champs:

  1. ne contient pas NULL (CharField ajoute la contrainte NOT NULL par défaut)
  2. n'est pas vide (première partie de check: length(system) > 0)
  3. n'a pas d'espaces (deuxième partie du check: même longueur après le remplacement des espaces)

Faites-moi savoir comment cela fonctionne pour vous, ou s'il y a des problèmes ou des inconvénients à cette approche.

12
Ralf

Mise à jour 2019

Django 2.2 a ajouté la prise en charge de contraintes au niveau de la base de données . Les nouvelles classes CheckConstraint et niqueConstraint permettent d'ajouter des contraintes de base de données personnalisées. Les contraintes sont ajoutées aux modèles à l'aide de option Meta.constraints .

La validation de votre système ressemblerait à quelque chose comme ceci:

class Dummy(models.Model):
    ...
    system = models.CharField(max_length=16)

    class Meta:
        constraints = [
            CheckConstraint(
                check=~Q(system="") & ~Q(system__contains=" "),
                name="system_not_blank")
        ]
11
Cesar Canassa

Vous pouvez ajouter la contrainte CHECK via une migration Django. Pour vérifier la longueur de la chaîne, vous pouvez utiliser char_length fonction et position pour vérifier la présence d'espaces blancs.

Citation de documents postgres ( https://www.postgresql.org/docs/current/static/ddl-constraints.html ):

Une contrainte de vérification est le type de contrainte le plus générique. Il vous permet de spécifier que la valeur dans une certaine colonne doit satisfaire une expression booléenne (vérité-valeur).

Pour exécuter sql arbitraire dans la migration, une opération RunSQL peut être utilisée ( https://docs.djangoproject.com/en/2.0/ref/migration-operations/#runsql ):

Permet l'exécution de SQL arbitraire sur la base de données - utile pour les fonctionnalités plus avancées des backends de base de données qui Django ne prend pas directement en charge, comme les index partiels.

Créer une migration vide:

python manage.py makemigrations --empty yourappname

Ajoutez sql pour créer une contrainte:

# Generated by Django A.B on YYYY-MM-DD HH:MM
from Django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ('yourappname', '0001_initial'),
    ]

    operations = [
         migrations.RunSQL('ALTER TABLE appname_dummy ADD CONSTRAINT syslen '
                           'CHECK (char_length(trim(system)) > 1);',
                           'ALTER TABLE appname_dummy DROP CONSTRAINT syslen;'),
         migrations.RunSQL('ALTER TABLE appname_dummy ADD CONSTRAINT syswh '
                           'CHECK (position(' ' in trim(system)) = 0);',
                           'ALTER TABLE appname_dummy DROP CONSTRAINT syswh;')


    ]

Exécuter la migration:

python manage.py migrate yourappname
7
ndpu

Je modifie ma réponse pour répondre à vos besoins.

Donc, si vous souhaitez exécuter une contrainte de base de données, essayez celle-ci:

import psycopg2
def your_validator():
    conn = psycopg2.connect("dbname=YOURDB user=YOURUSER")
    cursor = conn.cursor()
    query_result = cursor.execute("YOUR QUERY")
    if query_result is Null:
        # Do stuff
    else:
        # Other Stuff

Utilisez ensuite le pre_save signal.

Dans votre models.py ajout de fichier,

from Django.db.models.signals import pre_save
class Dummy(models.Model):
...
    @staticmethod
    def pre_save(sender, instance, *args, **kwargs)
        # Of course, feel free to parse args in your def.
        your_validator()
1
Panos Angelopoulos