djangosignalsargument-passing

Passing arguments django signals - post_save/pre_save


I am working on a notification app in Django 1.6 and I want to pass additional arguments to Django signals such as post_save. I tried to use partial from functools but no luck.

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

notify function has a keyword argument fragment_name which I want to pass as default in my signals.

Any suggestions?


Solution

  • Your attempt with partial isn't working because by default these receivers are connected using a weak reference.

    According to the Django docs:

    Django stores signal handlers as weak references by default, so if your handler is a local function, it may be garbage collected. To prevent this, pass weak=False when you call the signal’s connect().

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

    Include weak=False and this partial won't be garbage collected.

    My original answer is below and took an approach that wasn't using partial.

    You could decorate your post save function prior to connecting it with the post_save receiver.

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

    The extra inner in extra_args is for decorators that take parameters.

    If you want to do this programmatically this works the same way but note that you need to include weak=False to have the wrapped function not be garbage collected.

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

    Or without wrapping, but calling post_save.connect like your original attempt with partial

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