it-roy-ru.com

Django post_save предотвращает рекурсию без переопределения модели save ()

Существует много сообщений о переполнении стека о рекурсии с использованием сигнала post_save, к которому в подавляющем большинстве случаев приводятся комментарии и ответы: «почему бы не переопределить save ()» или сохранение, которое запускается только при created == True.

Что ж, я считаю, что есть хороший повод для того, чтобы не использовать save() - например, я добавляю временное приложение, которое обрабатывает данные о выполнении заказов полностью отдельно от нашей модели Order.

Остальная часть фреймворка, к счастью, не знает о приложении выполнения, а использование хуков post_save изолирует весь код, связанный с выполнением, от нашей модели Order.

Если мы отбросим службу выполнения, ничего в нашем основном коде не должно измениться. Мы удаляем приложение выполнения, и это все.

Итак, есть ли достойные методы, чтобы сигнал post_save не запускал один и тот же обработчик дважды?

29
Yuji 'Tomita' Tomita

Что вы думаете об этом решении?

@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

Вы также можете создать декоратор

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

вы можете использовать обновление вместо сохранения в обработчике сигнала

 Quersyset.filter (рк = instance.pk) .update (....) 
76
mossplix

Не отключайте сигналы. Если при отключении сигнала генерируется какая-либо новая модель того же типа, функция-обработчик не будет запущена. Сигналы глобальны по всему Django, и несколько запросов могут выполняться одновременно, что приводит к сбою некоторых, в то время как другие запускают свой обработчик post_save.

34
punkgode

Я думаю, что создание метода save_without_signals() на модели более явно:

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

Как насчет отключения и повторного подключения сигнала в вашей функции 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

Вы должны использовать queryset.update () вместо Model.save (), но вам нужно позаботиться о чем-то еще:

Важно отметить, что когда вы используете его, если вы хотите использовать новый объект, вы должны снова получить его объект, потому что он не изменит объект self, например:

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

Итак, если вы хотите использовать новый объект, вы должны сделать снова:

>>> 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

Вы также можете проверить аргумент raw в post_save и затем вызвать save_baseinstead из save.

4
dragoon

Проверь это...

Каждый сигнал имеет свои преимущества, о которых вы можете прочитать в документации здесь, но я хотел бы поделиться несколькими вещами, о которых следует помнить с сигналами pre_save и post_save.

  • Оба вызываются каждый раз .save () в модели вызывается. Другими словами, если вы сохраните экземпляр модели, сигналы будут отправлены.

  • выполнение save () для экземпляра в post_save часто может создать бесконечный цикл и, следовательно, привести к превышению максимальной глубины рекурсии - только если вы неправильно используете .save ().

  • pre_save отлично подходит для изменения только данных экземпляра, потому что вам не нужно вызывать save () когда-либо, что исключает возможность выше. Причина, по которой вам не нужно вызывать save (), заключается в том, что сигнал pre_save буквально означает «прямо перед сохранением».

  • Сигналы могут вызывать другие сигналы и/или запускать отложенные задачи (для сельдерея), которые могут быть огромными для удобства использования.

Источник: https://www.codingforentrepreneurs.com/blog/post-save-vs-pre-save-vs-override-save-method/

С уважением!! 

0
Jesús Díaz