Django two same signal types - django

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

Related

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

post_save error Message' has no attribute

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.

OneToOneField and Deleting

I have the following model:
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User)
# ...
def __unicode__(self):
return u'%s %s' % (self.user.first_name, self.user.last_name)
When using the Django admin to delete the user, the profile gets deleted as well, which is what I want. However, when using the Django admin to delete the profile, the user does not get deleted, which is not what I want. How can I make it so that deleting the profile will also delete the user?
Since Profile links to User, it is the dependent model in the relationship. Therefore when you delete a user, it deletes all dependent models. However when you delete a profile, since User does not depend on profile, it is not removed.
Unfortunately, according to on_delete Django docs, there is no on_delete rule which deletes the parent relations. In order to do that, you can overwrite the Profile's delete method:
class Profile(models.Model):
# ...
def delete(self, *args, **kwargs):
self.user.delete()
return super(self.__class__, self).delete(*args, **kwargs)
Then when doing:
Profile.objects.get(...).delete()
will also delete the profile's user. However the delete method will not be called when deleting profiles using querysets (which is what is called in Django Admin) since then Django uses SQL DELETE to delete objects in bulk:
Profile.objects.filter(...).delete()
In that case, as recommended by Django docs, you will have to use post_delete signal (docs).
from django.dispatch import receiver
from django.db.models.signals import post_delete
#receiver(post_delete, sender=Profile)
def post_delete_user(sender, instance, *args, **kwargs):
if instance.user: # just in case user is not specified
instance.user.delete()
Use a signal on the Profile's delete method to go and delete the related User:
from django.db.models.signals import post_delete
def delete_related_user(sender, **kwargs):
deleted_profile = kwargs['instance']
deleted_profile.user.delete()
post_delete.connect(delete_related_user, sender=Profile)

Execute code on model creation in Django

I want to execute some code in a Django model when it is first created. After that whenever it is saved I want to execute some other code. The second task can be easily done by overriding the save() method.
How can I do the first task?
Extending sdolan's answer by using receiver decorator:
from django.db import models
from django.dispatch import receiver
class MyModel(models.Model):
pass
#receiver(models.signals.post_save, sender=MyModel)
def execute_after_save(sender, instance, created, *args, **kwargs):
if created:
# code
You can use django signals' post_save:
# models.py
from django.db.models import signals
class MyModel(models.Model):
pass
def my_model_post_save(sender, instance, created, *args, **kwargs):
"""Argument explanation:
sender - The model class. (MyModel)
instance - The actual instance being saved.
created - Boolean; True if a new record was created.
*args, **kwargs - Capture the unneeded `raw` and `using`(1.3) arguments.
"""
if created:
# your code goes here
# django 1.3+
from django.dispatch import dispatcher
dispatcher.connect(my_model_post_save, signal=signals.post_save, sender=MyModel)
# django <1.3
from django.db.models.signals import post_save
post_save.connect(my_model_post_save, sender=MyModel)
field.default
The default value for the field. This can be a value or a callable object. If callable it will be called every time a new object is created.
So, we can use any field callable to do what we want on creation, huh? ;-)

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