aliteralmind
aliteralmind

Reputation: 20163

Password on create-an-account page not being properly saved to database

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

Answers (1)

Daniel Roseman
Daniel Roseman

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

Related Questions