What is the difference between the two Django post_save signals - python

I'm confused between why some tutorials and guides write two different post_save signal like below
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
when the profile can be saved by calling the save() method in one approach itself
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
profile = Profile(user=instance)
profile.save()

Related

Add field to django auth user?

I am trying to add a new field to the built in auth user. Here is my code
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from django.contrib.postgres.fields import ArrayField
class Profile(models.Model):
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
newField = ArrayField(models.CharField(max_length=16))
#receiver(post_save, sender=get_user_model())
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=get_user_model())
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
And in my view I have the following:
current_user = User.objects.get(username=request.user)
current_user.save()
And i am getting the error
Exception Type: RelatedObjectDoesNotExist
Exception Value: User has no profile.
Am i doing this wrong? I creates a profile table in the db but that didnt seem to be the issue
Its maybe because you are creating user and triggering both signal functions at a time. So maybe you can try like this:
#receiver(post_save, sender=get_user_model())
def save_user_profile(sender, instance, created, **kwargs):
if not created:
instance.profile.save()
Or better,combine both signals into one:
#receiver(post_save, sender=get_user_model())
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
else:
instance.profile.save()
The reason is your are defining two signals with one sender. So when the user is saved, both signals will be triggered, But the second is faster and will execute right away. And because for that user no profile is created, it throws an error. Because the profile object for that user by first signal isn't created yet.
#receiver(post_save, sender=get_user_model())
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
Note no need to perform instance.profile.save() here.

Django Profile Update on existing profile

I have the following signal for creating a profile when a new user registers and then sends an email to the admin informing them of the new user
#receiver(post_save, sender=User)
def create_user_profile(sender, **kwargs):
'''Create a profile for a new user'''
if kwargs['created']:
Profile.objects.create(user=kwargs['instance'])
#receiver(post_save, sender=User)
def notify_admin(sender, instance, created, **kwargs):
'''Notify the administrator that a new user has been added.'''
if created:
subject = 'New Registration created'
message = 'A new candidate %s has registered with the site' % instance.email
from_addr = 'no-reply#example.com'
recipient_list = ('admin#example.com', )
send_mail(subject, message, from_addr, recipient_list)
I am looking to also notify the admin when a profile is updated but am struggling to come up with a solution.
Any ideas?
Method -1
As #Ralf said, use a different signal for Profile model and check the created flag
#receiver(post_save, sender=Profile)
def profile_update_signal(sender, instance, created, **kwargs):
# Profile update signal
if not created:
# do something here <<<<<<
send_mail()
Method - 2
Override the save() method of Profile model
class Profile(models.Model):
# your fields
def save(self, *args, **kwargs):
if self.pk: # not a new object
# do something here <<<<<<
send_mail()
super(Profile, self).save(*args, **kwargs)

Attribute error in extending django user model?

I have done this before and got it working fine. Is this because i'm using django2.0?
models.py
class Profile(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
....
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
I get the error AttributeError: 'User' object has no attribute 'profile'. I don't get it?
To access all reverse related by ForeignKey profiles you need to use profile_set. If you need to obtain and save first related object try this:
def save_user_profile(sender, instance, **kwargs):
instance.profile_set.first().save()
But in this situation I believe you'd better use OneToOneField:
user = models.OneToOneField(User, on_delete=models.CASCADE)
which allows to create only one related profile and also will allow to this syntax:
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()

Extending a model in Django

I am trying to create a custom user model. This model has to extend another model and also link to built-in django User. Currently I have:
class Entity(models.Model):
name = models.CharField(max_length=256, null=True, blank=True)
class Profile(Entity):
user = models.OneToOneField(User, on_delete=models.CASCADE)
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
But when I try to create a user I get an error: UNIQUE constraint failed: project_profile.entity_ptr_id.
Do I need to add another function to create entity first? Or should I structure my models in another way?
if you creating the Profile instance with User it already saved in the database if you use create method. you don't need to save seperatly.
ie the following signls is enough for saving and creating user profile instance
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
The OneToOne relation means that you can have only one profile per user.
Modify the code to
class Profile(Entity):
user = models.ForeignKey(User, on_delete=models.CASCADE)

Is it possible to set the implicit OneToOne key directly when using multi table inheritance in Django?

I'm trying to extend a library model through multi-table inheritance and this is what I ended up with:
class CompetitionMedia(InstagramMedia):
visible = models.BooleanField(default=True)
most_creative = models.BooleanField(default=False)
#receiver(post_save, sender=InstagramMedia, dispatch_uid='create_competition_media')
def create_competition_media(sender, instance, created, **kwargs):
competition_media = CompetitionMedia()
competition_media.instagrammedia = instance
competition_media.save() # fails
#receiver(post_save, sender=InstagramMedia, dispatch_uid='create_competition_media2')
def create_competition_media2(sender, instance, created, **kwargs):
CompetitionMedia.objects.create(instagrammedia=instance) # Fails
Is it possible to do this?
As I understand it, you want to get something like this:
class CompetitionMedia(models.Model):
instagrammedia = models.OneToOneField(InstagramMedia, primary_key=True)
visible = models.BooleanField(default=True)
most_creative = models.BooleanField(default=False)
#receiver(post_save, sender=InstagramMedia, dispatch_uid='create_competition_media2')
def create_competition_media(sender, instance, created, **kwargs):
if created:
CompetitionMedia.objects.create(instagrammedia=instance)
I'm apparently supposed to set parent_ptr, like:
#receiver(post_save, sender=InstagramMedia, dispatch_uid='create_competition_media')
def create_competition_media2(sender, instance, created, **kwargs):
if created:
CompetitionMedia.objects.create(instagrammedia_ptr=instance)
However there's an issue where if you create and save the parent first and then try to create a child, the child will override te parent even on fields you haven't set in the child. As described in https://code.djangoproject.com/ticket/11618 and https://code.djangoproject.com/ticket/7623, so I would avoid it just because it's not very obvious that would happen.
If someone really wants to go down that route you'd need to do:
#receiver(post_save, sender=InstagramMedia, dispatch_uid='create_competition_media')
def create_competition_media2(sender, instance, created, **kwargs):
if created:
CompetitionMedia.objects.create(instagrammedia_ptr=instance)
instance.save() # This should re-save the parent values.
better yet:
#receiver(post_save, sender=InstagramMedia, dispatch_uid='create_competition_media')
def create_competition_media2(sender, instance, created, **kwargs):
if created:
CompetitionMedia.objects.create(instagrammedia_ptr=instance,
**instance.__dict__)

Categories