user1852176 user1852176 - 3 months ago 32
Python Question

Django form input missing in POST data

I'm using Django 1.9.8 and I'm trying to learn how to use form validation. I'm working on a form for registering users. I have 2 problems that I'm stuck on.


  1. The field for re-entering the password isn't accessible (rather, just not there at all) in the forms.py file methods. When I use
    raise Exception(self.cleaned_data.get('password'))
    to view the content, the password is shown. I can also view the username and email. When I do the same for the repassword field, it shows
    None
    Edit - this was solved by changing the forms.py file to use a clean() method

  2. The validation errors are not displaying at all on my form. If there are errors the redirect back to the form is working, but there is nothing in the inputs for the user to fix, nor is the error displayed. Edit - solved by changing the RegisterForm call after the else statement to
    form = RegisterForm(request.POST)



Here is the forms.py file

#forms.py
class RegisterForm(forms.Form):
username = forms.CharField(label="Username", max_length=30,
widget=forms.TextInput(attrs={'class': 'form-control', 'name': 'username'}))
email = forms.CharField(label="Email", max_length=30,
widget=forms.TextInput(attrs={'class': 'form-control', 'name': 'email'}))
password = forms.CharField(label="Password", max_length=30,
widget=forms.TextInput(attrs={'class': 'form-control', 'name': 'password', 'type' : 'password'}))
repassword = forms.CharField(label="RePassword", max_length=30,
widget=forms.TextInput(attrs={'class': 'form-control', 'name': 'repassword', 'type' : 'password'}))

#Edit - this was changed to the clean() function in the selected answer.
def clean_password(self):
password1 = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('repassword')
#raise Exception(self.cleaned_data.get('repassword')) # None is displayed

if password1 and password1 != password2:
raise forms.ValidationError("Passwords don't match")
return self.cleaned_data


Here's the form template

#register.html

{% if form.errors %}
<h2>ERROR!</h2>
{% for field in form %}
{% for error in field.errors %}
<div class="alert alert-error">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endfor %}
{% endif %}

<form method="post" action="" id="RegisterForm">
{% csrf_token %}
<p class="bs-component">
<table>
<tr>
<td>{{ form.username.label_tag }}</td>
<td>{{ form.username }}</td>
</tr>
<tr>
<td>{{ form.email.label_tag }}</td>
<td>{{ form.email }}</td>
</tr>
<tr>
<td>{{ form.password.label_tag }}</td>
<td>{{ form.password }}</td>
</tr>
<tr>
<td>{{ form.repassword.label_tag }}</td>
<td>{{ form.repassword }}</td>
</tr>
</table>
</p>
<p class="bs-component">
<center>
<input class="btn btn-success btn-sm" type="submit" value="Register" />
</center>
</p>
<input type="hidden" name="next" value="{{ next }}" />
</form>


Here's the view

#views.py
class RegisterViewSet(viewsets.ViewSet):

#GET requests
def register(self,request):
return render(request, 'authorization/register.html', {'form': RegisterForm})

#POST requests
def create(self,request):
form = RegisterForm(request.POST)
if form.is_valid():
username = request.POST['username']
email = request.POST['email']
password = request.POST['password']
user = User.objects.create_user(username,email,password)
user.save()
return HttpResponseRedirect('/users')
else:
return render(request, 'authorization/register.html', {'form': RegisterForm })
#changed the previous line to the following. This fixed the errors not displaying
#return render(request, 'authorization/register.html', {'form': RegisterForm(request.POST) })

Answer

I am not %100 sure why self.cleaned_data.get('repassword') returns None in that method, however clean_password is not the right place for performing validations of fields that depend on each other.

According to docs, you should perform that kind of validation in clean() function:

views.py

def register(self,request):
    form = RegisterForm() # Notice `()` at the end
    return render(request, 'authorization/register.html', {'form': form})

forms.py

...

def clean(self):
    cleaned_data = super(RegisterForm, self).clean()

    password1 = cleaned_data.get('password')
    password2 = cleaned_data.get('repassword')

    if password1 and password1 != password2:
        raise forms.ValidationError("Passwords don't match")

Please note that you don't have to implement clean_password and clean_repassword.

(If you still have to implement clean_password, you have to return password1 from it, not self.cleaned_data.)

You also need to render form errors correctly as described in the docs.

Don't forget to add it in your template:

{{ form.non_field_errors }}

As for the second error, the problem is, every time the validation fails you are returning a new fresh RegisterForm instance instead of the invalidated one.

You should change the line in create() function:

return render(request, 'authorization/register.html', {'form': RegisterForm}) 

to this:

return render(request, 'authorization/register.html', {'form': form})