Django Signal via Decorator according to model method?

I am trying to do something like these proposed signal decoders . In addition to having a decorator that connects a decorated method to a signal (with the signal sender as an argument to the decorator), I would like to use a decorator for class methods.

I would like to use a decorator as follows:

class ModelA(Model): @connect.post_save(ModelB) @classmethod def observe_model_b_saved(cls, sender, instance, created, **kwargs): # do some stuff pass 

Decorator:

 from django.db.models import signals def post_save(sender): def decorator(view): signals.post_save.connect(sender=sender, receiver=view) return view return decorator 

The error I get when I do this is:

  File "/Library/Python/2.6/site-packages//lib/python2.6/site-packages/django/dispatch/dispatcher.py", line 78, in connect
 AssertionError: Signal receivers must be callable.

I think the problem is that @classmethod returns a class method object that cannot be called. I really don’t understand how the classmethod works under the hood, but I can guess from this man page that the class method object has not been converted to a callable before accessing it from the class, for example ModelA.observe_model_b_saved . Is there a way that I can (1) define my method as a class or instance method on a model, and (2) connect it to a signal using a decorator directly in the method definition? Thanks!

+6
python django decorator django-models django-signals
source share
3 answers

This is not clear from your sample code, so I would ask if the signal listener should really be @classmethod ? That is, will the regular method do (and then use self.__class__ if you still need to access the class itself)? Do I need to be a method at all (can you just use the function)?

Another option might be to use the second method to listen for the signal and delegate the @classmethod call:

 class ModelA(Model): @classmethod def do_observe_model_b_saved(cls, sender, instance, created, **kwargs): # do some stuff pass @connect.post_save(ModelB) def observe_model_b_saved(self, sender, instance, created, **kwargs): self.do_observe_model_b_saved(sender, instance, created, **kwargs) 
+2
source share

Could you do this instead of @staticmethod? Thus, you can simply change the order of the decorators.

 class ModelA(Model): @staticmethod @connect.post_save(ModelB) def observe_model_b_saved(sender, instance, created, **kwargs): # do some stuff pass 

You will need to refer to the class by name, and not get the cls argument, but this will allow you to maintain a similar organization of the code.

+1
source share

Based on Matt's answer, the @staticmethod trick worked for me. You can use the string to refer to a specific model.

 class Foo(Model): @staticmethod @receiver(models.signals.post_save, sender='someappname.Foo') def post_save(sender, instance, created, **kwargs): print 'IN POST SAVE', sender, instance.id, created 
0
source share

All Articles