Django signals calling asynchronous task without celery - django

I'm doing in signal:
#receiver(post_save, sender=User)
def send_something(sender, instance, created, **kwargs):
if created:
loop = asyncio.get_event_loop()
async_function = sync_to_async(send_id, thread_sensitive=True)
loop.create_task(async_function(instance.id))
my another function is:
def send_id(user_id):
print(user_id)
But function is not calling

Related

Creating a handler for user_activated signal

I want to receive a signal when user is activated (i.e. when auth_user.is_active becomes 1). I only want to receive this signal once, the very first time that the user is activated.
I have used the answer given to this question, and it works for me:
#receiver(pre_save, sender=User, dispatch_uid='get_active_user_once')
def new_user_activation_handler(sender, instance, **kwargs):
if instance.is_active and User.objects.filter(pk=instance.pk, is_active=False).exists():
logger.info('user is activated')
However this seems to be a customized signal, I believe django has a built-in user_activated signal. I have tried using the built-in signal but it does not fire:
signals.py:
from django_registration.signals import user_activated
#receiver(user_activated, sender=User, dispatch_uid='django_registration.signals.user_activated')
def new_user_activation_handler(sender, instance, **kwargs):
logger.info('user is activated')
Also this is what I have in apps.py:
class MyClassConfig(AppConfig):
name = 'myclass'
def ready(self):
logger.info('ready...')
import myclass.signals # wire up signals ?
Not sure why this signal is not being fired?
In order to get the above code running, I had to install django-registration package.
All the examples that I have seen have:
from registration.signals import user_activated
But in my case I have to use the a diferent namespace:
from django_registration.signals import user_activated
Not sure why...
You have wrong sender. Please see:
Replace:
#receiver(user_activated, sender=User, dispatch_uid='django_registration.signals.user_activated')
def new_user_activation_handler(sender, instance, **kwargs):
logger.info('user is activated')
With:
from django_registration.backends.activation.views import ActivationView
#receiver(user_activated, sender=ActivationView, dispatch_uid='django_registration.signals.user_activated')
def new_user_activation_handler(sender, instance, **kwargs):
logger.info('user is activated')

Django two same signal types

I don't understand how 2 signals executed together relate to each other. For example consider the following:
from django.db import models
from django.contrib.auth.models import User
from PIL import Image
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
def __str__(self):
return f'{self.user.username} Profile'
# Overriding save method-image resizing
def save(self, **kwargs):
super().save(kwargs)
img = Image.open(self.image.path)
if img.height > 300 or img.width > 300:
output_size = (300, 300)
img.thumbnail(output_size)
img.save(self.image.path) # save() of parent class
#Signals when a user is registered his profile is automatically updated
#receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
Profile(user=instance) #could do Profile.objects.create(user=instance) instead to replace the 2 lines
instance.profile.save() #reverse query
I understand the following:
create_profile: is my receiver function. It will do something when .save() method takes place, in our case will create a Profile instance and save it.
sender: is the Model that is actually signalling this to take place. So in our case, if we want a signal to take place when the save method is executed on User model then the sender would be the User.
post_save is the signal
Regarding my question now. I have seen the above function broken down into the two following functions.
#receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
Profile(user=instance)
#receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
instance.profile.save() #reverse query
I am aware it is pointless since I could do what I did but I don't understand how does my save_profile function 'remembers' the Profile instance made on create_profile?
As far as I can tell, save_profile is a separate function that has no reference to create_profile function (example by create_profile or so on the save_profile body).
I am assuming when a user instance is saved the create_profile function is executed it makes a profile object then the save_profile function is executed and for same user instance the profile object is saved,
but I still don't understand how it knows I refer to same profile made by that instance.
Why you not state it is example from Corey tutorial ?
Corey Tutorial, part 8, and if I not mistaken he didnt have to create two separate signals, because one was enough ..
To your question:
The functions are:
#receiver(post_save, sender=User)
def create_profile_handler(sender, instance, created, **kwargs):
#Run each time a new user is created, and attach to the user a profile !
# sender - The model class (User)
# instance - The actual instance being saved. (User)
# created - A boolean. 'True' if a new record was created.
if created:
Profile.objects.create(user=instance)
#'create' - initialize and save a profile and attach it to the User
# Now, lets save the profile each time the user object is been saved.
#receiver(post_save, sender=User)
def save_profile_handler(sender, instance, **kwargs):
instance.profile.save() # User.profile.save() - saves the profile
we know from the Django Docs, that create() method include save() operation in it.
So not only that the second signal is redundant (because we can do its operations in the first signal), further more - the save is a bit redundant it self (because create calls save).

Execute delete() within save() in Django

I'm working on a Django/Wagtail project. I'm trying to build a very customized feature that requires an object to be deleted when hitting the Save button when certain conditions are met.
I override the Save method:
def save(self, *args, **kwargs):
if condition:
return super(ArticleTag, self).delete()
else:
return super(ArticleTag, self).save(*args, **kwargs)
I know this looks very odd and completely anti-adviseable, but it is exactly the behavior I'm trying to achieve.
Is there a better or "correct" way to do this?
Are there other steps to exactly reproduce the behavior as if the user had hit Delete directly?
If the object already exists in your db, you can do as follows:
def save(self, *args, **kwargs):
if condition:
self.delete() # you do not need neither to return the deleted object nor to call the super method.
else:
return super(ArticleTag, self).save(*args, **kwargs)
Using signals receivers
signals.py
from django.dispatch import receiver
from django.db.models.signals import post_save
__all__ = ['check_delete_condition']
#receiver(post_save, sender="yourapp.yourmodel")
def check_delete_condition(instance, raw, created, using, updatefields, **kwargs):
if condition:
instance.delete()
in your apps.py you can't put the signals import
from .signals import *
#rest of code

Consolidating multiple post_save signals with one receiver

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?
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
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):
...
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 ...
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

Avoid recursive save() when using celery to update Django model fields

I'm overriding a model's save() method to call an asynchronous task with Celery. That task also saves the model, and so I end up with a recursive situation where the Celery task gets called repeatedly. Here's the code:
Model's save method:
def save(self, *args, **kwargs):
super(Route, self).save(*args, **kwargs)
from .tasks import get_elevation_data
get_elevation_data.delay(self)
get_elevation_data task:
from celery.decorators import task
#task()
def get_elevation_data(route):
...
route.elevation_data = results
route.save()
How can I avoid this recursion?
Add a keyword argument that tells save not to recurse:
def save(self, elevation_data=True, *args, **kwargs):
super(Route, self).save(*args, **kwargs)
if elevation_data:
from .tasks import get_elevation_data
get_elevation_data.delay(self)
And then:
from celery.decorators import task
#task()
def get_elevation_data(route):
...
route.elevation_data = results
route.save(elevation_data=False)