I'm trying to create a basic user registration system for clients of a web application.
I've created the appropriate views and templates to create a form page which creates a Django User object and a UserProfile object of my own creation. (These are linked via a 1-1 field).
After visiting and filing in the forms on my registration page, I click submit and the fields related to initializing the UserProfile fields will be cleared and a "This field is required." error will be displayed over each input box (despite being properly filled in previously). If I fill these selected fields in again, and press submit the registration request will be processed correctly.
In the terminal, I've printed out the value of is_valid() for each form. On the first pass, the User form returns true, while the UserProfile form returns false. On the second submission they both return true.
Could you help me understand why this second form is returning false on the first pass and forcing me to resubmit?
Code is below:
models.py
from django.db import models
from django.contrib.auth.models import User
from django.forms import ModelForm
class UserProfile(models.Model):
user = models.OneToOneField(User)
name = models.CharField(max_length=100)
phone = models.CharField(max_length=32)
email = models.CharField(max_length=100)
institute = models.CharField(max_length=100)
address1 = models.CharField(max_length=100)
address2 = models.CharField(max_length=100)
city = models.CharField(max_length=100)
country = models.CharField(max_length=100)
postal_code = models.CharField(max_length=24)
description = models.TextField(max_length=2500)
def __unicode__(self):
return self.name
class UserForm(ModelForm):
class Meta:
model = User
fields = ['username', 'password', 'email']
class UserProfileForm(ModelForm):
class Meta:
model = UserProfile
exclude = ['user']
views.py
def registration(request):
if request.method == 'POST':
print('post')
user_form = UserForm(request.POST, prefix='user')
profile_form = UserProfileForm(request.POST, prefix='userprofile')
print('user form ' + str(user_form.is_valid()))
print('profile form ' + str(profile_form.is_valid()))
if user_form.is_valid() and profile_form.is_valid():
print('both valid')
user = user_form.save(commit=False)
user.is_active = False
user.save()
userprofile = profile_form.save(commit=False)
userprofile.user = user
userprofile.save()
print('success')
return HttpResponseRedirect('registration-success/')
else:
print('unbound')
user_form = UserForm(prefix='user')
profile_form = UserProfileForm(prefix='profile')
context = { 'userform': user_form,
'userprofileform': profile_form,}
return render(request, 'registration/register.html', context)
def success(request):
return render(request, 'registration/success.html', )
template.html
<!DOCTYPE html>
<html>
<body>
<h2> Registration </h2>
<form method="POST">
{% csrf_token %}
{{userform}}
</br></br>
{{userprofileform}}
<input type="submit" value="Submit"/>
</form>
forgot username/password<br />
new user
</body>
</html>
In your POST codepath, you have this:
profile_form = UserProfileForm(request.POST, prefix='userprofile')
In your else codepath, this:
profile_form = UserProfileForm(prefix='profile')
The prefix values need to match so that the POST data will be bound correctly to the profile form. It works on your resubmission because that goes through the POST codepath, so the ids used in the template match those the form object expects.
Related
I am in the middle of a project. I have extended the custom django user and modified it.
this is my user model:-
class User(AbstractUser):
name = models.CharField(max_length=200, null=True, blank=True)
usertype = models.CharField(choices = [('d','doctor'), ('p','patient')], max_length=1)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = []
def __str__(self):
return self.name
Also I have declared two seperate models named Patient and Doctors. My objective is to register the users in their respective models(Doctors or Patients) by checking the usertype.
Here are those models:-
class Patient(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='patient')
dob = models.DateField(null=True, blank=True)
contact = models.CharField(null=True, blank=True, max_length=100)
def __str__(self):
return self.user.name
class Doctor(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='doctor')
deg = models.TextField(null=True, blank=True)
def __str__(self):
return self.user.name
Now at the front end I want to apply the logic as every time a user is registered the user selects the usertype and based on that selection the Doctor or the Patient module is updated.
I have tried creating separate forms for that too.
Here are my forms :-
class MyUserCreation(UserCreationForm):
class Meta:
model = User
fields = ['name','username','usertype']
class DoctorCreation(ModelForm):
class Meta:
model = Doctor
fields = ['user','deg']
class PatientCreation(ModelForm):
class Meta:
model = Patient
fields = ['dob', 'contact','user']
The view handling this URL is :-
def registerUser(request):
page = 'general'
form = MyUserCreation()
if request.method == 'POST':
form = MyUserCreation(request.POST)
if form.is_valid:
user = form.save(commit=False)
user.save()
login(request, user)
return redirect('home')
else:
messages.error(request, 'Error occured')
if user.usertype == 'p':
page = 'patient'
form = PatientCreation()
form = PatientCreation(request.POST)
if form.is_valid:
form.save()
elif user.usertype== 'd':
page = 'doctor'
form = DoctorCreation()
form = DoctorCreation(request.POST)
if form.is_valid:
form.save()
context = {'form':form, 'page':page}
return render(request, 'rec/register_user.html', context )
The front end for this project is handled with very basic HTML.
Also, if possible I want the front end such that every time a user is registered and the usertype is selected(which is a dropdown menu) some more fields show up depending on the usertype selection by the user. If selected Doctor the additional fields respective to the Doctor module show up, and same for the patient module.
To keep it simple on the front end this solution works like:
Loads Page with User Form
Submit User Form
Uses value to Load Next form
Submit Next Form + Redirect
Notes:
Uses the Values POSTed to determine what form is being submitted
Uses Initial to set User for the 2nd Form
This current flow could be broken up into 3 view with their own distinct URLs
Django View
def registerUser(request):
form = None
if request.method == 'POST':
valid = False
if 'usertype' in request.POST:
# 1st form submit
form = MyUserCreation(request.POST)
if form.is_valid:
valid = True
user = form.save(commit=False)
user.save()
login(request, user)
# Get 2nd form for load
if user.usertype == 'p':
page = 'patient'
form = PatientCreation(initial={'user':user})
elif user.usertype== 'd':
page = 'doctor'
form = DoctorCreation(initial={'user':user})
else:
# 2nd form submit
if 'dob' in request.POST:
form = PatientCreation(request.POST)
if form.is_valid:
form.save()
valid = True
elif 'deg' in request.POST:
form = DoctorCreation(request.POST)
if form.is_valid:
form.save()
valid = True
if valid:
# form sequence done
return redirect('home')
if not valid:
# a form failed somewhere
print(form.errors)
messages.error(request, 'Error occured')
if form == None:
page = 'general'
form = MyUserCreation()
context = {'form':form, 'page':page}
return render(request, 'rec/register_user.html', context )
Basic Django HTML Form
<form action="" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
Now you could make this a single page by making the Template more complex, with JQuery Hiding/Showing extra fields based on dropdown Value on Change, but I assumed this would be the route you wanted.
Edit
To make the field disabled you'd just edit the forms.py
Note: the form-control is just showing that you can also add classes + extra attributes if you need to
class PatientForm(forms.ModelForm):
class Meta:
model = RunRequest
fields = (
'user',
'dob',
'contact',
)
def __init__(self, *args, **kwargs):
super(PatientForm, self).__init__(*args, **kwargs)
self.fields['user'].widget.attrs={'class': 'form-control', 'disabled':True}
i'm trying to allow a user to update their user profile with a city, description, website address etc.
Using Django 2.0, I have two forms in one view:
EditProfileForm (EPF) = Form for email, first and last name and password
The EditProfileForm seems to be able to save data. However, EditUserProfile seems to not.
EditUserProfile (EUP) = Form for further user info such as city, description, website address etc.
When entering the data and submitting the form, the data for EUP form doesn't appear to save or update the user information
I've also tried methods such as:
if form_EUP.is_valid():
obj = form_EUP.save(commit=False)
obj.user = request.user
obj.save()
and trying to create a similar custom save method to the format used in RegistrationForm but i've had no luck
I'm a bit of a beginner to the django framework so any ideas would be much appreciated, cheers.
views.py
def edit_profile(request):
if request.method == 'POST':
form_EPF = EditProfileForm(request.POST, instance=request.user)
form_EUP = EditUserProfile(request.POST, instance=request.user)
if form_EPF.is_valid():
form_EPF.save()
return redirect(reverse('accounts:view_profile'))
if form_EUP.is_valid():
form_EUP.save()
return redirect(reverse('accounts:view_profile'))
else:
form_EPF = EditProfileForm(instance=request.user)
form_EUP = EditUserProfile(instance=request.user)
args = {'form_EPF': form_EPF, "form_EUP": form_EUP}
return render(request, 'accounts/edit_profile.html', args)
forms.py
class RegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = (
'username',
'first_name',
'last_name',
'email',
'password1',
'password2'
)
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
class EditProfileForm(UserChangeForm):
class Meta:
model = User
fields = (
'email',
'first_name',
'last_name',
'password',
)
class EditUserProfile(ModelForm):
description = forms.CharField(required=False)
city = forms.CharField(required=False)
website = forms.URLField(required=False)
class Meta:
model = UserProfile
fields = (
'description',
'city',
'website',
'image',
)
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
description = models.CharField(max_length=100, default='')
city = models.CharField(max_length=100, default='')
website = models.URLField(default='')
phone = models.IntegerField(default=0)
image = models.ImageField(upload_to='profile_image', blank=True)
def __str__(self):
return self.user.username
def create_profile(sender, **kwargs):
if kwargs['created']:
user_profile = UserProfile.objects.create(user=kwargs['instance'])
post_save.connect(create_profile, sender=User)
edit_profile.html
{% block body %}
<div class="container">
<h1>Edit profile</h1>
<form method="post">
{% csrf_token %}
{{ form_EPF.as_p }}
{{ form_EUP.as_p }}
<button type="submit">Submit</button>
</form>
</div>
{% endblock %}
Here:
if form_EPF.is_valid():
form_EPF.save()
return redirect(reverse('accounts:view_profile')) # !!!!!
if form_EUP.is_valid():
form_EUP.save()
return redirect(reverse('accounts:view_profile'))
You are returning just after saving the user form, so the profile form is indeed never saved (unless the user form is invalid xD).
You want:
if form_EPF.is_valid() and form_EUP.is_valid():
form_EPF.save()
form_EUP.save()
return redirect(reverse('accounts:view_profile')) # !!!!!
Also, you have this which won't work:
form_EUP = EditUserProfile(request.POST, instance=request.user)
you're passing a ̀Userasinstance, but the form expects aUserProfile`.
And - while it won't prevent your code from running - you definitly want to think twice about your naming. "EditProfileForm" edits the User so it should really be named "EditUserForm" (or "UserEditForm" etc), and the variables in your view aren't any better - ̀form_EPFandform_EUPrequires active thinking to parse the suffix and match it with the (already counter-intuitive) form class names, and you _don't_ want to have to do any effort to understand what a name means when reading code. Just use the all_lower form of the class instead, so assumin you renamed your form classes to a much sanerUserEditFormandProfileEditForm, the variables would beuser_edit_formandprofile_edit_form`. That's a couple additional keystrokes indeed but on average you spend about 1000 times more time reading code than writing it so it's really worth optimizing for reading.
I have my custom user model:
class RemoteUser(AbstractUser):
class Meta:
verbose_name= 'MyUser'
verbose_name_plural = 'MyUsers'
# Custom fields here...
How do I set up my user admin form to edit user details hiding the password?
class RemoteUserForm(UserCreationForm):
# What do I put here?
Even if I exclude password from fields it keeps giving me KeyError 'password1'.
Use ModelForm, its very simple using that. You create a form class for editing user objects, in fields you can specifiy whichever you want to edit. Use this form in view to save the input data and use the context in template to complete the cycle.
Forms.py
from django.contrib.auth.models import User
class EditUserForm(forms.ModelForm):
class Meta:
model = User
fields = {'username'}
Views.py
def update_userprofile(request, pk):
user = User.objects.get(pk=pk)
user_form = EditUserForm(instance=user)
if request.user.id == user.id:
if request.method == "POST":
user_form = EditUserForm(request.POST, instance=user)
if user_form.is_valid():
created_user = user_form.save(commit=False)
return redirect('someview') #wherever you want
return render(request, "app_name/update_userprofile.html", {
"noodle": pk,
"noodle_form": user_form,}
else:
raise PermissionDenied
Update_userprofile.html
<form method="post">
{% csrf_token %} {{ noodle_form.as_p }}
<button type="submit">Submit</button>
</form>
I had to create a form from which some details go to default.auth.user model and some to my custom model so after searching from various sources I did this:
Django Version :1.7
model.py
class UserProfile(models.Model):
user = models.OneToOneField(User)
title_id = models.ForeignKey('Title')
mobile_number = models.CharField(max_length=10)
alternate_number = models.CharField(max_length=10)
date_of_birth = models.DateField()
profession_id = models.ForeignKey('Profession', null=True, blank=True)
house_no = models.CharField(max_length=100, blank=True, default='NA')
city_id = models.ForeignKey('City', null=True)
country_id = models.ForeignKey('Country', null=True)
state_id = models.ForeignKey('State', null=True)
locality_id = models.ForeignKey('Locality', null=True)
profile_picture_path = models.CharField(max_length=100, blank=True, default='NA')
forms.py:
class UserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput(attrs={'id': 'password'}))
email = forms.CharField(widget=forms.TextInput(attrs={'id': 'email_id'}))
username = forms.CharField(widget=forms.TextInput(attrs={'id': 'username'}))
first_name = forms.CharField(widget=forms.TextInput(attrs={'id': 'first_name'}))
last_name = forms.CharField(widget=forms.TextInput(attrs={'id': 'last_name'}))
class Meta:
model = User
fields = ('email', 'username', 'first_name', 'last_name', 'password')
class ExtraDetailsForm(UserForm):
confirm_password = forms.CharField(widget=forms.PasswordInput(attrs=
{'id':'confirm_password'}),max_length=32,
required=True,)
class Meta:
model = UserProfile
fields = ('email', 'username', 'title_id', 'first_name', 'last_name',
'password', 'confirm_password',
'date_of_birth', 'mobile_number', )
My view.py is :
def register(request):
# A boolean vakue for telling whether the registration was successful
registered = False
if request.method == 'POST':
user_form = UserForm(data=request.POST)
additional_details_form = ExtraDetailsForm(data=request.POST)
if user_form.is_valid() and additional_details_form.is_valid():
user = user_form.save()
user.set_password(user.password)
user.save()
additional_details = additional_details_form.save(commit=False)
additional_details.user = user
additional_details.save()
registered = True
else:
print(user_form.errors, additional_details_form.errors)
else:
user_form = UserForm
additional_details_form = ExtraDetailsForm
return render(request,
'users/register.html',
{'user_form' : user_form, 'additional_details_form': additional_details_form, 'registerered': registered})
regsiter.html:
{% if registerered %}
<p>Thank you for register. check ur email , entered email was</p>
{% else %}
<form action="/users/register/" method="post">{% csrf_token %}
{{ additional_details_form.as_p }}
<input type="submit" value="Register" />
</form>
{% endif %}
Now the good thing is that everything is working fine and details are being stored as they should be.
But the bad thing is I do not know whether it is a correct approach or not as I did not find any tutorial/resource where this approach is used?
This is correct approach and you do it almost right. Just couple notes:
if user_form.is_valid() and additional_details_form.is_valid():
In this line if user_form is invalid then validation for additional_details_form will not run. To always validate both change it to:
if all([user_form.is_valid(), additional_details_form.is_valid()]):
In else statement you set form class to *_form variables. It should be form instances instead:
user_form = UserForm()
additional_details_form = ExtraDetailsForm()
And it may be a good idea to wrap your save code into one transaction :-)
I would recommend that you use just one form here that contains all fields.
There is no benefit to using two forms, especially since one inherits the other, this is odd behaviour when you are then passing the POST data into each of them.
Consolidate the fields into a single form and then override the 'clean' method of the form to be able to check that the two password fields match.
You can create a single form to save data into one or many different models and this is especially useful in your case since you need to validate the data for these different models together.
Ok, firstly ExtraDetailsForm shouldn't inherit from UserForm because they are for different models. It should look something like this instead:
class UserForm(forms.ModelForm):
confirm_password = forms.CharField(widget=forms.PasswordInput(attrs=
{'id':'confirm_password'}),max_length=32,
required=True,)
class Meta:
model = User
fields = ('email', 'username', 'first_name', 'last_name', 'password',
'confirm_password')
class ExtraDetailsForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('title_id', 'date_of_birth', 'mobile_number')
Then in your view:
from django.contrib.auth import login
from django.shortcuts import redirect, render
def register(request):
user_form = UserForm(data=request.POST or None)
profile_form = ExtraDetailsForm(data=request.POST or None)
if all([user_form.is_valid(), profile_form.is_valid()]):
user = user_form.save(commit=False)
user.set_password(user.password)
user.save()
profile = profile_form.save()
# probably at this point you want to login the new user:
login(request, user)
# it's good practice to do a redirect here, after a successful
# form post, eg to display success page, as this will
# prevent accidental re-posting data if user reloads the page
return redirect('registration_success')
else:
print(user_form.errors, profile_form.errors)
return render(
request,
'users/register.html',
{
'user_form' : user_form,
'profile_form' : profile_form,
}
)
def registration_success(request):
return render('registration_success.html')
Finally you need to output both forms in the template:
<form action="/users/register/" method="post">{% csrf_token %}
{{ user_form.as_p }}
{{ profile_form.as_p }}
<input type="submit" value="Register" />
</form>
and a new template registration_success.html:
<p>Thank you for registering. Check your email, entered email was: {{ request.user.email }}</p>
I'm trying to limit number of "categories" that user have available when entering new "feed" only to categories that he owns and he created. The way it works now is that user can add "feed" to other users' "categories" as this is what the form displays. How can I fix it ?
thanks!
-M
models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=50)
user = models.ForeignKey(User)
class Feed(models.Model):
url = models.URLField()
name = models.CharField(max_length=50)
created = models.DateTimeField(auto_now_add=True)
description = models.TextField(blank=True)
category = models.ForeignKey(Category)
user = models.ForeignKey(User)
forms.py
class FeedForm(forms.ModelForm):
class Meta:
model = Feed
exclude = ['user']
views.py
def addfeed(request, user):
user = request.user
page_title = "Add feed"
instance = Category.objects.filter(user=request.user)
if request.method == 'POST':
form = FeedForm(request.POST, instance=instance)
if form.is_valid():
feed = form.save(commit=False)
feed.user = request.user
feed.save()
return HttpResponseRedirect("/user/" + user.username + "/manage")
else:
form = FeedForm()
return render(request, "form_manage.html", {
'page_title': page_title,
'form': form,
})
Set the queryset attribute of the field somewhere. Because it depends on your user, it's something you have to set during or after instantiating the form. For instance, here's how to do it in the view:
def addfeed(request, user):
user = request.user # why does this view take user as an arg and then reassign?
page_title = "Add feed"
categories = Category.objects.filter(user=request.user)
if request.method == 'POST':
form = FeedForm(request.POST)
form.fields['category'].queryset = categories
if form.is_valid():
feed = form.save(commit=False)
feed.user = request.user
feed.save()
return HttpResponseRedirect("/user/" + user.username + "/manage")
else:
form = FeedForm()
form.fields['category'].queryset = categories
return render(request, "form_manage.html", {
'page_title': page_title,
'form': form,})
I removed the instance argument to your POST case's form construction because that's meant for passing in an existing Feed instance, not a categories queryset.
You could also do this in the form's __init__ if you pass in the correct categories queryset.
I use javascript to do this. For example, you could pass a list of the relevant categories as extra context in your view then use javascript in your template to empty the pre-populated option field in the form and replace it with your extra context.