83

So I read the Django source code (post 1.5) that you can now register multiple multiple signals to a receiver function:

def receiver(signal, **kwargs):
    """
    A decorator for connecting receivers to signals. Used by passing in the
    signal (or list of signals) and keyword arguments to connect::

        @receiver(post_save, sender=MyModel)
        def signal_receiver(sender, **kwargs):
            ...

        @receiver([post_save, post_delete], sender=MyModel)
        def signals_receiver(sender, **kwargs):
            ...

    """
    ... implementation code...

However, I want to register multiple post_save signals from different senders to the same function. Right now, I just call

post_save.connect(fn_name, model_name)

for each model that I have. Is there a better way to do this with the new Django 1.5 @receiver decorator capability?

fangsterr
  • 3,670
  • 4
  • 37
  • 54

4 Answers4

234

You can do that using the @receiver decorator:

from django.dispatch import receiver

@receiver(post_save, sender=Model1)
@receiver(post_save, sender=Model2)
@receiver(post_save, sender=Model3)
def my_signal_handle(sender , **kwargs)
    # some code here
gdvalderrama
  • 713
  • 1
  • 17
  • 26
hubert
  • 2,997
  • 3
  • 20
  • 26
42

Per the Django documentation on receivers, receivers by default do not need to be connected to a specific sender. So what you're describing is default Django functionality.

In other words, to do this using the @receiver decorator you simply don't specify a sender in the decorator. For example:

@receiver(post_save) # instead of @receiver(post_save, sender=Rebel)
def set_winner(sender, instance=None, created=False, **kwargs):
    list_of_models = ('Rebel', 'Stormtrooper', 'Battleground')
    if sender.__name__ in list_of_models: # this is the dynamic part you want
        if created: # only run when object is first created
            ... set the winner ...

This assumes models that look like:

class Rebel(models.Model):
    ...

class Stormtrooper(models.Model):
    ...

class Battleground(models.Model):
    ...
Tyler Hayes
  • 1,338
  • 13
  • 11
  • 15
    won't this receiver also get called on *every* other model's post_save? that could be a lot of calls... – nivcaner Nov 30 '15 at 14:07
  • @nivcaner yes that's what the question was asking to do: use one `post_save` to listen for changes on multiple models – Tyler Hayes Dec 01 '15 at 17:17
  • 19
    @TylerHayes multiple models is not the same as all models – DylanYoung Dec 13 '16 at 19:46
  • @DylanYoung True. The answer is marked as accepted. If the question-asker feels this is no longer the best answer for their issue, they're welcome to change it. – Tyler Hayes Dec 13 '16 at 22:43
  • 2
    If you made the list of models a set like `{'rebel', 'stormtrooper'}`, the `in` statement would be O(1) instead of O(N) and this is then no more complex than the multiple registrations in the now-accepted answer; with the added benefit that the `list_of_models` can be defined in settings or computed. So a thumbs up to this one, thank you @Tyler Hayes! – thclark May 27 '21 at 13:19
  • this cause transaction error when you want to create and query or update modules even when u prevent the infit loop by if statment – Ali Husham Jun 25 '21 at 13:25
21

You can skip model_name and you will connect to all models post_save. Then you can filter if you are in right model in the handler:

post_save.connect(foo)

def foo(sender, **kwargs):
    if sender not in [FooModel, BarModel]:
        return
    ... actual code ...

or you can filter based on field in model:

def foo(sender, **kwargs):
    if not getattr(sender, 'process_by_foo', False):
        return
    ... actual code ...
Tomasz Wysocki
  • 11,170
  • 6
  • 47
  • 62
12
def receiver_with_multiple_senders(signal, senders, **kwargs):
    """
    Based on django.dispatch.dispatcher.receiver

    Allows multiple senders so we can avoid using a stack of
    regular receiver decorators with one sender each.
    """

    def decorator(receiver_func):
        for sender in senders:
            if isinstance(signal, (list, tuple)):
                for s in signal:
                    s.connect(receiver_func, sender=sender, **kwargs)
            else:
                signal.connect(receiver_func, sender=sender, **kwargs)

        return receiver_func

    return decorator
C S
  • 1,363
  • 1
  • 17
  • 26