Django Signal via Decorator on Model Method?

|▌冷眼眸甩不掉的悲伤 提交于 2020-01-01 09:22:52

问题


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

I'd like to use the decorator like so:

class ModelA(Model):

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

The decorator is:

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 guess the problem is that @classmethod returns a class method object which is not callable. I don't really understand how classmethod works under the hood, but I surmise from this reference page that the class method object is not translated into a callable until it is accessed from the class, e.g., ModelA.observe_model_b_saved. Is there any way that I can both (1) define my method as a class or instance method on a model, and (2) connect it to a signal using a decorator directly on the method definition? Thanks!


回答1:


It's not clear from your example code, so I'd be asking if the signal listener actually has to be a @classmethod? I.e. Will a regular method do (and then use self.__class__ if you still need to access the class itself)? Does it need to be a method at all (can you just use a function)?

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

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:


Could you make it a @staticmethod instead? That way, you can just swap 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'd have to refer to the class by full name instead of getting passed the cls argument, but this would allow you to keep a similar code organization.




回答3:


Based off of Matt's answer, the @staticmethod trick worked for me. You can use a string to reference a model non-concretely.

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


来源:https://stackoverflow.com/questions/2310676/django-signal-via-decorator-on-model-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!