web-dev-qa-db-fra.com

Django validation des champs du modèle

Où doit aller la validation de champs du modèle dans django?

Je pourrais nommer au moins deux choix possibles: dans la méthode surchargée .save () du modèle ou dans la méthode .to_python () des modèles. Sous-classe de champ (évidemment pour que cela fonctionne, vous devez écrire des champs personnalisés).

Cas d'utilisation possibles:

  • lorsqu'il est absolument nécessaire de s'assurer qu'une chaîne vide n'est pas écrite dans la base de données (vide = l'argument mot-clé faux ne fonctionne pas ici, c'est uniquement pour la validation du formulaire)
  • quand il est nécessaire de s'assurer que l'argument du mot-clé "choix" est respecté au niveau de la base de données et pas seulement dans l'interface d'administration (sorte d'émulation d'un type de données enum)

Il existe également un attribut au niveau de la classe empty_strings_allowed dans les modèles. La définition de la classe de base du champ et les classes dérivées la remplacent volontiers, mais cela ne semble pas produire d'effet sur le niveau de la base de données, ce qui signifie que je peux toujours construire un modèle avec des champs de chaîne vide et l'enregistrer dans la base de données . Ce que je veux éviter (oui, c'est nécessaire).

Les implémentations possibles sont

sur le terrain:

class CustomField(models.CharField):
    __metaclass__ = models.SubfieldBase
    def to_python(self, value):
        if not value:
            raise IntegrityError(_('Empty string not allowed'))
        return models.CharField.to_python(self, value)

au niveau du modèle:

class MyModel(models.Model)
    FIELD1_CHOICES = ['foo', 'bar', 'baz']
    field1 = models.CharField(max_length=255, 
               choices=[(item,item) for item in FIELD1_CHOICES])

    def save(self, force_insert=False, force_update=False):
        if self.field1 not in MyModel.FIELD1_CHOICES:
            raise IntegrityError(_('Invalid value of field1'))
        # this can, of course, be made more generic
        models.Model.save(self, force_insert, force_update)

Peut-être que je manque quelque chose et que cela peut être fait plus facilement (et plus propre)?

58
shylent

Django dispose d'un système validation du modèle depuis la version 1.2.

Dans les commentaires, sebpiq dit "Ok, maintenant il y a un endroit pour mettre la validation du modèle ... sauf qu'il n'est exécuté que lors de l'utilisation d'un ModelForm! Donc la question demeure, quand il est nécessaire de s'assurer que la validation est respectée au niveau de la base de données , que devez-vous faire? Où appeler full_clean? "

Il n'est pas possible via la validation au niveau Python de s'assurer que la validation est respectée au niveau db. Le plus proche est probablement d'appeler full_clean Dans une méthode save surchargée. Cela n'est pas fait par défaut, car cela signifie que tous ceux qui appellent cette méthode save devraient maintenant être préparés à intercepter et à gérer ValidationError.

Mais même si vous faites cela, quelqu'un peut toujours mettre à jour les instances de modèle en bloc à l'aide de queryset.update(), qui contournera cette validation. Il n'y a aucun moyen Django pourrait implémenter une queryset.update() raisonnablement efficace qui pourrait toujours effectuer une validation au niveau Python sur chaque objet mis à jour.

La seule façon de garantir réellement l'intégrité au niveau de la base de données consiste à utiliser des contraintes au niveau de la base de données; toute validation que vous effectuez via l'ORM nécessite que l'auteur du code d'application soit conscient du moment où la validation est appliquée (et gère les échecs de validation).

C'est pourquoi la validation du modèle n'est appliquée par défaut que dans ModelForm - car dans un ModelForm, il existe déjà un moyen évident de gérer un ValidationError.

62
Carl Meyer

Je pense que tu veux ça ->

from Django.db.models.signals import pre_save

def validate_model(sender, **kwargs):
    if 'raw' in kwargs and not kwargs['raw']:
        kwargs['instance'].full_clean()

pre_save.connect(validate_model, dispatch_uid='validate_models')

(Copié de http://djangosnippets.org/snippets/2319/ )

6
Darius

Le problème fondamental pour cela est que la validation doit avoir lieu sur les modèles. Cela a été discuté pendant un certain temps dans Django (modèle de formulaire de recherche en fonction de la validation sur la liste de diffusion des développeurs). Cela conduit à une duplication ou à des choses échappant à la validation avant d'appuyer sur la base de données.

Bien que cela ne touche pas le tronc, Malcolm's "solution de validation du modèle du pauvre" est probablement la solution la plus propre pour éviter de se répéter.

3
Arthur Debert

Si je vous comprends "clairement" - vous devez remplacer la fonction get_db_prep_save au lieu de to_python

1
Oduvan