web-dev-qa-db-ja.com

引数を渡すDjangoシグナル-post_save / pre_save

Django 1.6で通知アプリに取り組んでおり、post_saveなどのDjangoシグナルに追加の引数を渡したい。使用しようとしたfunctoolsから部分的ですが、運がありません。

from functools import partial
post_save.connect(
    receiver=partial(notify,
        fragment_name="categories_index"),
            sender=nt.get_model(),
            dispatch_uid=nt.sender
    )

notify関数にはキーワード引数fragment_nameがあり、これをシグナルのデフォルトとして渡します。

助言がありますか?

21
Mo J. Mughrabi

デフォルトでは、これらのレシーバーは弱い参照を使用して接続されているため、partialでの試行は機能しません。

Django docs によると:

Djangoはデフォルトでシグナルハンドラーを弱参照として保存するため、ハンドラーがローカル関数の場合、ガベージコレクションされる可能性があります。これを防ぐには、シグナルのconnect()を呼び出すときにweak = Falseを渡します。

from functools import partial
post_save.connect(
    receiver=partial(notify,
        fragment_name="categories_index"),
            sender=nt.get_model(),
            dispatch_uid=nt.sender,
            weak=False
    )

Weak = Falseを含めると、このパーシャルはガベージコレクションされません。

私の最初の答えは以下のとおりで、部分的なものを使用していないアプローチを採用しました。

Post_saveレシーバーに接続する前に、postsave関数を装飾することができます。

from Django.dispatch import receiver
from Django.db.models.signals import pre_save, post_save, post_delete

def extra_args(fragment_name, *args, **kwargs):
    def inner1(f, *args, **kwargs):
        def inner2(sender, instance, **kwargs):
            f(sender, instance, fragment_name=fragment_name, **kwargs)
        return inner2
    return inner1

@receiver(post_save, sender=ExampleModel)
@extra_args(fragment_name="categories_index")
def my_post_save(sender, instance, fragment_name, **kwargs):
    print "fragment_name : ", fragment_name
    #rest of post save...

Extra_argsのextrainnerは、 パラメーターを受け取るデコレーター 用です。

これをプログラムで実行する場合、これは同じように機能しますが、ラップされた関数がガベージコレクションされないようにするには、weak=Falseを含める必要があることに注意してください。

receiver(post_save, sender=aSenderClass, weak=False)(extra_args(fragment_name="meep")(my_post_save))

または、折り返すことなく、部分的に元の試みのようにpost_save.connectを呼び出す

post_save.connect(extra_args(fragment_name="meepConnect")(my_post_save), sender=Author, weak=False)
13
Daniel Rucci

次のように、モデルのカスタム保存メソッドで追加の引数を定義できます。

class MyModel(models.Model):
    ....

    def save(self, *args, **kwargs):
        super(MyModel, self).save(*args, **kwargs)
        self.my_extra_param = 'hello world'

そして、post_saveシグナルレシーバーのインスタンスを介してこの追加の引数にアクセスします。

@receiver(post_save, sender=MyModel)
def process_my_param(sender, instance, *args, **kwargs):
    my_extra_param = instance.my_extra_param
23
Eugene Soldatov

事前定義された信号が適切でない場合は、いつでも独自の信号を定義できます。

_import Django.dispatch

custom_post_save = Django.dispatch.Signal(providing_args=[
    "sender", "instance", "created", "raw", "using", "update_fields", "fragment_name"
])
_

次に、モデルでsave()メソッドをオーバーライドする必要があります。

_from Django.db import router

class YourModel(Model):

    # Your fields and methods

    def save(self, force_insert=False, force_update=False, using=None,
         update_fields=None):
         custom_signal_kwargs = {
             "sender": self.__class__,
             "instance": self,
             "created": self.pk is None,
             "raw": False, # As docs say, it's True only for fixture loading
             "using": using or router.db_for_write(self.__class__, instance=self),
             "update_fields": update_fields,
             "fragment_name": "categories_index" # The thing you want
         }
         super(YourModel, self).save(force_insert=False, force_update=False, using=None,
             update_fields=None)
         custom_post_save.send(**custom_signal_kwargs) # Send custom signal
_

これで、このカスタム信号をnotify(...)レシーバーに接続するだけで、kwargsで_fragment_name_を取得できます。

3
ElmoVanKielmo

Django Signalsを担当するコードはここで定義されています https://github.com/Django/django/blob/master/Django/dispatch/dispatcher.py 。レシーバーをどのように検査するかをご覧ください。問題はそこにあると思います。おそらく、シグナルに必要な引数を尊重するだけでなく、fragment_nameの値も設定するラッパー関数が必要です。

def fragment_receiver(sender, **kwargs)
    return notify(sender, fragment_name="categories_index", **kwargs)
1
Sean Perry