'RelatedManager' object has no attribute 'save' - django

This is the models.py code
class Profile(models.Model):
user = models.ForeignKey(User, unique=True, on_delete=models.CASCADE, related_name='profile')
#receiver(post_save, sender=User)
def update_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
instance.profile.save()
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
# Profile.objects.save()
instance.profile.save()
and This the views.py code:
def signup(request):
....
if request.method == "POST":
user_form = UserRegistrationForm(request.POST)
if user_form.is_valid():
user = user_form.save(commit=False)
user.save(using='default')
login(request, user)
return HttpResponse("User Loged In")
Then after fill the registration form and click the submit button, Django get me the following Error:

You defined a ForeignKey from a Profile to a User, but that means every Profile is attached to a User, it is however perfectly possible that multiple Profiles are attached to the same User.
Hence if you use a_user.profile, it will not return a single Profile, but a RelatedManager: a manager that manages the collection (that can contain zero, one or multiple elements) of Profile.
I think however that you actually want to associated Profiles with distinct Users, in which case you better use a OneToOneField:
class Profile(models.Model):
user = models.OneToOneField(User, unique=True, on_delete=models.CASCADE)
If you then query someuser.profile you will either get a Profile instance, or it will raise a Profile.DoesNotExists error in case no profile is associated with that user.
By default the related_name of a OneToOneField is the name of the class in lowercase, so you no longer need to specify this.
Note however that in your code instance.profile.save() is not necessary, since the Profile, is basically already saved by creating it:
#receiver(post_save, sender=User)
def update_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
# unnecessary
# instance.profile.save()

Related

Overwriting save method to create entry in related table automatically django

After registration email with email confirmation is sent to a new user. I created model
UserWithConfirmation with new field is_email_confirmed. I was following this https://docs.djangoproject.com/en/4.1/topics/auth/customizing/#extending-the-existing-user-model.
I want to have UserWithConfirmation created for each new user when user is saved. For now I have sth like this.
from django.db import models
from django.contrib.auth.models import User
class UserWithConfirmation(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="user_with_confirmation")
is_email_confirmed = models.BooleanField(default=False)
def __str__(self):
return self.user.username
class User:
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
create_user_with_confirmation(User)
def create_user_with_confirmation(user):
UserWithConfirmation(user=user)
UserWithConfirmation.save()
How to make it works?
Just have UserWithConfirmation extend User
class UserWithConfirmation(User):
is_email_confirmed = models.BooleanField(default=False)
and change the entry when the email is confirmed
I solved my problem using signals
I changed UserWithConfirmation to Profile
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
is_email_confirmed = models.BooleanField(default=False)
#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()

Extend User model in Django with jwt authorization

I am trying to extend User model in Django. I already have authorization using jwt Token and I am currently trying to add another field to User model, such as phone number and address.
My views.py looks like this:
class MyObtainTokenPairView(TokenObtainPairView):
permission_classes = (AllowAny,)
serializer_class = MyTokenObtainPairSerializer
class RegisterView(generics.CreateAPIView):
queryset = User.objects.all()
permission_classes = (AllowAny,)
serializer_class = RegisterSerializer
models.py like this:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone_number = models.IntegerField(blank=False)
address = models.CharField(max_length=500, 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()
In here I'm trying to create another class that will store phone number and address. How can I incorporate my Profile class into User so that it will create new user model and what is more use earlier implemented Token authorization.

Django 3.1, I'm NOT getting RelatedObjectDoesNotExist error for User and Profile models

as the title reads I'm NOT getting the RelatedObjectDoesNotExist error in Django 3.1(Latest Release)
I'm not using signals. I create a superuser using the (python manage.py createsuperuser) command, which, as expected, does not create a profile.
models.py
'''
class User(AbstractUser):
pass
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
birth_date = models.DateField(null=True, blank=True)
'''
views.py
'''
class RegisterView(View):
def get(self, request):
form = UserSignUpForm()
# print(form)
return render(request, 'users/register.html', {'form': form})
def post(self, request):
form = UserSignUpForm(request.POST)
if form.is_valid():
form.save()
username = request.POST.get('username')
Profile.objects.create(user=User.objects.get(username=username))
return redirect('users:login-page')
return render(request, 'users/register.html', {'form': form})
'''
when I use the createsuperuser command no profile is created, so I expect to get RelatedObjectDoesNotExist if I try to sign in. But I do NOT! why is that? also if I edit the database manually and remove a profile and keep the user, the user still works with no RelatedObjectDoesNotExist error!
is this something that has changed with Django 3.1 !
thank you
I actually found the issue.
in the absence of signals connecting User and Profile models, Django couldn't tell a profile was missing for the signed-in user and no error was raised.
Try to create a new signals.py in the same folder and make the create_profile and save_profile functions in the below, that should work.
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()

Adding field to Profile when creating User

I have a signup form that looks like this:
class SignUpForm(UserForm):
username = forms.CharField(max_length=32, min_length=1)
initials = forms.CharField(max_length=3)
password = forms.CharField(widget=forms.PasswordInput())
confirm_password = forms.CharField(widget=forms.PasswordInput())'
...
I'm creating the user like this:
class SignUpView(FormView, LoginErrorView):
form_class = SignUpForm
template_name = "website/sign_up.html"
def form_valid(self, form):
User.objects.create_user(username=form.cleaned_data['username'],
email=form.cleaned_data['email'],
password=form.cleaned_data['password'])
....
The Profile model looks like this:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
initials = models.CharField(max_length=3)
....
I'm creating the profile like this:
#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()
What I would like to do is to save initials in post_save/create_user_profile (in the Profile-model) from the SignUpForm when I'm creating the Profile object, but I can't figure out any simple way of doing this. Any ideas?
You can save profile just after saving user. Try this:
class SignUpView(FormView, LoginErrorView):
form_class = SignUpForm
template_name = "website/sign_up.html"
def form_valid(self, form):
user = User.objects.create(username=form.cleaned_data['username'],
email=form.cleaned_data['email'],
password=form.cleaned_data['password'])
Profile.objects.create(user=user, initials=form.cleaned_data['initials'])
Remove post_save. I hope this will work. If not, please comment.

OneToOne field in a django modelform

I have a OneToOne field corresponding to a user (user_id), and in my avatar upload form I get a drop down list of all users to select one, but I want django to fill it with the current logged in user. Also, when submitting the form, for some users I get this message "Profile with this User already exists."
For the first problem I think that inline-formsets are the solution, but I don't know how to apply this to my form. I have no clue about the second problem. I am creating the Profile with a signal every time a new user is created, and I don't know how to just update it. I'm new to django.
Here is the code for my model:
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)
avatar_img = models.ImageField(upload_to='avatars', 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()
The form:
from django import forms
from profiles.models import Profile
class AvatarForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('user', 'avatar_img', )
And the view:
from django.shortcuts import render, redirect
from profiles.forms import AvatarForm
def model_form_upload(request):
if request.method == 'POST':
form = AvatarForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect('index')
else:
form = AvatarForm()
return render(request, 'profiles/avatar_form.html', {
'form': form
})
Thanks.
Update with the solution... I added this to the form:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['user'].widget.attrs['disabled'] = 'true'
self.fields['user'].widget = HiddenInput()
You can use initial argument for the form. When creating form object in your view you can give an initial argument for your form.
In your view.
This
form = AvatarForm()
can be replaced with
form = AvatarForm(initial={'user': request.user})
This reference should be helpful to you.