Adding One-To-One field to the default Django user model - django

I'm trying to add one-to-one field to the default Django user model but
for some reason I keep getting error from the database:
django.db.utils.IntegrityError: NOT NULL constraint failed: frontend_usermodel.test_suites_id
this is models file:
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.db import models
class TestSuite(models.Model):
id = models.IntegerField(primary_key=True)
...
...
def __str__(self):
return self.name
class Meta:
ordering = ('name',)
class UserModel(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
test_suites = models.OneToOneField(TestSuite, on_delete=models.CASCADE)
def __str__(self):
return self.email
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserModel.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
What can I do to solve this ?
UPDATE:
class TestSuite(models.Model):
id = models.IntegerField(primary_key=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
...
...

Your UserModel requires both fields user and test_suites, so this line:
UserModel.objects.create(user=instance)
will fail because test_suites is None and therefore the NULL constraint is violated. You have to pass also a TestSuite instance to create your UserModel instance.
Although I don't know the exact business requirements of your application, it would seem more logical to me to set the OneToOneField on the TestSuite. I can imagine a user without test suite, and I would expect only when you create a test suite, you would assign the user. By doing that, you won't need a TestSuite instance when creating a User.

Related

How to trigger a function on user creation with django?

I'm new to django and I would like to know if it is possible to trigger a function after a user has been created? For user creation I use the django administration interface.
I want to assign a default theme to a user and to do this I have the following class in my models.py file
class Profile(models.Model):
user = models.OneToOneField(User, null=False, on_delete=models.CASCADE)
selected_theme = models.TextField(max_length=50, default="Clair")
This class allows me to store the theme selected by the user
I would like to use a function that assigns a theme to a user when creating it.
To do so you need to add the following lines to the model.py file in your Profile Class:
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
user = models.OneToOneField(User, null=False, on_delete=models.CASCADE)
selected_theme = models.TextField(max_length=50, default="Clair")
#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()

How to create auto generated membership id in django

I want to create an auto-generated membership id of a user in the profile table based on the current date and username. User table has OneToOneField relationship with the profile table. So when I create a user, I have to put username in the registration form. The signals.py creates a profile row in the table for the user. I want when the profile is created it would have a membership id which is the mix of current date and username. My code is as follow:
singlas.py
from django.db.models.signals import post_save, pre_save
from .models import Ext_User
from django.dispatch import receiver
from .models import Profile
#receiver(post_save, sender=Ext_User)
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
models.py
class Profile(models.Model):
user = models.OneToOneField(Ext_User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics', null=True, blank=False)
membership_id = models.CharField(max_length=50, default='', null=True, blank=True)
def __str__(self):
return f'{self.user.username} Profile'
I have got some guideline to user pre_save into signals.py but don't understand how to figure out.
You can try this
#receiver(post_save, sender=Ext_User)
def create_profile(sender, instance, created, **kwargs):
if created:
profile = Profile()
profile.membership_id = str(instance.username) + str(datetime.datetime.now())
profile.user_id = instance.pk
profile.save()
format DateTime as your desired format

This model works, but I do not understand how the signal works

I have the following in model.py:
from django.db import models
from django.contrib.auth.models import User
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)
university = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
ROLE = (
('CUSTOMER', 'User'), # (value to be set on model, human readable value)
('WORKER', 'Worker'),
)
role = models.CharField(max_length = 20, choices = ROLE, default = 'USER')
def __str__(self):
return self.user.username
#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 also have a corresponding form that, when filled out and sumbitted, saves to the database as a Profile properly.
What I do not understand is instance.profile.save() how does this work? To me it appears it should be instance.Profile.save() since, Profileexists. I am not sure where this lowercase profileis coming from?
In Django, reverse accessors are defined with the name of the related model in lowercase by default.
As your Profile model has a one-to-one relationship with User model, you can access profile instance belonging to a User instance (say user) as user.profile.
You can override this naming with a parameter where you define the OneToOneField in your model definition with the keyword argument related_name.
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='myprofile')
Now, you access via user.myprofile
See Django documentation here
By the way, be careful when you are accessing profile of a user via User instance. If there is no Profile record associated with that User instance, a DoesNotExist exception is raised.

Django: extend User model with profile onetoonefield errors

I have this model
from django.contrib.auth.models import User
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from my_app.models import Teams
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
team = models.ForeignKey(Teams, on_delete=models.CASCADE)
class Meta:
app_label = 'my_app'
def __str__(self):
return self.name
#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 then go into the shell
from django.contrib.auth.models import User
when I type
user = User.objects.create_user(username='test', email='dummyemail#dum.com', password='test')
I unsurprisingly get
IntegrityError: NOT NULL constraint failed: my_app_profile.team_id
but when I type
user = User.objects.create_user(username='test', email='dummyemail#dum.com', password='test', team='developer')
I get
TypeError: 'team' is an invalid keyword argument for this function
If I type
user = User.objects.create_user(username='test', email='dummyemail#dum.com', password='test', profile.team='developer')
I get
SyntaxError: keyword can't be an expression
Any help, hints or guidance would be greatly appreciated
You haven't allowed team to be null, so when you create a profile like so:
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
# Team isn't defined
Profile.objects.create(user=instance)
You will get an error.
Allow team to be null (or set a default in the code above):
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
team = models.ForeignKey(Teams, on_delete=models.CASCADE, null=True)
class Meta:
app_label = 'my_app'
def __str__(self):
return self.name
When you run
User.objects.create_user(username='test', email='dummyemail#dum.com', password='test')
you get IntegrityError because of post_save signal. It tries to create a Profile instance but in Profile model team cannot be NULL. By default, model fields have null=False.
When you run
user = User.objects.create_user(username='test', email='dummyemail#dum.com',
password='test', team='developer')
you get error because team is a field in Profile model, not User model.
When you run
user = User.objects.create_user(username='test', email='dummyemail#dum.com',
password='test', profile.team='developer')
you get error because you cannot use . to refer attributes. You need to use __ to filter on foreign key properties. See this question for example. However, even if you use __, it will still give error because their is no field named profile in User model.
One more thing I would suggest is to combine both post_save signals into one because both have same sender.
See the documentation about ForeignKey.related_name. By default, OneToOneField have related name to join one model to an ohter, but ForeignKey don't. Just have this option and you can do user.team to access to the model related.
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
team = models.ForeignKey(Teams,
on_delete=models.CASCADE,
related_name='team',
null=True)

Updating the a user profile upon user save

I'm following the 'User profile' approach to extend my User model, like so:
# models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE, primary_key=True)
my_field = models.CharField(max_length=100)
# signals.py
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
With this approach, I have to explicitly call user.profile.save(), which to me feels clunky, as I want the profile to give the illusion it is part of the User object:
# views.py
def some_func(request):
user = User.objects.create_user('dummy', 'dummy#dummy.com', '12345678')
user.profile.my_field = 'hello'
user.save() # This does not persist the profile object...
user.profile.save() # ...this does
To remedy this, I've changed create_user_profile() to the following, which works:
# signals.py
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
profile = UserProfile.objects.get_or_create(user=instance)
profile.save()
Numerous examples I've encountered do not use this approach. Are there any caveats to using this approach?
The better way is to specify a custom user model.
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
custom_field = models.ForeignKey(
'contracts.Contract'
)
...
class Meta(AbstractUser.Meta):
swappable = 'AUTH_USER_MODEL'
You have to update the settings.py defining the AUTH_USER_MODEL property:
AUTH_USER_MODEL = 'app_name.User'
You can use a custom User model like this :
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return f'{self.user.username} Profile'
and then the signals.py file :
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import Profile
#receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
instance.profile.save()
what the signals.py file does here is to inform you when a new Profile object is created of the User type which you can further use to create forms that update/create the user's profile.
And to make all this work, you need to import the signals.py file in the apps.py file of your app. For example, this is what your apps.py file would look like :
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'
def ready(self):
import users.signals
Yes, there are a few. In the following situations the post_save signal would not be fired.
1 If the save method does not successfully save the object (such as when an IntegrityError occurs)
2 When you call MyModel.objects.update()
3 When you override the save method and forget to call the superclass method.
4 When your signal receiver hasn't been successfully registered.
In these situations your profile wouldn't be saved.