Reputation: 20163
I have this form, as pretty much straight-copied from the second green box on this official Django doc page:
class UserForm(forms.ModelForm):
password1 = forms.CharField(label="Password", widget=forms.PasswordInput())
password2 = forms.CharField(label="Password confirmation", widget=forms.PasswordInput())
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2')
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("Passwords don't match")
return password2
def save(self, commit=True):
# Save the provided password in hashed format
user = super(UserForm, self).save(commit=False)
user.set_password(self.cleaned_data["password2"])
if commit:
user.save()
return user
The form displays properly, and catches errors as expected (like "passwords do not match"), but when attempting to login with the just created user/pass, it fails. I can reset the password via email, and then successfully log in.
Why is the form not saving the password correctly?
(Does the user's "activeness" matter here?)
(Also, I understand there's an auth form that already does this create-a-user-account-with-password-confirmation, but I don't see it on https://docs.djangoproject.com/en/1.7/topics/auth/default/#built-in-auth-views
)
views.py
def create_user_account_success(request):
return render_to_response("registration/create_account_success.html", RequestContext(request))
MIDDLE_YEAR_STR = str(DISCOVERED_MIN_YEAR + ((DISCOVERED_MAX_YEAR - DISCOVERED_MIN_YEAR) // 2))
def create_user_account(request):
context = RequestContext(request)
if(request.method == "POST"):
#Form was filled. Process it.
user_form = UserForm(data=request.POST)
profile_form = UserProfileForm(data=request.POST)
if(user_form.is_valid() and profile_form.is_valid()):
#To get a form element, use either
#print(request.POST["password1"])
#or, *after* calling UserForm(data=request.POST) and then
#user_form.is_valid():
#print(user_form.cleaned_data["password1"])
#commit
user = user_form.save()
user.set_password(user.password)
user.save()
profile = profile_form.save(commit=False)
profile.user = user
if("picture" in request.FILES):
profile.picture = request.FILES["picture"]
profile.save()
return redirect("create_account_success")
else:
#Not a POST. Form hasn't been filled. Get a blank form
global MIDDLE_YEAR_STR
user_form = UserForm()
profile_form = UserProfileForm(initial={
"year_discovered": MIDDLE_YEAR_STR})
context["user_form"] = user_form
context["profile_form"] = profile_form
#Render and display the form
return render_to_response("registration/create_account.html", context)
forms.py
from django import forms
from django.contrib.auth.models import User
from .models import UserProfile, DISCOVERED_MIN_YEAR, DISCOVERED_MAX_YEAR
class UserForm(forms.ModelForm):
password1 = forms.CharField(label="Password", widget=forms.PasswordInput())
password2 = forms.CharField(label="Password confirmation", widget=forms.PasswordInput())
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2')
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("Passwords don't match")
return password2
def save(self, commit=True):
# Save the provided password in hashed format
user = super(UserForm, self).save(commit=False)
user.set_password(self.cleaned_data["password2"])
if commit:
user.save()
return user
class UserProfileForm(forms.ModelForm):
year_choices = ((x,str(x)) for x in range(DISCOVERED_MIN_YEAR, DISCOVERED_MAX_YEAR+1))
#year_discovered = forms.ChoiceField(choices=year_choices)
year_discovered = forms.IntegerField(required=False,
min_value=DISCOVERED_MIN_YEAR, max_value=DISCOVERED_MAX_YEAR,
widget=forms.Select(choices=year_choices))
#year_discovered = forms.IntegerField(min_value=DISCOVERED_MIN_YEAR, max_value=DISCOVERED_MAX_YEAR)
class Meta:
model = UserProfile
fields = ('year_discovered', 'profile_picture')
create_account.html
{% extends "base.html" %}
{% block title %}Create account{% endblock %}
{% block content %}
<h1>Billy Joel Album Browser: Create account</h1>
{% if registration_was_successful %}
<P>Success!</P>
{% else %}
<form id="user_form" method="post" action="{% url 'create_account' %}"
enctype="multipart/form-data">
{% csrf_token %}
<!-- Display each form. The as_p method wraps each element in a paragraph
(<p>) element. This ensures each element appears on a new line,
making everything look neater. -->
{{ user_form.as_p }}
{{ profile_form.as_p }}
<!-- Provide a button to click to submit the form. -->
<input type="submit" name="submit" value="Register" />
</form>
{% endif %}
{% endblock %}
create_account_succes.html
{% extends "base.html" %}
{% block content %}
<H1>Welcome!</H1>
<P><a href="{% url 'login' %}">Login</A> to proceed.</P>
{% endblock %}
Upvotes: 1
Views: 60
Reputation: 599926
You have some duplication in your password-setting code between the form and the view, which is causing your problem.
In the form's save
method, you correctly call user.set_password()
to set the hashed password onto the newly-created user. But then, your view calls set_password
again: so what it does is take the already-hashed password, hash it again, then set the doubly-hashed password onto the user. Of course, when you come to log in, the password no longer matches, because it has been double-hashed.
Just remove the two lines (user.set_password
and user.save
) from your view.
Upvotes: 1