Attribute error in extending django user model? - python

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

Related

Tips to better manage the creation of a non-nullable object through signals

I find myself in this situation:
In models:
class Client(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
# other...
signals.py:
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Client.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.client.save()
There are specific fields of the Client class that cannot be null and therefore I get errors like:
NOT NULL constraint failed: accounts_client.dob
What is the best solution in this case?
The obvious answer is to just make sure that data is being passed to those fields to ensure the non-null constraint doesn't fail. As to how you do that, the easiest way is to set a default value on your field - this default doesn't necessarily need to be a fixed value, it can accept a callable function - however, it will not have the context of the object you are creating. To do that, you would need to override the save() method.

OneToOne connection between User and Profile in Django

I am creating an app in django in which after user model is created, via the signal I create a profile:
from django.contrib.auth.models import User
class Profile(models.Model):
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 as far as I feel, this does not guarantees me that profile id always equals user's one
Is there any smart way to do this safer?
I think about creating a signal which will delete profile model after user one and opposite, but I still feel that not all cases are covered
When you are creating a separate model linked in such a way, you can add primary_key = True to your OneToOne field eg,
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
This will ensure you can use the same PK value for the User and the Profile.
It's not recommended to use the built-in django User model. It's better to create your own User model, that inherits from AbstractUser. Then override the save method:
from django.contrib.auth.models import AbstractUser
class MyUser(AbtractUser)
""" Add additional fields if you want """
def __str__(self):
return "something"
def save(self, *args, **kwargs):
Profile.objects.create(user=self)
super(MyUser, self).save(*args, **kwargs)
You can also override the delete() and other model methods, read the documentation about it here: https://docs.djangoproject.com/en/4.0/topics/db/models/#overriding-predefined-model-methods

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.

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