传递参数给Django信号-post_save/pre_save

31

我正在Django 1.6中开发一个通知应用程序,并希望传递额外参数给Django信号,例如post_save。 我尝试使用functools中的partial函数,但没有成功。


我在Django 1.6上编写了一个通知应用程序,想要将额外参数传递给Django信号(如post_save)。我尝试过使用functools中的partial函数,但没有成功。
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,我想在我的信号中将其作为默认值传递。

有任何建议吗?

5个回答

34

您可以像这样在模型的自定义保存方法中定义其他参数:

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

在这种情况下,使用getattr()访问post-save接收器中的附加参数(而不是直接访问它)的目的是什么? - Troy
你说得对,调用getattr是不必要的,参数可以直接访问。我已经编辑了答案,谢谢。 - Eugene Soldatov
4
这个答案是错误的。如果想要它起作用,那么在调用超级保存方法之前必须执行 self.my_extra_param = 'hello world' - Ren
这个回答误导了问题的意图。 - undefined

21
你使用 partial 尝试可能不起作用,因为这些接收器默认使用弱引用连接。
根据 Django 文档

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,这个 partial 就不会被垃圾回收。

我的原始答案在下面,采用了不使用 partial 的方法。

在连接 post_save 接收器之前,你可以装饰你的帖子保存函数。

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 中的额外内部参数是为了 需要带有参数的修饰器

如果您想以编程方式执行此操作,则可以使用相同的方法,但请注意,您需要包含 weak=False 以使包装函数不被垃圾回收。

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

或者不使用包装,而是像您最初尝试的那样使用partial调用post_save.connect

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

1
嗨,丹,你的方法看起来很稳健和合乎逻辑。我尝试实现它,但是 Django 信号没有捕获它,你有什么想法吗?这是我的 startup.py 脚本,它在 Django 加载时执行:https://gist.github.com/mo-mughrabi/11517411 - Mo J. Mughrabi
1
嘿,看了一下那段代码,你是正确的。我曾经遇到过一些奇怪的行为,以为只是我的 shell 的问题。看起来如果加上 weak=False 就可以解决了。 - Daniel Rucci
如果所涉及的模型是Django的用户模型(我无法控制其保存功能),该怎么办?我也不能使用https://dev59.com/NGgu5IYBdhLWcg3wpYgj技术,因为我的`post_save`处理程序会立即触发。我现在非常苦恼。 - Csaba Toth
这看起来是一个很好的解决方案!不过我在想,如果我们使用 weak=False 会发生什么垃圾回收呢? - Novarac23

10

我尝试了Eugene Soldatov的答案,但是让我意识到这可以变得更简单:

你可以像这样做:

obj = MyModel.objects.first()
obj.my_extra_param = "hello world"
obj.save() # this will trigger the signal call

然后像 Eugene 的回答中一样设置接收器,它也可以正常工作。

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

无需创建易出错的自定义保存方法。
这是 Django 3.0 中的当前工作方式。我没有尝试过先前的版本。
为什么会发生这种情况?好的文档为您提供了答案:https://docs.djangoproject.com/en/3.2/ref/models/instances/#what-happens-when-you-save

我理解post_save的工作原理。但是当执行obj.my_extra_param = "hello world"时,为什么没有出现属性错误? - Sandeep Balagopal
2
因为它仅在对象上编写,而不是在数据库级别上编写。作为任何Python对象,它能够持有新定义的字段并传递这些数据。每当您执行 obj.refresh_from_db() 时,该值将消失。 - Ren
1
谢谢,@Ren。这应该是一个易于接受的答案,而且有效。 - Maninder Singh
简单而伟大! - OlegТ

4

如果预定义的信号不适合,你总是可以定义自己的信号。

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

1
Django中负责信号的代码在这里定义: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)

但是,包装器如何发送不同的fragment_name值?当使用post_save进行注册时,我需要传递额外的参数,以便在信号处理程序中访问它。您能否详细说明一下您的方法? - Mo J. Mughrabi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接