Django Form Validation Guide

Table of Contents

Objectives

Form validation is an important feature for web applications, and there are many ways to do it.

In this tutorial, I will talk about some different ways to do form validation in Django, and compare them to help you choose the best way in your project.

After reading this article, you will learn:

  1. What is Built-in Form Validation in HTML
  2. How to use Javascript to do client form validation
  3. How to use jQuery Validation or Parsley to do client form validation, and how they work
  4. How to use Yup to do client form validation
  5. How to use Django Form to do server form validation
  6. How to use Django Rest Framework to do server form validation

Built-in Form Validation

Built-in form validation uses HTML form validation features, which are supported by all modern browsers. It doesn't require any JavaScript.

For example:

  1. required: Specifies whether a form field needs to be filled in before the form can be submitted.
  2. type: Specifies whether the data needs to be a number, an email address, or some other specific preset type.
  3. pattern: Specifies a regular expression that defines a pattern the entered data needs to follow.

Let's create test.html and open it in Chrome

<form>
  <label for="username">Username</label>
  <input id="username" name="username" required />
  <button>Submit</button>
</form>

We add required attribute to the input element, and if we click the Submit button, the browser will check form inputs and display an error message.

Pseudo-Class

During the built-in form validation, if the element is valid, it will match :valid pseudo-class.

If the element is invalid, it will match :invalid pseudo-class.

Let's update test.html and open it in Chrome

<style>
  input:valid {
    background-color: palegreen;
  }

  input:invalid {
    background-color: lightpink;
  }
</style>

<form>
  <label for="username">Username</label>
  <input id="username" name="username" required />
  <button>Submit</button>
</form>

Here we use CSS code to improve the style of the built-in form validation.

You can input username to the input field and you will see the background color will change.

Tailwind CSS

Tailwind also provides peer and peer-invalid, which can help us improve style of the built-in form validation. (https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-sibling-state)

Here is a simple example:

<script src="https://cdn.tailwindcss.com"></script>

<form>
  <label for="username">Username</label>
  <input id="username" name="username" class="peer border border-slate-400" required />
  <p class="mt-2 invisible peer-invalid:visible text-pink-600 text-sm">
    Username is required
  </p>
  <button class="bg-blue-500 text-white px-5 py-1">Submit</button>
</form>

Pros and Cons

Pros:

  1. It is simple to use.
  2. The validation is done by Browser

Cons:

  1. Not flexible, and it's hard to customize.

To know more about the built-in form validation, you can read https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation

Javascript

If we want more control and better user experience, we need Javascript (Constraint Validation API).

Update test.html and open it in Chrome

<form >
  <label for="username">Username</label>
  <input id="username" name="username" pattern="[a-z\d]+" required />
  <button>Submit</button>
</form>

<script>
  const username = document.getElementById("username");

  username.addEventListener("input", (event) => {
    if (username.validity.patternMismatch) {
      username.setCustomValidity("Only letters and numbers are allowed for username");
    } else {
      username.setCustomValidity("");
    }
  });

</script>

Notes:

  1. We add pattern pattern="[a-z\d]+" to the username field.
  2. We use Javascript to handle input event for the username field, if patternMismatch is true, we render specific error message.

Turn Off Built-in Form Validation

<form method="post" novalidate>
  <label for="username">Username</label>
  <input id="username" name="username" pattern="[a-z\d]+" required />
  <div id="usernameError"></div>
  <button>Submit</button>
</form>

<script>
  const username = document.getElementById("username");
  const form = document.querySelector("form");
  const usernameError = document.getElementById("usernameError");

  username.addEventListener("input", (event) => {
    if (username.validity.valid) {
      // reset
      usernameError.textContent = "";
      usernameError.className = "";
    } else {
      displayError();
    }
  });

  form.addEventListener("submit", (event) => {
    if (!username.validity.valid) {
      displayError();
      // prevent form from being submitted
      event.preventDefault();
    }
    // if everything is valid, form will be submitted
  });

  function displayError(){
    if (username.validity.patternMismatch) {
      usernameError.textContent = "Only letters and numbers are allowed for username";
    }

    usernameError.classList.add("error")
  }

</script>

Notes:

  1. If we add novalidate attribute to the form, then browser will not do automatic validation when we submit the form.
  2. We can still access username.validity to check if the field has valid value or not.
  3. In the form submit handler, we check if the field is valid, if not, we display the error and prevent the form submission by calling event.preventDefault()

If you want to do more complex validation, we can do it in this way.

  1. In the field input event handler, we write custom Javascript to do validation
  2. We use an object errorMessages to store the validation error messages for all fields.
  3. In the form submit handler, we check the errorMessages and then decide if we should submit the form or not.

Pros and Cons

Pros:

  1. It is simple to use, we do not need to load additional libraries

Cons:

  1. We need to write a little more Javascript code to do the validation, which is not very convenient.

To learn more, please check Validating forms using JavaScript

jQuery Validation

jQuery Validation is a jQuery plugin that makes simple client side form validation easy, whilst still offering plenty of customization options.

Let's look at the example:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.5/jquery.validate.min.js"></script>

<form method="post">
  <label for="username">Username</label>
  <input id="username" name="username" pattern="[a-z\d]+" required />
  <div id="usernameError"></div>
  <button>Submit</button>
</form>

<script>
  $(document).ready(function () {

    // Add custom validator
    $.validator.addMethod('usernamePattern', function (value) {
      return /[a-z\d]+/.test(value);
    }, 'Only letters and numbers are allowed for username');

    $("form").validate({
      rules: {
        username: {
          required: true,
          usernamePattern: true
        },
      },
      submitHandler: function (form) {
        form.submit();
      }

    });
  });
</script>

Notes:

  1. We add usernamePattern method to the $.validator object.
  2. We use $("form").validate to pass options to the validator.
  3. If client validation passed, we call form.submit() to submit the form.

How jQuery Validation works?

  1. jQuery Validation would listen to the focusin, focusout, keyup events on the form fields, and then run code to validate the field. https://github.com/jquery-validation/jquery-validation/blob/1.19.5/src/core.js#L427
  2. It would also listen to the form submit event, and then run code to validate the form if user try to submit the form. https://github.com/jquery-validation/jquery-validation/blob/1.19.5/src/core.js#L46
  3. If form pass the client validation, it would submit the form or call the submitHandler function.

In jQuery Validation, the validation rules are defined in the Javascript file, if we want to make it read rules from the HTML element attributes, you can check https://github.com/aspnet/jquery-validation-unobtrusive

Pros and Cons

Pros:

  1. It provides many features, and you can quickly get things done.
  2. Very flexible and there are many 3-party resources about it.

Cons:

  1. It depends on jQuery, new projects may not want to use jQuery.

Parsley

Parsley.js is another popular form validation library (which is similar with jQuery Validation).

It can let developers define validation rules in the HTML element directly.

<!-- this optional textarea have a length validator that would be checked on keyup after 10 first characters, with a custom message only for minlength validator -->
<label for="message">Message (20 chars min, 100 max) :</label>
<textarea name="message"
          data-parsley-trigger="keyup"
          data-parsley-minlength="20"
          data-parsley-maxlength="100"
          data-parsley-validation-threshold="10"
          data-parsley-minlength-message="Come on! You need to enter at least a 20 characters long comment..">
</textarea>

Yup

Yup is a JavaScript schema builder for value parsing and validation. Define a schema, transform a value to match, validate the shape of an existing value, or both. Yup schema are extremely expressive and allow modeling complex, interdependent validations, or value transformations.

Yup focus on the value validation, we can use it to validate the form values in flexible ways, and it is a very popular option for some modern frontend frameworks like React, Vue, Angular, etc.

  1. React Solution: Formik Doc
  2. Vue Solution: VeeValidate Doc

We can use Yup to validate the form values, and then display the validation error messages if we need.

// here we can validate in the submit handler, you can also make it work on the field level

// build validation schema
const schema = yup.object().shape({
  username: yup
    .string()
    .required("Username is required")
    .matches(/^[a-zA-Z0-9.\-_]+$/, "Username must contain only letters, numbers, underscore, dot or dash character"),
});

// convert form data to object
const formData = new FormData(formElem);
const objectData = {};
formData.forEach(function(value, key){
  objectData[key] = value;
});

try {
  await schema.validate(objectData, { abortEarly: false });
} catch (error) {
  // display validation error
  console.log(error);
  return;
}

// if validation passed, submit the form

Notes:

  1. Yup can also work with Promise, so we can use it do Ajax validation just like jQuery Validation.
  2. Compared with jQuery Validation, it is more flexible, we can define the validation rules in the Javascript file, or in the HTML element attributes. We can even build domain specific language (DSL) to define the validation rules in our own way.
  3. It only focuses on value validation, so it is not a complete form validation solution, we need to write more code to manipulate DOM elements to display the validation error messages in some cases.

If you want to try Yup in Django, I recommend embrace frontend engineering, and use npm install to install it.

You can check https://github.com/AccordBox/python-webpack-boilerplate for a quick start.

Pros and Cons

Pros:

  1. It provides many features, the schema model makes it easy to customize.

Cons:

  1. Developers need to write some Javascript to display the validation error messages.

Server-side Validation 1: Django Form

Client-side validation should not be considered an exhaustive security measure! Your apps should always perform security checks on any form-submitted data on the server-side as well.

Next, I will talk about some server-side form validation solutions.

Let's define a Django form:

from django import forms
from django.core.validators import RegexValidator


class TestForm(forms.Form):
    username = forms.CharField(
        max_length=100,
        required=True,
        validators=[
            RegexValidator(
                '^[a-zA-Z0-9.\-_]+$',
                message="Username must contain only letters, numbers, underscore, dot or dash character"
            )
        ]
    )

Here we create a Django form to validate the username on the server side.

Let's test some code in the Django shell

In [2]: form = TestForm()

In [3]: form.as_p()
Out[3]: '<p><label for="id_username">Username:</label> <input type="text" name="username" maxlength="100" required id="id_username"></p>'

As you can see, Django form can generate the HTML code for the form, and we can render it in the template.

<p>
  <label for="id_username">Username:</label>
  <input type="text" name="username" maxlength="100" required id="id_username">
</p>

After receiving POST request, we can use the is_valid() to validate the form data.

# validate the form data
In [4]: form = TestForm({'username': '@test'})

In [5]: form.is_valid()
Out[5]: False

In [6]: form.errors
Out[6]: {'username': ['Username must contain only letters, numbers, underscore, dot or dash character']}

Here we validated the form data on the server side, and we can get the validation error message from the form.errors property.

You can check Django Doc: Working with forms to learn more.

Pros and Cons

Pros:

  1. Simple to use

Cons:

  1. User needs to submit the form to server to get the validation error messages.

Server-side Validation 2: DRF Serializer

The Django REST framework Serializer is very similar with the Django Form, we can use it convert data to Python primitive types, and validate the data.

from django.core.validators import RegexValidator
from rest_framework import serializers


class TestSerializer(serializers.Serializer):
    username = serializers.CharField(
        validators=[
            RegexValidator(
                '^[a-zA-Z0-9.\-_]+$',
                message="Username must contain only letters, numbers, underscore, dot or dash character"
            )
        ]
    )

Let's test in Django shell

In [2]: serializer = TestSerializer(data={'username': '@test'})

In [3]: serializer.is_valid()
Out[3]: False

In [4]: serializer.errors
Out[4]: {'username': [ErrorDetail(string='Username must contain only letters, numbers, underscore, dot or dash character', code='invalid')]}

As you can see, the Django REST framework Serializer syntax is very similar with the Django form.

In most cases, developers need to write some Javascript to send JSON data from the Frontend App to DRF API and consume the response from the API. And this usually happen in React, Vue, Angular or other frontend frameworks.

For example, if the client get 400 Bad Request, which is validation error and client will display the error message to the user.

Pros and Cons

Pros:

  1. Simple to use

Cons:

  1. Developers might need to write form validation code in both frontend and backend.

Server-side Solution 3: HTML Through Ajax or Websocket

Let's think about the above solutions:

  1. Writing javascript for client-side validation take time.
  2. The traditional server-side validation (based on Django form) is not very friendly.
  3. User needs to click submit button, and then wait for the server response to see if it is valid or not, the whole page will be refreshed, the user experience is not good.

How about this way:

  1. User submit the form, Javascript inject the event and then send the form data to the server.
  2. After server returning the response, for example, the response contains some validation error message.
  3. The client javascript only refresh the form part on the page.
  4. So the user can still see the form validation error message immediately, and the whole page will not be refreshed.

This feature also called Partial update (or inline form validation) and it is becoming more and more popular.

The benefit of this workflow:

  1. Developers only write the server-side validation code.
  2. The user experience is improved, compared with the traditional server-side validation.
  3. This solution is a good fit for Rapid Prototyping.

Option 1: HTMX

HTMX is very popular in the Django community, and I recommend you to check this great blog post

Inline form validation with Django and Htmx

Option 2: Hotwire

Hotwire is the default frontend solution shipped in Ruby on Rails

The Turbo can help us implement the Partial update feature without writing any Javascript code.

As for the dynamic form, you can check this great blog post: Dynamic forms with Turbo

If you want to know how to use Hotwire with Django, please check my book The Definitive Guide to Hotwire and Django

Other Options

Unpoly do similar work as HTMX, you can check the demo if you are interested.

Pros and Cons

Pros:

  1. Very efficient for Rapid Prototyping.
  2. The user experience is improved, compared with the traditional server-side validation.

Cons:

  1. Developers need to learn some new technologies.

Conclusion

In this tutorial, I talked about some different ways to do form validation in Django projects, the concepts should also work with other web frameworks such as Flask, FastAPI, etc.

I hope you have learned something new from this tutorial. If you have any question, please feel free to contact me.

Launch Products Faster with Django

SaaS Hammer helps you launch products in faster way. It contains all the foundations you need so you can focus on your product.

Michael Yin

Michael Yin

Michael is a Full Stack Developer from China who loves writing code, tutorials about Django, and modern frontend tech.

He has published some ebooks on leanpub and tech course on testdriven.io.

© 2024 SaaS Hammer