Search code examples
django-formsdjango-validationvalidationerror

How to display field level validation errors in a manually rendered form template in Django


I have a Sign Up form for new user creation. I have created the form manually in the html template as I don't wish Django to generate it automatically. But I am not being able to catch and display the validation errors in my template. For testing purpose, I have just added one validator usernameValidator in my forms.py which is getting called nicely but not able to display the error message in the template. How best can I display the errors.

What I have tried so far:

signup.html:

{% extends "login_base.html" %}

{% block content %}

<div class="register-box">
    <div class="card card-outline card-primary">
        <div class="card-body">
            <p class="login-box-msg">Register a new membership</p>
            <p>Validation Messages:
                {{ form.non_field_errors }}
                {% for field, errors in form.errors.items %}
                    {% for error in errors %}
                        {{field.label}}: {{error|escape}}
                    {% endfor %}
                {% endfor %}
            </p>
            {% include "messages.html" %}
            <form method="post">
                {% csrf_token %}
                <div class="input-group mb-3">
                    {{ form.first_name }}
                    <div class="input-group-append">
                        <div class="input-group-text">
                            <span class="fas fa-user"></span>
                        </div>
                    </div>
                    {{ form.first_name.errors }}                    
                </div>
                <div class="input-group mb-3">
                    {{ form.last_name }}
                    <div class="input-group-append">
                        <div class="input-group-text">
                            <span class="fas fa-user"></span>
                        </div>
                    </div>
                    {{ form.last_name.errors }} 
                </div>
                <div class="input-group mb-3">
                    {{ form.username }}
                    <div class="input-group-append">
                        <div class="input-group-text">
                            <span class="fas fa-user"></span>
                        </div>
                    </div>
                    {{ form.username.errors }} 
                </div>
                <div class="input-group mb-3">
                    {{ form.email }}
                    <div class="input-group-append">
                        <div class="input-group-text">
                            <span class="fas fa-envelope"></span>
                        </div>
                    </div>
                    {{ form.email.errors }} 
                </div>
                <div class="input-group mb-3">
                    {{ form.password1 }}
                    <div class="input-group-append">
                        <div class="input-group-text">
                            <span class="fas fa-lock"></span>
                        </div>
                    </div>
                    {{ form.password1.errors }} 
                </div>
                <div class="input-group mb-3">
                    {{ form.password2 }}
                    <div class="input-group-append">
                        <div class="input-group-text">
                            <span class="fas fa-lock"></span>
                        </div>
                    </div>
                    {{ form.password2.errors }} 
                </div>
                <div class="row">
                    <div class="col-8">
                        <div class="icheck-primary">
                            <input type="checkbox" id="agreeTerms" name="terms" value="agree">
                            <label for="agreeTerms">
                                I agree to the <a href="#">terms</a>
                            </label>
                        </div>
                    </div>
                    <!-- /.col -->
                    <div class="col-4">
                        <button type="submit" class="btn btn-primary btn-block">Register</button>
                    </div>
                    <!-- /.col -->
                </div>
            </form>

            <a href="{% url 'signin' %}" class="text-center">I already have a membership</a>
        </div>
        <!-- /.form-box -->
    </div><!-- /.card -->
</div>
<!-- /.register-box -->
{% endblock content %}

views.py:

from django.http import HttpResponse
from django.contrib.auth import login, authenticate
from django.shortcuts import render, redirect
from accounting_app.forms import *
from django.contrib import messages


def index(request):
    return render(request, 'index.html')


def signUpForm(request):
    formObj = SignUpForm()  
    if request.method == 'POST':
        formObj = SignUpForm(request.POST)
        if formObj.is_valid():
            formObj.save()
            messages.success(request, 'You have signed up successfully!')
            raw_username = form.cleaned_data.get('username')
            raw_password = form.cleaned_data.get('password1')
            user = authenticate(
                username = raw_username,
                password = raw_password
            )
            login(request, user)
            return redirect('/')
        else:
            print(formObj.errors)

    context = {
        'form': formObj
    }

    return render(request, 'registration/signup.html', context)    

forms.py:

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
# from django.core.exceptions import ValidationError
from django.forms import ValidationError

def usernameValidator(inputValue):
    # username = str(inputValue)
    if(len(inputValue)<8):
        print("############## Username must contain at least 8 characters!")        
        raise forms.ValidationError({'id_username', "at least 8 chars!!"})



class SignUpForm(UserCreationForm):
    print("##############  Inside forms.py > SignUpForm() method")
    first_name = forms.CharField(max_length=30, required=True, help_text='First Name', 
                                 widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'First Name'}))
    last_name  = forms.CharField(max_length=30, required=True, help_text='Last Name',
                                 widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'Last Name'}))
    username   = forms.CharField(max_length=30, required=True, help_text='Username',
                                 widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'Username'}),
                                 validators=[usernameValidator]
                                 )
    email      = forms.EmailField(max_length=254, required=True, help_text='Valid Email Address', 
                                  widget=forms.EmailInput(attrs={'class':'form-control', 'placeholder':'Email Address'}))
    password1  = forms.CharField(widget=forms.PasswordInput(attrs={'class':'form-control', 'placeholder':'Password'}))
    password2  = forms.CharField(widget=forms.PasswordInput(attrs={'class':'form-control', 'placeholder':'Retype Password'}))

    class Meta:
        model = User
        fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2',)

Updated forms.py (working):

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.forms import ValidationError

class SignUpForm(UserCreationForm):
    first_name = forms.CharField(max_length=30, required=True, help_text='First Name', widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'First Name'}))
    last_name  = forms.CharField(max_length=30, required=True, help_text='Last Name', widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'Last Name'}))
    username   = forms.CharField(max_length=30, required=True, help_text='Username', widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'Username'}))
    email      = forms.EmailField(max_length=254, required=True, help_text='Valid Email Address', widget=forms.EmailInput(attrs={'class':'form-control', 'placeholder':'Email Address'}))
    password1  = forms.CharField(widget=forms.PasswordInput(attrs={'class':'form-control', 'placeholder':'Password'}))
    password2  = forms.CharField(widget=forms.PasswordInput(attrs={'class':'form-control', 'placeholder':'Retype Password'}))

    class Meta:
        model = User
        fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2',)

    def clean_first_name(self):
        first_name = self.cleaned_data["first_name"]
        if(len(first_name)<2):
            raise ValidationError("First Name must contain at least 2 characters!")
        else:
            return first_name 

    def clean_username(self):
        username = self.cleaned_data["username"]
        if(len(username)<2):
            raise ValidationError("Username must contain at least 8 characters!")
        else:
            return username    

    def clean_email(self):
        email = self.cleaned_data["email"]
        if not("@gmail.com" in email):
            raise ValidationError("Email must be of gmail.com domain!")
        else:     
            return email

Solution

  • You have multiple ways of throwing a ValidationError

    A: A reusable Validator (that's what you did) is useful if you want to use it on many fields of your model, or even multiple different models. Since the goal of these validators is to be reusable for multiple fields you should not hardcode a binding to a specific field in it. Django assigns the error to the correct field all by itself.

    from django.core.exceptions import ValidationError
    
    def usernameValidator(inputValue):
        if len(inputValue) < 8:
            print("############## Username must contain at least 8 characters!")  # delete this print statment          
            raise forms.ValidationError(
                "Username must contain at least 8 characters!", 
                params ={'value': inputValue}
            )
    

    This should work in your case. But there is a better way for you in my opinion because

    B: Specific clean methods inside of the form or model allow to keep the logic closer to your actual model or form. Either in your user-model or in that form you can implement a method like this:

    def clean_username(self):
        if len(self.username) < 8:
            raise ValidationError(
                {'username': "Username must contain at least 8 characters!"}
            )
    

    With this option you have to define the field. But remember: the field is called "username" and not "username_id" as in your proposed code snippet of the ValidationError.

    C: Third option would be to through the ValidationError in the forms or models clean() method. But you would only want to do checks there that check up on interactions between fields.

    Let me know if this works out for you!