Creating a User Profile page using OneToOne field with User Model - django

I'm currently using Django all-auth, and it has a /accounts/profile page which I want to create/populate with a form which updates user information.
I have a Teacher field, which extends the User Model using OneToOne field.
models.py
class Teacher(models.Model):
user = models.OneToOneField(User, on_delete=models.PROTECT, related_name='Teacher')
bio = models.TextField(max_length=500, blank=True)
availability = models.BooleanField(default=False)
teacher_logo = models.FileField()
This teacher model is what I want the user to update in /accounts/profile.
forms.py
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ('first_name', 'last_name', 'email')
class TeacherForm(forms.ModelForm):
class Meta:
model = Teacher
fields = ('availability', 'bio','teacher_logo')
views.py
#login_required
#transaction.atomic
def update_profile(request):
if request.method == 'POST':
user_form = UserForm(request.POST, instance=request.user)
teacher_form = TeacherForm(request.POST, instance=request.user.teacher)
if user_form.is_valid() and teacher_form.is_valid():
user_form.save()
teacher_form.save()
messages.success(request, _('Your profile was successfully updated!'))
return redirect('users:index')
else:
messages.error(request, _('Please correct the error below.'))
else:
user_form = UserForm(instance=request.user)
teacher_form = TeacherForm(instance=request.user.teacher)
return render(request, 'accounts/profile.html', {
'user_form': user_form,
'teacher_form': teacher_form
})
template users/profile.html
<form method="post">
{% csrf_token %}
{{ user_form.as_p }}
{{ teacher_form.as_p }}
<button type="submit">Save changes</button>
</form>
urls.py
url(r'^profile/$', views.update_profile, name='Update-Profile')
I can use an update view, but then I need to specify in the URL, which seems an incorrect way of doing it; Also, users will be able to edit someone else profiles.
When I run the above, I get a complaint that 'User' object has no attribute 'teacher'.
When I remove .teacher from TeacherForm(instance=request.user.teacher) It loads the page with the form, but when I update, it still gives me the same complaint (removed in both places in views.py)
EDIT: models.py extra
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Teacher.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.Teacher.save()

you set related name as Teacher, so you need:
teacher_form = TeacherForm(instance=request.user.Teacher)
# ^^^^
or better set related_name to 'teacher'
class Teacher(models.Model):
user = models.OneToOneField(
User,
on_delete=models.PROTECT,
related_name='teacher')
# ^^^

Related

How do I make a User Profile form using OnetoOneField extending the User Model?

I would like to make a form that extends the User Model using OnetoOneField. It would basically be a form in which a user can add/update their information after they have registered.
models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
username = models.CharField(max_length=120)
name = models.CharField(max_length=120) # max_length = required
email = models.EmailField(max_length=120)
paypal_id = models.CharField(max_length=120)
bio = models.TextField(max_length=500, blank=True)
def __str__(self):
return self.user.username
forms.py
class UserProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ["username", "name", "email", "paypal_id", "bio"]
views.py
def userprofile_view(request):
if request.method == 'POST':
profile_form = UserProfileForm(request.POST)
if profile_form.is_valid():
profile = profile_form.save(commit=False)
profile.save()
return redirect('account')
else:
profile_form = UserProfileForm()
context = {'profile_form': profile_form}
return render(request, 'accounts/account_create.html', context)
template.html
{% extends 'base.html' %}
{% block content %}
<form action="." method="POST">
{% csrf_token %}
{{ profile_form.as_p }}
<input type="submit" value="Save"/>
</form>
{% endblock %}
I keep getting this error when I hit Save:
(1048, "Column 'user_id' cannot be null")
Is there any fix for this?
You can create both user and profile models at once using a generic create view.
Forms:
class UserProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ["username", "name", "email", "paypal_id", "bio", "user"]
class UserForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ['username', 'first_name', 'last_name', 'email', 'password1', 'password2']
View:
class CreateUserProfileView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = Profile
form_class = UserProfileForm
user_form_class = UserForm
template_name = 'accounts/account_create.html'
success_message = "Profile created successfully"
success_url = reverse_lazy('profile-list')
def get(self, request):
profile_form = self.form_class()
user_form = self.user_form_class()
return render(request, self.template_name, {'profile_form': profile_form, 'user_form': user_form})
def post(self, request, *args, **kwargs):
profile_form = self.form_class(request.POST)
user_form = self.user_form_class(request.POST)
if all([profile_form.is_valid(), user_form.is_valid()]):
user = user_form.save()
profile = profile_form.save(commit=False)
profile.user = user
profile.save()
messages.success(request, self.success_message)
else:
return render(request, self.template_name, {'profile_form': profile_form, 'user_form': user_form})
return HttpResponseRedirect(self.success_url)
You need to overwrite save() method of Profile to create/update User on-the-fly while saving Profile.
I.e.:
class Profile(models.Model):
...
def save(self, *args, **kwargs):
if not self.user.pk:
self.user = User.objects.create_user(self.user.username, password=self.user.password)
self.user.save() # mandatory as create_user is not recognized as save operation
super().save(*args, **kwargs)

How do I update a CustomUser value via UpdateView using a hidden logic

I've spent several hours on this and I'm not able to see any signs as to why the change on the flag is not getting through.
Please note the change form already works for all exposed fields, i.e. the user can go in and change the name or country already and it will get saved after clicking on update profile.
What I'm now trying to do is to also change the confirmed_email flag to True (but without telling or letting the user see it) whenever the client makes an update to the form.
To do this I check if the user was logged in using Linkedin (or any Social account for that matter) via something along the lines of ""if user.social_auth.exists()"". That said, it's not that i can't fulfill this function, it's that even when i use a silly condition that i know it's true the field "email_confirmed" will still NOT change to True in the back-end.
Thanks so much in advance. I appreciate your time.
PS. I'm a noob with Django but loving it.
Models.py
class CustomUser(AbstractUser):
id = models.BigAutoField(primary_key=True)
email = models.EmailField(unique=True)
email_confirmed = models.BooleanField(default=False)
country = models.CharField(max_length=30,choices=COUNTRY, null=True, blank=False)
first_name = models.CharField(max_length=50, null=False, blank=False, default="")
last_name = models.CharField(max_length=50, null=False, blank=False, default="")
Views.py
class SignUpView(CreateView):
form_class = CustomUserCreationForm
success_url = reverse_lazy('home')
template_name = 'signup.html'
...
class UpdateProfileView(UpdateView):
form_class = CustomUserChangeForm
success_url = reverse_lazy('home')
template_name = 'update_profile.html'
def get_object(self, queryset=None):
return self.request.user
Forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = CustomUser
fields = ('first_name', 'last_name','country',)
class CustomUserChangeForm(UserChangeForm):
password = None
class Meta:
model = CustomUser
fields = ('first_name', 'last_name','country',)
update_profile.html
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Home{% endblock title %}
{% block content %}
{% if user.is_authenticated %}
<h2>Update Profile</h2>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-success" type="submit">Update</button>
</form>
{% else %}
<p>You are not logged in</p>
Log In |
Sign Up
{% endif %}
{% endblock content %}
My main attempt was to adding another leg in the view (see def ChangeActiveStatus below).
class UpdateProfileView(UpdateView):
form_class = CustomUserChangeForm
success_url = reverse_lazy('home')
template_name = 'update_profile.html'
def get_object(self, queryset=None):
return self.request.user
def ChangeActiveStatus(request):
if request.method == "POST":
form = self.form_class(request.POST)
user = form.save(commit=False)
if form.is_valid() and user.social_auth.exists() == True:
user.email_confirmed = True
form.save()
else:
form = form()
return render(request, 'login', {'form':form})
The issue here is that you're setting the email_confirmed = True on user and not form.instance. You could also save the user instance rather than calling form.save().
form = self.form_class()
if request.method == "POST":
form = self.form_class(request.POST)
if form.is_valid() and user.social_auth.exists():
user = form.save(commit=False)
user.email_confirmed = True
user.save()
return render(request, 'login', {'form':form})
Or
form = self.form_class()
if request.method == "POST":
form = self.form_class(request.POST)
if form.is_valid() and user.social_auth.exists():
form.instance.email_confirmed = True
form.save()
return render(request, 'login', {'form':form})
I ended up implementing a solution via models.py instead, basically bypassing the need to save any changes on one of the fields through the views, i.e. this is the logic, which will be triggered every time the client changes something in their profile.
def save(self, *args, **kwargs):
try:
CustomUser.objects.latest('id').id
except:
...
...
if self.is_active == True and self.email_verified == False:
self.email_verified = True
super(CustomUser, self).save(*args, **kwargs)
def __str__(self):
return self.email

Access data of a model linked through a OneToOneField model on a form

I want a pre-populated form with the details (e.g. first name and surname) about the profile of a logged-in user, so that they can update them. I have a custom user model with first name and surname in it, and then a profile model which is linked to the user model and extends it, containing some extra information.
I've defined a constant within the profile model which theoretically should get the user's first name and surname.
models.py:
class User(AbstractBaseUser):
email = models.EmailField(verbose_name="email", unique=True, max_length=255)
first_name = models.CharField(max_length=30, blank=True, null=True)
surname = models.CharField(max_length=30, blank=True, null=True)
[...]
objects = UserManager()
[...]
Profile model added
class Profile(models.Model):
user = models.OneToOneField(User, related_name='current_user', on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
def surname(self):
return self.user.surname}
def first_name(self):
return self.user.first_name
[...]
views.py:
#login_required
def profile_edit(request):
if request.method == 'POST':
p_form = ProfileUpdateForm(request.POST, request.FILES, instance=request.user.profile)
if p_form.is_valid():
p_form.save()
messages.success(request, f'Your account has been updated')
[...]
forms.py:
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('first_name', 'surname')
template.html:
{% extends "base.html" %}
{% block content %}
<div>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ p_form }}
<button class="button" type="submit"> User update</button>
</form>
</div>
{% endblock content %}
When accessing the template via the browser I expect to see the form already populated with the profile's (i.e. user's) first name and surname. Instead, I get a django.core.exceptions.FieldError: Unknown field(s) (surname, first_name) specified for Profile in the shell.
--
Answer
User ruddra's answer works fine, and I've flagged it as the answer to my problem. Nevertheless, declaring two different form objects and printing them out in the template would also work:
views.py:
u_form = UserUpdateForm(request.POST, instance=request.user)
p_form = ProfileUpdateForm(request.POST, request.FILES, instance=request.user.profile)
forms.py:
class UserUpdateForm(forms.ModelForm):
class Meta:
model = User
fields = ('first_name', 'surname')
class ProfileUpdateFormOld(forms.ModelForm):
class Meta:
model = Profile
fields = ('image',)
template.html:
{{ u_form }}
{{ p_form }}
Basically those fields are from User model, they are not in Profile model. So you can change the model class in ProfileUpdateForm to User:
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = User
fields = ('first_name', 'surname')
Updated answer based on comments:
class ProfileUpdateForm(forms.ModelForm):
first_name = forms.CharField(max_length=255)
surname = forms.CharField(max_length=255)
def __init__(self, *args, **kwargs):
super(ProfileUpdateForm, self).__init__(*args, **kwargs)
self.initial['first_name'] = self.instance.first_name()
self.initial['surname'] = self.instance.surname()
class Meta:
model = Profile
fields = ('first_name', 'surname')
def save(self, *args, **kwargs):
user = self.instance.user
user.first_name = self.cleaned_data.get('first_name')
user.surname = self.cleaned_data.get('surname')
user.save()
return super(ProfileUpdateForm, self).save(*args, **kwargs)
Alternative to override the __init__(...) method is to send the initial data when initiating the form, pass the initial data. For example:
profile = request.user.profile
ProfileUpdateForm(instance=profile, initial={'first_name':profile.first_name(), 'surname': profile.surname()})

Issue extending django User model

I have a website where the user should be able to sign up as a "worker" or a "customer", an uber model type of site. I created the two models WorkerProfile and CustomerProfile and the two forms, but each time I submit either the customer or worker form, the new user gets put in both the Customer profile's and Worker profile's in the database at http://127.0.0.1:8000/admin/ , how do I prevent this from happening?
models.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 WorkerProfile(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 = models.CharField(max_length = 10, default = 'USER')
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def create_worker_profile(sender, instance, created, **kwargs):
if created:
WorkerProfile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_worker_profile(sender, instance, **kwargs):
instance.workerprofile.save()
class CustomerProfile(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 = models.CharField(max_length = 10, default = 'CUSTOMER')
needLaundryDone = models.BooleanField(default = False)
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def create_customer_profile(sender, instance, created, **kwargs):
if created:
CustomerProfile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_customer_profile(sender, instance, **kwargs):
instance.customerprofile.save()
forms.py:
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class WorkerSignUpForm(UserCreationForm):
#birth_date and university fields need to be declared seperately because they are not apart of User:
birth_date = forms.DateField(help_text='Required. Format: YYYY-MM-DD')
university = forms.CharField()
class Meta:
model = User
fields = ('username',
'email',
'first_name',
'last_name',
'birth_date',
'university',
'password1',
'password2', )
class CustomerSignUpForm(UserCreationForm):
#birth_date and university fields need to be declared seperately because they are not apart of User:
birth_date = forms.DateField(help_text='Required. Format: YYYY-MM-DD')
university = forms.CharField()
class Meta:
model = User
fields = ('username',
'email',
'first_name',
'last_name',
'birth_date',
'university',
'password1',
'password2', )
views.py:
def signup(request):
if request.method == 'POST':
form_worker = WorkerSignUpForm(request.POST)
form_customer = CustomerSignUpForm(request.POST)
if form_worker.is_valid():
user = form_worker.save()
user.refresh_from_db() # load the profile instance created by the signal
user.workerprofile.birth_date = form_worker.cleaned_data.get('birth_date')
user.workerprofile.university = form_worker.cleaned_data.get('university')
user.save() # explicitly save custom fields not in User model
raw_password = form_worker.cleaned_data.get('password1')
user = authenticate(username=user.username, password=raw_password)
login(request, user) # login user after signup
return redirect('home')
elif form_customer.is_valid():
user = form_customer.save()
user.refresh_from_db() # load the profile instance created by the signal
user.customerprofile.birth_date = form_customer.cleaned_data.get('birth_date')
user.customerprofile.university = form_customer.cleaned_data.get('university')
user.save() # explicitly save custom fields not in User model
raw_password = form_customer.cleaned_data.get('password1')
user = authenticate(username=user.username, password=raw_password)
login(request, user) # login user after signup
return redirect('home')
else:
form_worker = WorkerSignUpForm(request.POST)
form_customer = CustomerSignUpForm(request.POST)
return render(request, 'core/signup.html', {'form_worker': form_worker,'form_customer': form_customer })
signup.html:
{% extends 'core/base.html' %}
{% block head %}
<title> Sign Up</title>
{% endblock %}
{% block body %}
<h3>Sign Up As Worker</h3>
<form method="post">
{% csrf_token %}
{{ form_worker.as_p }}
<button type="submit">Sign up</button>
</form>
<h3>Sign Up As Customer</h3>
<form method="post">
{% csrf_token %}
{{ form_customer.as_p }}
<button type="submit">Sign up</button>
</form>
{% endblock %}
Don't use signals here. They both fire on save of User, and each create their related object.
You should remove those signals and instead do this in the view. In the is_valid block for each form you can create only the specific object you need.
if form_worker.is_valid():
user = form_worker.save()
worker = WorkerProfile(user=user)
worker.birth_date = form_worker.cleaned_data.get('birth_date')
worker.university = form_worker.cleaned_data.get('university')
worker.save()
raw_password = form_worker.cleaned_data.get('password1')
...
elif form_customer.is_valid():
user = form_customer.save()
customer = CustomerProfile(user=user)
...
This is normal :
user = models.OneToOneField(User, on_delete=models.CASCADE)
If you call this in both models, so during the save(), it will create both.
What I propose you, is to modify your models. Why don't you just create a model 'Profil' and put a boolean field 'is_customer' True/False ?

Django User Profile Form Empty

i know their is already a lot of post about that but i tried a lot of solutions and i cannot display my form !
I want to do something really simple (i'm a Django beginner), i create a specific UserProfile to extend the basic one, and i want to let the user edit it :
Here is my model :
class UserProfile(models.Model):
user = models.OneToOneField(User)
cc = models.CharField(max_length=400, blank=True)
lang = models.CharField(max_length=5, blank=True, )
def __unicode__(self):
return unicode(self.user.email)
class UserProfileForm(forms.ModelForm):
class meta:
model = UserProfile
def __init__(self,*args,**kwargs):
super(UserProfileForm, self).__init__(*args, **kwargs)
self.user = kwargs['instance']
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
Here is my view method :
#login_required
def accountform(request):
data = {}
data.update(csrf(request))
user_profile = request.user.get_profile()
data['form'] = UserProfileForm(instance=user_profile)
print user_profile
return render(request, 'accountform.html', data)
Here is my template :
<form action="/contact/" method="post">
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
when i display the from i just see the submit button...
The meta inner class should be capitalized - Meta
class UserProfileForm(forms.ModelForm):
class Meta: # not meta
model = UserProfile