I am trying to create an "edit profile" page/form and have followed the SimpleIsBetterThanComplex tutorial on how to extend the user model by way of the OneToOne Link. However, when i try to display the user profile form to allow the user to update their profile, the form doesn't display at all except for the Submit button... Any ideas where i might have gone wrong here?
models.py
from __future__ import unicode_literals
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
from django.utils import timezone
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
#account types
is_userA = models.BooleanField('User A', default=True)
is_userB = models.BooleanField('User B', default=False)
is_superuser = models.BooleanField('SuperUser', default=False)
#other fields here
avatar = models.ImageField('avatar', upload_to='static/media/images/avatars/', null=True, blank=True)
phone = models.CharField('phone number', max_length=20, blank=True, default='')
address = models.CharField('address', max_length=100, default='', blank=True)
city = models.CharField('city', max_length=100, default='', blank=True)
state = models.CharField('state', max_length=2, default='', blank=True)
country = models.CharField('country', max_length=100, default='', blank=True)
date_joined = models.DateTimeField(default=timezone.now)
#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()
forms.py
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ('first_name', 'last_name', 'email')
views.py
#login_required
#transaction.atomic
def update_profile(request):
if request.method == 'POST':
user_form = UserForm(request.POST, instance=request.user)
if user_form.is_valid():
user_form.save()
messages.success(request, _('Your profile was successfully updated!'))
return redirect('settings:profile')
else:
messages.error(request, _('Please correct the error below.'))
else:
user_form = UserForm(instance=request.user)
return render(request, 'profiles/profile.html', {
'user_form': user_form,
})
template
<div class="col-md-12">
<div class="mb32">
<h5 class="uppercase">General Information</h5>
</div>
<div class="row">
<form method="post">
{% csrf_token %}
{{ user_form.as_p }}
<button type="submit">Save changes</button>
</form>
Your view looks for now looks like this:
views.py
#login_required
#transaction.atomic
def update_profile(request):
if request.method == 'POST':
user_form = UserForm(request.POST, instance=request.user)
if user_form.is_valid():
user_form.save()
messages.success(request, _('Your profile was successfully updated!'))
return redirect('settings:profile')
else:
messages.error(request, _('Please correct the error below.'))
else:
user_form = UserForm(instance=request.user)
return render(request, 'profiles/profile.html', {
'user_form': user_form,
})
According to this view if method POST, you validate enterd data from the form, but when its GET method you just define UserForm, so i think you have to make indent in the last line:
views.py
#login_required
#transaction.atomic
def update_profile(request):
if request.method == 'POST':
user_form = UserForm(request.POST, instance=request.user)
if user_form.is_valid():
user_form.save()
messages.success(request, _('Your profile was successfully updated!'))
return redirect('settings:profile')
else:
messages.error(request, _('Please correct the error below.'))
else:
user_form = UserForm(instance=request.user)
return render(request, 'profiles/profile.html', {
'user_form': user_form,
})
So your last line return render(request, 'profiles/profile.html', {'user_form': user_form,}) have to be under else statement, cause its GET as I understand.
Related
I have written an extension of the Django auth.user model which adds middle_name and phone fields to the model.
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
middle_name = models.CharField(max_length=20, blank=True)
phone = models.CharField(max_length=10, blank=True)
def __str__(self):
return(f'{self.user.username} profile')
I am using the following forms to accept input from the user.
class RegistrationForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['first_name', 'last_name', 'email', 'username', 'password1', 'password2']
class ProfileForm(forms.ModelForm):
phone = forms.CharField(max_length=10, min_length=10)
middle_name = forms.CharField(max_length=20, required=True)
class Meta:
model = Profile
fields = ['middle_name', 'phone']
The view for the register route is as follows:
def register(request):
if request.method == 'POST':
user_form = RegistrationForm(request.POST)
profile_form = ProfileForm(request.POST)
if user_form.is_valid() and profile_form.is_valid():
username = user_form.cleaned_data.get('username')
user_form.save()
profile_form.save()
messages.success(request, f'Welcome { username }! Your account has been created. Sign in to continue.')
return redirect('login')
else:
return render(request, 'home/register.html', { 'user_form': user_form, 'profile_form': profile_form })
else:
user_form = RegistrationForm()
profile_form = ProfileForm()
return render(request, 'home/register.html', { 'user_form': user_form, 'profile_form': profile_form })
This is contents of the signals.py file
from django.contrib.auth.models import User
from django.db.models.signals import post_save
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()
The issue is while a row for the user is getting created in the Profile model, data is not getting written into the fields of the row. I am seeing a Exception Value: NOT NULL constraint failed: home_profile.user_id
By using a signal, you will create a Profile object, but the profile_form will be unaware of that, and thus create another one that does not link to the user. You thus might want to remove the signals. Signals are often an anti-pattern, and furthermore there are several ORM calls that can circumvent the signals.
If you remove the signals, you will still need to link the Profile object wrapped in your profile_form to the user, you do this with:
# remove the signal that constructs a Profile
def register(request):
if request.method == 'POST':
user_form = RegistrationForm(request.POST)
profile_form = ProfileForm(request.POST)
if user_form.is_valid() and profile_form.is_valid():
user = user_form.save() # ← assign the user to a variable
profile_form.instance.user = user # ← set it as user of the profile
profile_form.save()
messages.success(request, f'Welcome {user.username}! Your account has been created. Sign in to continue.')
return redirect('login')
else:
user_form = RegistrationForm()
profile_form = ProfileForm()
return render(request, 'home/register.html', { 'user_form': user_form, 'profile_form': profile_form })
models.py
class Profile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
# on_deleting user, profile will also be deleted
image = models.ImageField(default="profilepic.jpg",upload_to="profile_pictures")
dob = models.DateField(null=True)
bio = models.TextField(null=True)
anonymous = models.BooleanField(default=False)
def __str__(self):
return f'{self.user.username} Profile'
views.py
#login_required
def profile_edit(request):
if request.method == 'POST':
u_form = UserUpdateForm(request.POST, instance=request.user)
p_form = ProfileForm(request.POST, request.FILES, instance=request.user.profile)
if u_form.is_valid() and p_form.is_valid():
u_form.save()
p_form.save()
messages.success(request, f'your account has been updated')
return redirect('users:profile')
else:
u_form = UserUpdateForm(instance=request.user)
p_form = ProfileForm()
context = {
'u_form' : u_form,
'p_form' : p_form
}
return render(request, 'users/profile_edit.html',context)
in users/profile_edit.html
<div class="form-group">
<label class="radio-inline"><input type="radio" name="anonymous" value='True'>Anonymous</label>
<label class="ml-5 radio-inline"><input type="radio" name="anonymous" value='False' checked>Non-Anonymous</label>
</div>
forms.py
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from .models import Profile
class UserUpdateForm(forms.ModelForm):
class Meta:
model = User
fields = ['first_name','last_name']
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ['image','dob','bio']
the rest of the form works fine (updates the contents of profile as expected) but the BooleanField for anonymous does not work when the radio buttons are clicked before submitting the form
after #harshil9968 asked for profile form, I realized that i had not included anonymous in my profileform, hence the output of radio button was not being processed on form submit
I am new to Django and creating a Django project with an account system. I extended the User model with a one-to-one UserProfile model.
However, I am having problems saving the data in the UserProfile model (saving to User model is having no problem). I tried to follow this guide to fix it but ran into AttributeError at /account/login/
'User' object has no attribute 'UserProfile' errors which is saying there are errors with instance.UserProfile.save() under models.py
How do I get the UserProfile form to save and fix this no attribute error?
models.py
from django.db import models
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
import datetime
from django.conf import settings
class UserProfile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
blank=True,
null=True,
unique=True,
related_name='userprofile'
)
bio = models.CharField(max_length=200, default='', blank=True)
campus = models.CharField(max_length=100, default='', blank=True)
website = models.URLField(default='', blank=True)
phone = models.IntegerField(default=0, blank=True)
image = models.ImageField(upload_to='profile_image', blank=True)
birthday = models.DateField(blank=True, default=datetime.date.today)
gender = models.CharField(default='', blank=True, max_length=20)
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.UserProfile.save()
forms.py
# Edit User form
class EditProfileForm(UserChangeForm):
class Meta:
model = User
fields = (
'email',
'first_name',
'last_name',
'password',
)
# Edit User Profile form
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = (
'bio',
'campus',
'birthday',
'gender',
'website',
'phone',
'image',
)
views.py
#login_required
#transaction.atomic
def edit_profile(request):
if request.method == 'POST':
user_form = EditProfileForm(request.POST, instance=request.user)
profile_form = UserProfileForm(request.POST, instance=request.user.UserProfile.user)
if user_form.is_valid() and profile_form.is_valid():
user = user_form.save(commit=False)
#user.username = user.email
user.save()
profile = profile_form.save(commit=False)
profile.user = user
profile.save()
return redirect('/account/profile')
else:
profile_form = UserProfileForm(instance=request.user.userprofile)
user_form = EditProfileForm(instance=request.user)
args = {
'user_form': user_form, # basic user form
'profile_form': profile_form # user profile form
}
return render(request, 'accounts/edit_profile.html', args)
templates accounts/edit_profile.html
<form method="POST">
{% csrf_token %}
{{ user_form.as_p }}
{{ profile_form.as_p }}
<button type="submit">Submit</button>
</form>
urls.py
url(r'^profile/edit/$', views.edit_profile, name='edit_profile')
SOLUTION:
in models.py only have one receiver and change else:
instance.UserProfile().save() to else:
instance.userprofile().save()
#receiver(post_save, sender=User)
def create_or_update_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
else:
instance.userprofile.save()
in views.py fix typos
change profile_form = UserProfileForm(request.POST, instance=request.user.UserProfile.user) to profile_form = UserProfileForm(request.POST, instance=request.user.userprofile)
You have two handlers listening on the same signal (post_save) and your save_user_profile function runs before create_user_profile can find a chance to create the UserProfile instance.
I would suggest consolidating both handlers into one, such as:
#receiver(post_save, sender=User)
def create_or_update_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
else:
instance.userprofile.save()
I'm using two ModelForms to create na user with a Profile instance. Profile ModelForm has a single field. I'm doing form.save(commit=False), setting the required field (country) other than the one in the form and saving. I get an error saying that 'country_id' cannot be null.
models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
bio = models.TextField(blank=True)
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
email_confirmed = models.BooleanField(default=False)
#receiver(post_save, sender=User)
def update_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
instance.profile.save()
forms.py
class UserRegisterForm(UserCreationForm):
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
class ProfileRegisterForm(forms.ModelForm):
class Meta:
model = Profile
fields = ['country']
views.py
def register(request):
if request.method == 'POST':
user_form = UserRegisterForm(request.POST)
profile_form = ProfileRegisterForm(request.POST)
if user_form.is_valid() and profile_form.is_valid():
user = user_form.save(commit=False)
user.is_active = False
profile = profile_form.save(commit=False)
profile.user = user
user.save()
current_site = get_current_site(request)
subject = 'Activate Your Account'
message = render_to_string('account_activation_email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode(),
'token': account_activation_token.make_token(user)
})
user.email_user(subject, message)
return redirect('activation_email_sent')
else:
user_form = UserRegisterForm()
profile_form = ProfileRegisterForm()
return render(request, 'register.html', {'user_form': user_form, 'profile_form': profile_form})
Yeah. I removed the post_save, made country nullable and saved user and profile separately. Thanks for the help.
views.py
def register(request):
if request.method == 'POST':
user_form = UserRegisterForm(request.POST)
profile_form = ProfileRegisterForm(request.POST)
if user_form.is_valid() and profile_form.is_valid():
user = user_form.save(commit=False)
user.is_active = False
user.save()
profile = profile_form.save(commit=False)
profile.user = user
profile.save()
I want to fetch each and every detail of user profile. I know function get_profile() has been depreciated.
I want get user profile and then pass it as context to template.
Actually I am making a "Edit user profile" functionality.
My Model:
class UserProfile(models.Model):
user = models.OneToOneField(User)
state = models.CharField(max_length=200, blank=True)
country = models.CharField(max_length=200, blank=True)
zipcode = models.CharField(max_length=200, blank=True)
And views:
#login_required
def dashboard(request):
context = RequestContext(request)
profile = request.user.userprofile
context_dict = {'profile': profile}
return render_to_response('appname/dashboard.html', context_dict, context)
To edit both User and Profile instance you have to use two forms:
class UserForm(forms.ModelForm):
class Meta:
class = User
fields = ('username', 'first_name', 'last_name')
class ProfileForm(forms.ModelForm):
class Meta:
class = UserProfile
exclude = ('user', )
#login_required
def edit_profile(request):
user = request.user
profile = user.userprofile
if request.method == 'POST':
user_form = UserForm(request.POST, instance=user)
profile_form = ProfileForm(request.POST, instance=profile)
if all([user_form.is_valid(), profile_form.is_valid()]):
user_form.save()
profile_form.save()
return redirect('.')
else:
user_form = UserForm(instance=user)
profile_form = ProfileForm(instance=profile)
return render(request, 'user_profile.html',
{'user_form': user_form, 'profile_form': profile_form})
And the template should contain both forms in the single <form> tag:
<form action="." method="POST">
{% csrf_token %}
{{ user_form.as_p }}
{{ profile_form.as_p }}
<button>Update</button>
</form>