Pourquoi le model.save () de django n'appelle-t-il pas full_clean ()?

150

Je suis juste curieux de savoir s'il y a une bonne raison pour laquelle orm de django n'appelle pas 'full_clean' sur un modèle à moins qu'il ne soit enregistré dans le cadre d'un formulaire de modèle.

Notez que full_clean () ne sera pas appelé automatiquement lorsque vous appelez la méthode save () de votre modèle. Vous devrez l'appeler manuellement lorsque vous souhaitez exécuter la validation de modèle en une étape pour vos propres modèles créés manuellement. Doc propre complet de django

(NOTE: citation mise à jour pour Django 1.6 ... les précédents documents django avaient également une mise en garde concernant ModelForms.)

Y a-t-il de bonnes raisons pour lesquelles les gens ne voudraient pas ce comportement? Je pense que si vous preniez le temps d'ajouter une validation à un modèle, vous voudriez que cette validation soit exécutée à chaque fois que le modèle est enregistré.

Je sais comment tout faire fonctionner correctement, je cherche juste une explication.

Aaron
la source
11
Merci beaucoup pour cette question, cela m'a empêché de me cogner la tête contre le mur beaucoup plus de temps. J'ai créé un mixin qui pourrait aider les autres. Découvrez l'essentiel: gist.github.com/glarrain/5448253
glarrain
Et j'utilise enfin le signal pour attraper le pre_savecrochet et le faire full_cleansur tous les modèles capturés.
Alfred Huang

Réponses:

59

AFAIK, c'est à cause de la rétrocompatibilité. Il y a aussi des problèmes avec ModelForms avec des champs exclus, des modèles avec des valeurs par défaut, des signaux pre_save (), etc.

Sources qui pourraient vous intéresser:

lqc
la source
3
L'extrait le plus utile (IMHO) de la deuxième référence: "Développer une option de validation" automatique "qui est à la fois assez simple pour être réellement utile et suffisamment robuste pour gérer tous les cas extrêmes est - si c'est même possible - bien plus que peut être accompli sur la période 1.2. Par conséquent, pour l'instant, Django n'a rien de tel, et ne l'aura pas dans la 1.2. Si vous pensez que vous pouvez le faire fonctionner pour la 1.3, votre meilleur pari est de créer un proposition, y compris au moins un exemple de code, ainsi qu'une explication de la façon dont vous allez le garder à la fois simple et robuste. "
Josh
30

En raison de la compatibilité, le nettoyage automatique à la sauvegarde n'est pas activé dans le noyau django.

Si nous commençons un nouveau projet et que nous voulons que la saveméthode par défaut sur Model puisse nettoyer automatiquement, nous pouvons utiliser le signal suivant pour effectuer un nettoyage avant que chaque modèle ne soit enregistré.

from django.dispatch import receiver
from django.db.models.signals import pre_save, post_save

@receiver(pre_save)
def pre_save_handler(sender, instance, *args, **kwargs):
    instance.full_clean()
Alfred Huang
la source
2
Pourquoi est-ce meilleur (ou pire) que de remplacer la méthode save sur certains BaseModel (dont tous les autres hériteront) pour appeler d'abord full_clean, puis appeler super ()?
J__
7
Je vois deux problèmes avec cette approche 1) dans le cas où full_clean () de ModelForm serait appelé deux fois: par le formulaire et par le signal 2) Si le formulaire exclut certains champs, ils seraient quand même validés par le signal.
mehmet
1
@mehmet Alors peut - être vous pouvez les ajouter if send == somemodel, then exclude some fieldsàpre_save_handler
Simin Jie
4
Pour ceux qui utilisent ou envisagent d'utiliser cette approche: gardez à l'esprit que cette approche n'est pas officiellement prise en charge par Django et ne sera pas prise en charge dans un avenir prévisible (voir ce commentaire dans Django bug tracker: code.djangoproject.com/ticket/ 29655 # comment: 3 ), vous risquez donc de tomber sur certaines imperfections comme l'arrêt de l'authentification pour fonctionner ( code.djangoproject.com/ticket/29655 ) si vous activez la validation pour tous les modèles. Vous devrez gérer vous-même ces problèmes. Cependant, il n'y a pas de meilleure approche atm.
Evgeny A.
2
Depuis Django 2.2.3, cela pose un problème avec le système d'authentification de base. Vous obtiendrez un ValidationError: Session with this Session key already exists. Pour éviter cela, vous devez ajouter une instruction if pour sender in list_of_model_classesempêcher le signal de remplacer les modèles d'authentification par défaut de Django. Définissez list_of_model_classescomme vous le souhaitez
Addison Klinke
15

Le moyen le plus simple d'appeler la full_cleanméthode consiste simplement à remplacer la saveméthode dans votre model:

def save(self, *args, **kwargs):
    self.full_clean()
    return super(YourModel, self).save(*args, **kwargs)
M.Void
la source
Pourquoi est-ce meilleur (ou pire) que d'utiliser un signal?
J__
6
Je vois deux problèmes avec cette approche 1) dans le cas où full_clean () de ModelForm serait appelé deux fois: par le formulaire et par la sauvegarde 2) Si le formulaire exclut certains champs, ils seraient toujours validés par la sauvegarde.
mehmet
3

Au lieu d'insérer un morceau de code qui déclare un récepteur, nous pouvons utiliser une application comme INSTALLED_APPSsection danssettings.py

INSTALLED_APPS = [
    # ...
    'django_fullclean',
    # your apps here,
]

Avant cela, vous devrez peut-être installer à l' django-fullcleanaide de PyPI:

pip install django-fullclean
Alfred Huang
la source
13
Pourquoi voudriez-vous pip installune application avec 4 lignes de code (vérifiez le code source ) au lieu d'écrire ces lignes vous-même?
David D.
Une autre bibliothèque que je n'ai pas essayée moi-même: github.com/danielgatis/django-smart-save
Flimm
2

Si vous avez un modèle dont vous voulez vous assurer qu'il a au moins une relation FK, et que vous ne voulez pas utiliser null=Falseparce que cela nécessite la définition d'un FK par défaut (qui seraient des données inutiles), la meilleure façon que j'ai trouvée est pour ajouter de la personnalisation .clean()et des .save()méthodes. .clean()lève l'erreur de validation et .save()appelle le fichier clean. De cette façon, l'intégrité est appliquée à la fois à partir des formulaires et à partir d'autres codes d'appel, de la ligne de commande et des tests. Sans cela, il n'y a (AFAICT) aucun moyen d'écrire un test qui garantit qu'un modèle a une relation FK avec un autre modèle spécifiquement choisi (pas par défaut).

class Payer(models.Model):

    name = models.CharField(blank=True, max_length=100)
    # Nullable, but will enforce FK in clean/save:
    payer_group = models.ForeignKey(PayerGroup, null=True, blank=True,)

    def clean(self):
        # Ensure every Payer is in a PayerGroup (but only via forms)
        if not self.payer_group:
            raise ValidationError(
                {'payer_group': 'Each Payer must belong to a PayerGroup.'})

    def save(self, *args, **kwargs):
        self.full_clean()
        return super().save(*args, **kwargs)

    def __str__(self):
        return self.name
shacker
la source
1

Commenter la réponse de @Alfred Huang et commenter. On peut verrouiller le hook pre_save sur une application en définissant une liste de classes dans le module actuel (models.py) et en la comparant dans le hook pre_save:

CUSTOM_CLASSES = [obj for name, obj in
        inspect.getmembers(sys.modules[__name__])
        if inspect.isclass(obj)]

@receiver(pre_save)
def pre_save_handler(sender, instance, **kwargs):
    if type(instance) in CUSTOM_CLASSES:
        instance.full_clean()
Peter Shannon
la source