post_save error Message' has no attribute - django

I'm trying to use signals post_save for the first time. I have read the documents, but still need some advice.
I'm trying to update my model field called 'charge'.
#receiver(post_save, sender=Message)
def my_handler(sender, **kwargs):
if not sender.charge:
sender(charge=sender.length(sender))
sender.save()
However, this gives the error Message' has no attribute 'charge', but charge does exist within message!

sender here is the Message class itself, not the instance that's being saved. The actual instance is passed as the keyword argument instance. Also, with post_save, if you're not careful, you'll get yourself in an infinite loop. Better to use pre_save.
#receiver(pre_save, sender=Message)
def my_handler(sender, **kwargs):
instance = kwargs['instance']
if not instance.charge:
instance.charge = instance.length()
# No need to save, as we're slipping the value in
# before we hit the database.

Related

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

How to get a request in post_save function

Hello I am using Post_save signal for sending a notification from the model ....
But I am getting an error like this "sending_notification() missing 1 required positional argument: 'request'
"
#receiver(post_save, sender=Plan)
def sending_notification(request, sender, instance,created, **kwargs):
if created:
notify.send(request.user, recipient = request.user, verb = "Some messages")
You cannot access request there, Remove request from signal params.
#receiver(post_save, sender=Plan)
def sending_notification(sender, instance, created, **kwargs):
if created:
pass
I see what you are trying to achieve here, you are using django-notifications-hq to send a notification to users, you can do this in the view itself as request won't be available in model signals.
https://stackoverflow.com/a/4716440/8105570

post_delete/pre_delete signals not firing for specific sender

I´ve got a model "Comment" and a signal to take actions when a comment is deleted.
The signal executes when deleting the comment in the admin, but not when deleted through django-rest-framework.
#receiver(post_delete, sender=Comment, dispatch_uid=str(uuid.uuid1())) # I tried also removing dispatch_uid
def comment_post_delete(sender, instance, *args, **kwargs):
I´m not really sure if this is related to django-rest-framework but that´s how my app works.
Other thing to note is that many other signals are working just fine.
All the signals are declared in a separate file signals.py and I import it at the end of models.py with a simple import signals
The only difference with other delete operations is that I´m overriding the "destroy" method of the viewset:
class CommentViewSet(mixins.CreateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
serializer_class = CommentSerializer
def destroy(self, request, *args, **kwargs):
# only the comment author or the media owner are allowed to delete
instance = self.get_object()
if request.user != instance.user and request.user != instance.media.owner:
error = {'detail': 'No tienes permiso para borrar este comentario'}
return Response(data=error, status=status.HTTP_403_FORBIDDEN)
return super(CommentViewSet, self).destroy(request, args, kwargs)
post_delete and pre_delete will not be fired if the sender parameter does not match the model you are expecting.
To check the sender, create a receiver without sender parameter:
#receiver(post_delete)
def comment_post_delete(sender, instance, *args, **kwargs):
if sender == Comment:
# do something
Why could a signal get dispatched with a different model if the model being saved was "Comment"?
This can happen when django automatically set a deferred model, so when I was expecting a "Comment" I was getting something like "Comment_deferred_somefield".
Django automatic deferring can happen for example when the query is using Model.objects.only('field1', 'field2', 'etc') and there are some missing fields in the only() method

Django: passing variables from pre_save to post_save signals

I use the pre_save and post_save signals to send analytics to Mixpanel. I prefer to keep this separated from my model's save method.
Is there a way to save the old values of an instance when the pre_save signal occurs, and then check the new values against them on post_save?
My code looks like this:
#receiver(pre_save, sender=Activity)
def send_user_profile_analytics(sender, **kwargs):
activity_completed_old_value = kwargs['instance'].is_completed
# store this value somewhere?
#receiver(post_save, sender=Activity)
def send_user_profile_analytics(sender, **kwargs):
if kwargs['instance'].is_completed != activity_completed_old_value:
# send analytics
For me it seems more robust to use post_save to send the analytics than pre_save, but at that point I can't see what has changed in the model instance. I would like to prevent using globals or implementing something in my model's save function.
You can store them as instance attributes.
#receiver(pre_save, sender=Activity)
def send_user_profile_analytics(sender, **kwargs):
instance = kwargs['instance']
instance._activity_completed_old_value = instance.is_completed
#receiver(post_save, sender=Activity)
def send_user_profile_analytics(sender, **kwargs):
instance = kwargs['instance']
if instance.is_completed != instance._activity_completed_old_value:
# send analytics
In this way you "send analytics" only if is_completed changes during save (that means that save doesn't just store the value but makes some further elaboration).
If you want to perform an action when a field is changed during instance life-time (that is from its creation till the save) you should store the initial value during post_init (and not pre_save).

django signals, how to use "instance"

I am trying to create a system which enables user to upload a zipfile, and then extract it using post_save signal.
class Project:
....
file_zip=FileField(upload_to='projects/%Y/%m/%d')
#receiver(post_save, sender=Project)
def unzip_and_process(sender, **kwargs):
#project_zip = FieldFile.open(file_zip, mode='rb')
file_path = sender.instance.file_zip.path
with zipfile.ZipFile(file_path, 'r') as project_zip:
project_zip.extractall(re.search('[^\s]+(?=\.zip)', file_path).group(0))
project_zip.close()
unzip_and_process method works fine when correct file paths are provided(in this case, i need to provide instance.file_zip.path. However, I couldn't get/set the instance with the signals. Django documentation about signals is not clear and have no examples. So, what do I do?
Actually, Django's documentation about signals is very clear and does contain examples.
In your case, the post_save signals sends the following arguments: sender (the model class), instance (the instance of class sender), created, raw, and using. If you need to access instance, you can access it using kwargs['instance'] in your example or, better, change your callback function to accept the argument:
#receiver(post_save, sender=Project)
def unzip_and_process(sender, instance, created, raw, using, **kwargs):
# Now *instance* is the instance you want
# ...
This worked for me when connecting Django Signals:
Here is the models.py:
class MyModel(models.Model):
name = models.CharField(max_length=100)
And the Signal that access it post_save:
#receiver(post_save, sender=MyModel)
def print_name(sender, instance, **kwargs):
print '%s' % instance.name