web-dev-qa-db-ja.com

Django post_saveは、モデルのsave()をオーバーライドせずに再帰を防止します

_post_save_シグナルを使用した再帰に関するスタックオーバーフローの投稿が多数あります。これらには、コメントと回答が圧倒的に多くなっています。「なぜsave()をオーバーライドしないか」または_created == True_でのみ発生する保存。

さて、save()を使用しないことには良いケースがあると思います-たとえば、注文処理データを注文モデルとは完全に分離して処理する一時アプリケーションを追加しています。

フレームワークの残りの部分はフルフィルメントアプリケーションに気付かず、post_saveフックを使用すると、フルフィルメント関連のすべてのコードがOrderモデルから分離されます。

フルフィルメントサービスを削除しても、コアコードは変更する必要がありません。フルフィルメントアプリを削除しました。これで完了です。

では、post_save信号が同じハンドラーを2回起動しないようにするための適切なメソッドはありますか?

32

このソリューションについてどう思いますか?

@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()
27
xakdog

シグナルハンドラで保存する代わりに更新を使用できます

 quersyset.filter(pk = instance.pk).update(....)
78
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.
24
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

Model.save()の代わりにqueryset.update()を使用する必要がありますが、他のことに注意する必要があります。

これを使用するとき、新しいオブジェクトを使用したい場合は、彼のオブジェクトを再度取得する必要があることに注意してください。これは、セルフオブジェクトを変更しないためです。次に例を示します。

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

post_saveraw引数を確認し、saveの代わりにsave_baseを呼び出すこともできます。

4
dragoon

これをチェックしてください...

ここのドキュメントで読むことができるように、各信号には独自の利点がありますが、私はpre_saveおよびpost_save信号に留意するためにいくつかのことを共有したいと思いました。

  • どちらも、モデルの.save()が呼び出されるたびに呼び出されます。つまり、モデルインスタンスを保存すると、信号が送信されます。

  • post_save内のインスタンスでsave()を実行すると、多くの場合、終了しないループが作成されるため、最大再帰深度超過エラーが発生します-.save()を正しく使用しない場合のみ。

  • pre_saveは、save()を呼び出す必要がないため、インスタンスデータだけを変更するのに最適です。これにより、上記の可能性が排除されます。 save()を呼び出す必要がないのは、pre_saveシグナルが文字通り保存される直前を意味するためです。

  • シグナルは、他のシグナルを呼び出したり、(Celeryの場合)遅延タスクを実行したりすることができます。

ソース: https://www.codingforentrepreneurs.com/blog/post-save-vs-pre-save-vs-override-save-method/

よろしく!!

0
Jesús Díaz