web-dev-qa-db-fra.com

Django post_save a empêché la récupération et le remplacement du modèle save ()

Il existe de nombreuses publications Stack Overflow sur la récursivité utilisant le signal post_save, auxquelles les commentaires et les réponses sont en grande majorité: "pourquoi ne pas remplacer save ()" ou une sauvegarde qui est uniquement déclenchée sur created == True.

Eh bien, j'estime qu'il est judicieux de ne pas utiliser save() - par exemple, j'ajoute une application temporaire qui traite les données d'exécution de la commande de manière totalement distincte de notre modèle de commande.

Le reste de la structure ignore parfaitement l'application d'exécution et l'utilisation de hooks post_save isole tous les codes liés à l'exécution de notre modèle Order.

Si nous abandonnons le service d'exécution, rien de notre code principal ne doit changer. Nous supprimons l'application de réalisation, et c'est tout.

Donc, y a-t-il des méthodes décentes pour s'assurer que le signal post_save ne déclenche pas le même gestionnaire deux fois?

29

Que pensez-vous de cette solution?

@receiver(post_save, sender=Article)
def generate_thumbnails(sender, instance=None, created=False, **kwargs):

    if not instance:
        return

    if hasattr(instance, '_dirty'):
        return

    do_something()

    try:
        instance._dirty = True
        instance.save()
    finally:
        del instance._dirty

Vous pouvez également créer un décorateur

def prevent_recursion(func):

    @wraps(func)
    def no_recursion(sender, instance=None, **kwargs):

        if not instance:
            return

        if hasattr(instance, '_dirty'):
            return

        func(sender, instance=instance, **kwargs)

        try:
            instance._dirty = True
            instance.save()
        finally:
            del instance._dirty

    return no_recursion


@receiver(post_save, sender=Article)
@prevent_recursion
def generate_thumbnails(sender, instance=None, created=False, **kwargs):

    do_something()
23
xakdog

vous pouvez utiliser update au lieu de sauvegarder dans le gestionnaire de signal

 quersyset.filter (pk = instance.pk) .update (....) 
76
mossplix

Ne déconnectez pas les signaux. Si un nouveau modèle du même type est généré alors que le signal est déconnecté, la fonction de gestionnaire ne sera pas activée. Les signaux sont globaux sur Django et plusieurs demandes peuvent être exécutées simultanément, ce qui fait que certaines échouent tandis que d'autres exécutent leur gestionnaire post_save.

34
punkgode

Je pense que créer une méthode save_without_signals() sur le modèle est plus explicite:

class MyModel()
    def __init__():
        # Call super here.
        self._disable_signals = False

    def save_without_signals(self):
        """
        This allows for updating the model from code running inside post_save()
        signals without going into an infinite loop:
        """
        self._disable_signals = True
        self.save()
        self._disable_signals = False

def my_model_post_save(sender, instance, *args, **kwargs):
    if not instance._disable_signals:
        # Execute the code here.
23
Rune Kaagaard

Pourquoi ne pas déconnecter puis reconnecter le signal dans votre fonction post_save:

def my_post_save_handler(sender, instance, **kwargs):
    post_save.disconnect(my_post_save_handler, sender=sender)
    instance.do_stuff()
    instance.save()
    post_save.connect(my_post_save_handler, sender=sender)
post_save.connect(my_post_save_handler, sender=Order)
20
dgel

Vous devriez utiliser queryset.update () au lieu de Model.save () mais vous devez vous occuper d'autre chose:

Il est important de noter que lorsque vous l'utilisez, si vous souhaitez utiliser le nouvel objet, vous devez récupérer son objet à nouveau, car il ne modifiera pas l'objet self, par exemple:

>>> MyModel.objects.create(pk=1, text='')
>>> el = MyModel.objects.get(pk=1)
>>> queryset.filter(pk=1).update(text='Updated')
>>> print el.text
>>> ''

Donc, si vous voulez utiliser le nouvel objet, vous devriez recommencer:

>>> MyModel.objects.create(pk=1, text='')
>>> el = MyModel.objects.get(pk=1)
>>> queryset.filter(pk=1).update(text='Updated')
>>> el = MyModel.objects.get(pk=1) # Do it again
>>> print el.text
>>> 'Updated'
4
ruhanbidart

Vous pouvez également vérifier l'argument raw dans post_save, puis appeler save_baseinstead de save.

4
dragoon

Regarde ça...

Chaque signal a ses propres avantages, comme vous pouvez le lire dans la documentation ici, mais je voulais partager quelques éléments à garder à l’esprit avec les signaux pre_save et post_save.

  • Les deux sont appelés à chaque fois que .save () est appelé sur un modèle. En d'autres termes, si vous enregistrez l'instance de modèle, les signaux sont envoyés.

  • l'exécution de save () sur l'instance dans un post_save peut souvent créer une boucle sans fin et donc provoquer une erreur de surchauffe de la récursivité maximale - uniquement si vous n'utilisez pas correctement .save ().

  • pre_save est idéal pour modifier uniquement les données d'instance car vous n'avez pas à appeler save (), ce qui élimine la possibilité susmentionnée. Vous n'avez pas à appeler save () car un signal de pré-enregistrement signifie littéralement juste avant d'être enregistré.

  • Les signaux peuvent appeler d'autres signaux et/ou exécuter des tâches différées (pour le céleri), ce qui peut être énorme pour la facilité d'utilisation.

Source: https://www.codingforentrepreneurs.com/blog/post-save-vs-pre-save-vs-override-save-method/

Cordialement!! 

0
Jesús Díaz