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.
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:
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')) )
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:
CharField
ajoute la contrainte NOT NULL
par défaut)check
: length(system) > 0
)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.
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")
]
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
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()