Django - how to get all models bound to post_save receivers? - python

I'm working on a script which finds all Django pre_save and post_save signals and comments overwritten save methods so programmer is aware of all signals affecting the workflow.
For Example
There are two receivers:
#receiver(pre_save, sender=MyModel)
def sig_mymodel_pre_save(instance, sender, **kwargs):
...
#receiver(post_save, sender=MyModel)
def sig_mymodel_post_save(instance, sender, created, **kwargs):
...
And model MyModel:
class MyModel(..):
...
def save(self,*args,**kwargs):
...
super().save(...)
....
I want the script to modify MyModel code to look like this:
class MyModel(..):
...
def save(self,*args,**kwargs):
...
# SIGNAL pre_save | myapp.models.sig_mymodel_pre_save
super().save(...)
# SIGNAL post_save | myapp.models.sig_mymodel_post_save
....
So the first thing I'm going to do is to list all receivers of post_save and pre_save signals:
def get_signals() -> {}:
result = {}
for signal in [pre_save, post_save]:
result[signal] = signal.receivers
return result
def comment_signals_to_save_methods():
for signal, receivers in get_receivers():
for receiver in receivers:
models = ???
And here is the problem - I can't get models for the receiver. There is no such method or attribute.
Do you know how to do it?

Related

django write signal handler as class (write class based django signal handlers)

From django docs it is clear that we can write signals handlers as function.
from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel
#receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
...
Is it possible to write the signal handlers as classes?
If yes HOW?
YourClass(Inheritance):
#receiver(pre_save, sender=MyModel)
def __call__(self, sender, **kwargs):
#your code
return
And then when you want to use this class you would import it and write:
my_handler = YourClass()
So you then could include an instantiated version in your settings file or wherever you need it.
Something like this should be possible. But why may I ask?

Debug Django signal handlers

I am trying to use signals for the first time. I have a model, UserAlias, and I want to execute some code after a UserAlias record is created.
UserAlias is defined in aliases/models.py
I created a aliases/signals/handlers.py. Here is that file's contents:
from django.db.models.signals import post_save, post_init, post_delete
from django.dispatch import receiver
from aliases.models import UserAlias
#receiver(post_init, sender=UserAlias)
def post_init_handler(sender, **kwargs):
print('hello there from a signal')
#receiver(post_save, sender=UserAlias)
def post_save_handler(sender, **kwargs):
print('hello there from a signal')
#receiver(post_delete, sender=UserAlias)
def post_delete_handler(sender, **kwargs):
print('hello there from a signal')
But when I execute:
from aliases.models import *
newalias = UserAlias.objects.create(...omitted...)
I do not see any of my debug print statements execute.
What am I missing here?
UPDATED:
I moved the definitions of my post_init_handler, post_save_handler and post_delete_handler to my aliases/models.py file after the declaration of my UserAlias class.
Also my post_init_handler now looks like this:
#receiver(post_init, sender=UserAlias)
def post_init_handler(sender, instance, **kwargs):
print(f'hello there from a signal {sender} {instance}')
I tried declaring it as Paaksing suggested ...
#receiver(post_init, sender=UserAlias)
def post_init_handler(sender, instance, created, **kwargs):
print(f'hello there from a signal {created} {sender} {instance}')
But I would get this error:
TypeError: post_init_handler() missing 1 required positional argument: 'created'
UPDATE II:
The post_init handler does not have a created parameter. The created parameter goes with the post_save handlers. Like this:
#receiver(post_save, sender=UserAlias)
def post_save_handler(sender, instance, created, **kwargs):
if created:
print(f'hello from post_save_handler( {sender}, {instance}, {created})')
You missed a created attr.
Consider adding instance if working with models that are user-like so you can manipulate them on the signals.
#receiver(post_save, sender=UserAlias)
def post_save_handler(sender, instance, created, **kwargs):
if created:
print('hello there from a signal')
Update: As Red Cricket said, the post_init does not have a created instance, but works with post_save as expected.
In the django doc shows all the instances that you can work with each signal: https://docs.djangoproject.com/en/3.0/ref/signals/
You created your signals in a seperate module (which is good). You also need to import the module at the right direction to use them.
The Django documentation advices to put the signals inside your app config file.
You could do it for example like this.
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'myproject.app'
def ready(self) -> None:
import myproject.app.aliases/signals/handlers # noqa

Sort django signal's receivers

Django calls receiver methods in its own way. Is there any way that we can sort the receivers of Django signal? I didn't find anything related to it in official Django documentation.
Unless I'm mistaken, there isn't any reason to have more than one receiver for the same signal and sender. It is possible but I don't believe it is the right way to approach it and you will not have explicit control over the calling sequence of each.
If, for example, you have a set of duplicate signals (that do different things) like so:
from django.db.models import signals
from django.dispatch import receiver
from django.contrib.auth import get_user_model
User = get_user_model()
#receiver(signals.pre_save, sender=User)
def signal_receiver_1(*args, **kwargs):
pass
#receiver(signals.pre_save, sender=User)
def signal_receiver_2(*args, **kwargs):
pass
#receiver(signals.pre_save, sender=User)
def signal_receiver_3(*args, **kwargs):
pass
That would work but the order of execution isn't explicitly known. However, you could rewrite it to something like this to explicitly state the order of execution:
from django.db.models import signals
from django.dispatch import receiver
from django.contrib.auth import get_user_model
User = get_user_model()
def signal_receiver_1(*args, **kwargs):
pass
def signal_receiver_2(*args, **kwargs):
pass
def signal_receiver_3(*args, **kwargs):
pass
#receiver(signals.pre_save, sender=User):
def pre_save_signal_receiver_master(*args, **kwargs):
signal_receiver_1(*args, **kwargs)
signal_receiver_2(*args, **kwargs)
signal_receiver_3(*args, **kwargs)
https://docs.djangoproject.com/en/2.2/topics/signals/#listening-to-signals
"All of the signal’s receiver functions are called one at a time, in the order they were registered."
Try:
#receiver(signals.pre_save, sender=User)
def signal_receiver_1(*args, **kwargs):
print('RECEIVER 1')
#receiver(signals.pre_save, sender=User)
def signal_receiver_2(*args, **kwargs):
print('RECEIVER 2')
#receiver(signals.pre_save, sender=User)
def signal_receiver_3(*args, **kwargs):
print('RECEIVER 3')
Output will be:
RECEIVER 1
RECEIVER 2
RECEIVER 3
And then mix it up...

Several Django signal receivers in one module

I have separate python module for signal receivers, it's called signals.py and imported in ready() method of my AppConfig.
In this module I implemented post_save and post_delete signal receivers for specific model and registered them via decorator:
#receiver(post_save, sender=MyModel)
def generate_smth(sender, instance, created, **kwargs):
...
And it works fine.
But, when I added to signals.py receivers of same signals in the same manner, but from different specific models:
#receiver(post_save, sender=AnotherModel)
def generate_smth(sender, instance, created, **kwargs):
...
My functions stopped to receive signals. But if I move receivers into separate python modules mymodel_signals.py and anothermodel_signals.py and import both modules in ready() then all of them works again.
Why it isn't possible to keep receivers in one module?
#receiver(post_save, sender=MyModel)
#receiver(post_save, sender=AnotherModel)
def generate_smth(sender, instance, created, **kwargs):
if sender.__name__ = 'MyModel':
# Bar
else:
# Foo
Do you want both functions to have the same behaviour? If yes, you can do:
def do_smth(sender, instance, created, **kwargs):
...
#receiver(post_save, sender=MyModel)
def generate_smth(sender, instance, created, **kwargs):
do_smth(sender, instance, created, **kwargs)
#receiver(post_save, sender=AnotherModel)
def generate_another_smth(sender, instance, created, **kwargs):
do_smth(sender, instance, created, **kwargs)

django post_save signals on update

I am trying to set up some post_save receivers similar to the following:
#receiver(post_save, sender=Game, dispatch_uid='game_updated')
def game_updated(sender, **kwargs):
'''DO SOME STUFF HERE'''
MyPick.objects.filter(week=game.week, team=game.home_team).update(result=home_result)
MyPick.objects.filter(week=game.week, team=game.away_team).update(result=away_result)
#receiver(post_save, sender=MyPick, dispatch_uid='user_pick_updated')
def update_standings(sender, **kwargs):
'''DO STUFF'''
The first receiver is getting called correctly after an update on the Game object, however the calls to update on the MyPick object are not causing the second receiver to be called. Does the post_save signal not work on update or am I missing something else here?
update() is converted directly to an SQL statement; it doesn't call save() on the model instances, and so the pre_save and post_save signals aren't emitted. If you want your signal receivers to be called, you should loop over the queryset, and for each model instance, make your changes and call save() yourself.
Just one more thing to #Ismali Badawi's answer.
This calls post_save
user = User.objects.get(id=1)
user.username='edited_username'
user.save()
This does not call post_save
User.objects.filter(id=1).update(username='edited_username')
In the code,
from django.db.models.signals import post_save
#receiver(post_save, sender=User)
def do_something_when_user_updated(sender, instance, created, **kwargs):
if not created:
# User object updated
user_obj = instance
pass

Categories

Resources