10

This is my view:

def main_page(request):

    if request.method == 'POST':
        form = RegistrationForm(request.POST)
        if form.is_valid():
            user = User.objects.create_user(
                username=form.clean_data['username'],
                password=form.clean_data['password1'],
                email=form.clean_data['email']
            )
        return HttpResponseRedirect('/')
    else:
        form = RegistrationForm()
        variables = {
            'form': form
        }
        return render(request, 'main_page.html', variables)

and this is my main_page.html:

{% if form.errors %}
    <p>NOT VALID</p>
    {% for errors in form.errors %}
        {{ errors }}
    {% endfor %}
{% endif %}
<form method="post" action="/">{% csrf_token %}
    <p><label for="id_username">Username:</label>{{ form.username }}</p>
    <p><label for="id_email">Email Address:</label>{{ form.email }}</p>
    <p><label for="id_password">Password:</label>{{ form.password1 }}</p>
    <p><label for="id_retypePassword">Retype Password:</label>{{ form.password2 }}</p>
    <input type="hidden" name="next" />
    <input type="submit" value="Register" />
</form>

When I go to the url which uses the main_page view, it just displays the form. When I submit the form with errors (with blank fields and without a proper email address) it just redirects me to the same page and doesn't display any errors. It doesn't even say "NOT VALID".

When I change

{% if form.errors %}

to

{% if not form.is_valid %}

it always says "NOT VALID" (even if it is the first time going to the url and even if I didn't submit anything yet).

This is my RegistrationForm:

from django import forms
import re
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist

class RegistrationForm(forms.Form):
    username = forms.CharField(label='Username', max_length=30)
    email = forms.EmailField(label='Email')
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput())
    password2 = forms.CharField(label='Password (Again)', widget=forms.PasswordInput())

    def clean_password2(self):
        if 'password1' in self.cleaned_data:
            password1 = self.cleaned_data['password1']
            password2 = self.cleaned_data['password2']
            if password1 == password2:
                return password2
        raise forms.ValidationError('Passwords do not match.')

        def clean_username(self):
        username = self.cleaned_data['username']
        if not re.search(r'^\w+$', username): #checks if all the characters in username are in the regex. If they aren't, it returns None
            raise forms.ValidationError('Username can only contain alphanumeric characters and the underscore.')
        try:
            User.objects.get(username=username) #this raises an ObjectDoesNotExist exception if it doesn't find a user with that username
        except ObjectDoesNotExist:
            return username #if username doesn't exist, this is good. We can create the username
        raise forms.ValidationError('Username is already taken.')
SilentDev
  • 20,997
  • 28
  • 111
  • 214

3 Answers3

14

It is redirecting you because you always return HttpResponseRedirect if the method is POST, even if the form is not vaild. Try this:

def main_page(request):
    form = RegistrationForm()
    if request.method == 'POST':
        form = RegistrationForm(request.POST)
        if form.is_valid():
            user = User.objects.create_user(
                username=form.clean_data['username'],
                password=form.clean_data['password1'],
                email=form.clean_data['email']
            )
            return HttpResponseRedirect('/')        
    variables = {
        'form': form
    }
    return render(request, 'main_page.html', variables)

That way, the form instance, on which is_valid was called, is passed to the template, and it has a chance to display the errors. Only if the form is valid, the user is redirected. If you want to be fancy, add a message using the messages framework before redirecting.

If you want it a little bit more concise:

def main_page(request):
    form = RegistrationForm(request.POST or None)
    if form.is_valid():
        user = User.objects.create_user(
            username=form.clean_data['username'],
            password=form.clean_data['password1'],
            email=form.clean_data['email']
        )
        return HttpResponseRedirect('/')        
    variables = {
        'form': form
    }
    return render(request, 'main_page.html', variables)
sk1p
  • 6,645
  • 30
  • 35
  • right, that's what I initially had but what happens "if request.method == 'POST'" but the form is not valid? It still needs to return something, right? I tried your view and it gives a ValueError if the form is not valid,the error says that "the main_page view didn't return an HttpResponse object". – SilentDev Jan 14 '14 at 23:26
  • from my undestanding, I wanted it to always redirect to the main page even if the form is not valid because then I would be able to display the errors in the form on the main page right? And if it is valid, then that's good. If it's not, it is still good because it will redirect to the main page with form.errors is what I thought. It's weird because even after I hit submit with form errors (using the view which I posted), the template doesn't display form errors so it's like the view doesn't realize that request.method == 'POST'. – SilentDev Jan 14 '14 at 23:30
  • 1
    Well, `if request.method == "POST"` and `not form.is_valid`, the execution just continues below, and finally returns the result of the `render` function, so it should work...? – sk1p Jan 14 '14 at 23:34
  • 1
    You don't want to redirect if you got an error, because by redirection, you lose all your state: [HTTP is stateless](http://stackoverflow.com/a/4913942/540644). After the browser POSTs your form to the server, and the server responds with the redirect, the browser doesn't POST to the new url; it will issue a GET request. – sk1p Jan 14 '14 at 23:37
  • Ah, you are correct! when I said "right, that's what I initially had", turns out it wasn't exactly what I initially had. I initially had an "else: variables = { 'form': form } return ...." and that's why it gave me a ValueError when request.method=="POST" but not form.is_valid. Okay thanks. – SilentDev Jan 14 '14 at 23:47
  • For me the page just gets refreshed if it's invalid. – AnonymousUser Apr 15 '22 at 07:27
4

Make your view something like this:

if form.is_valid():
    pass
    # actions
else:
    # form instance will have errors so we pass it into template
    return render(request, 'template.html', {'form': form})

And in the templates you can iterate over form.errors or simple:

{{ forms.as_p }}
xelblch
  • 699
  • 3
  • 11
  • right but there is also the possibility if request.method != "POST" (because it my my main page, so if it is the first time the user just accesses the main page, then he didn't submit anything yet so request.method != "POST" right? – SilentDev Jan 14 '14 at 23:33
  • 1
    Yes, request method would be GET. I recommend you take a look at django class based views. Here's a quick example of form validation without dirty `if request.method == POST` etc: http://pastebin.com/D5YwFLpP If user open page first time method `get` will display form. If user submit form method `post` will validate it and make appropriate actions. – xelblch Jan 14 '14 at 23:41
1

You can reformat your view to display the form errors in the console as below

def main_page(request):

    if request.method == 'POST':
        form = RegistrationForm(request.POST)
        if form.is_valid():
            user = User.objects.create_user(
                username=form.clean_data['username'],
                password=form.clean_data['password1'],
                email=form.clean_data['email']
            )
        else:
            print(form.errors)
            return HttpResponse("Form Validation Error")

        return HttpResponseRedirect('/')
    else:
        form = RegistrationForm()
        variables = {
            'form': form
        }
        return render(request, 'main_page.html', variables)


As always, keep the indent as above. If the form has any error , it will display in the console like

<ul class="errorlist"><li>date<ul class="errorlist"><li>Enter a valid date.</li></ul></li></ul>

Hope it helps

ANFAS PV
  • 293
  • 2
  • 13