Extending a model in Django - python

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)

Related

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.

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

How to create a form in Django that fill the fields on a related model

I Have a Model that have a OnetoOne relationship with django.contrib.auth.models auth.
When I create one User Model I send a signal to the Profile Model which is related to this User. Then the Profile Object is created, but I can't figure out how to fill the others fields of Profile Object. Eg.
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
#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()
Could some one help me? How can I fill these bio, location and birth_date on a form?
Edit:
I found a Solution. don't know if its the best practices. Inside forms.py override the save()
def save(self, commit=True):
# Save the provided password in hashed format
user = super(RegisterForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
user.save()
user.profile.location= self.cleaned_data["location"]
user.profile.save()
return user

primary key must be unique when extending table

I'm trying to add extend user model. I keep getting this error: Primary key is not unique.
class UserExtended(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,parent_link=True,primary_key=True)
If I remove primary_key=True then I get the error instance.userextended.id does not exists well, of course it doesn't since now I dont have id.
How do I get around this?
In models.py
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class UserExtended(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
# Then you can also add fields like these to extended model
profilepic = models.ImageField(upload_to='uploads/users/%Y/%m/%d/', null=True, blank=True)
designation = models.CharField(max_length=200,null=True, blank=True)
about = models.TextField(null=True, blank=True)
website = models.URLField(null=True,blank=True)
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserExtended.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.userextended.save()
Now, whenever a new user is created, a extended record for that user will be created automatically.
Also, if a user record is saved, then it will automatically be updated in extended record.

Categories