Django and Ajax: Robust authentication and authorization system with real-time form validations for web applications

Motivation

In contemporary times, real-time form validations and interactive application's user interfaces are prevalent and they are mostly incorporated in Software requirements. Being a framework for "perfectionists with deadlines", Django does help a bit by providing a classical server-side form validations for all projects that utilize its form module. However, these validations are not in real-time. You only get information about them after you submitted the form and the page is reloaded. Also, your application might require some other validation logic based on requirement which are not covered by Django out-of-box. Hence this article. We will be taken through a simple process of extending Django's default form validation logic and making sure users' inputs conform with them in real-time.

Project Requirement Specification

Throughout this tutorial series, we'll be working towards implementing these requirements:

Build a multi-user authentication system. A user can either be a Student or a Lecturer. For students, their usernames must start with CPE and must be at least 9 characters long not counting any forward slash that it contains. As for Lecturers, only their emails should be filled in and to be valid, all email addresses must end with @futa.edu.ng. Lecturers' usernames should be autogenerated and must start with the words before @ in the email address provided. Email addresses must be unique and confirmed by sending confirmation mails upon registration and the mails must support HTML. Until confirmation, no user is allowed to log in. Time attacks must be addressed by sending the mails asynchronously. Users can either login with any of Email/Password or Username/Password combination. Password reset functionality should be incorporated as well. At first login, Lecturers should be prompted to complete their profiles.

😲 That was mouthful huh! It is from this end too 😫😩. Readers are enjoined to suggest other features and provide implementations in the comment section, I will be glad πŸ€—.

Technology stack

We will not be using fancy JavaScript framework for this. Our stack comprises:

  • Django (version 3.2.5)
  • Pure JavaScript and jQuery(version 3.6.0, for AJAX)
  • Pure CSS3 and Materialize CSS framework (version 1.0.0)
  • HTML5

Assumption

A simple prerequisite to follow along is basic familiarity with Django β€” like setting up virtual environment, bootstraping a project and application, a bit about its MVT architecture β€” JavaScript and CSS. You do not need to be an expert β€” I ain't one in any of the technologies.

NOTE
I have created a project named authentication, and an application, accounts. Both have been linked and static as well as templates configured. You are enjoined to do these before proceeding. Current folder structure is as follows:

└──  accounts/
 β”‚  β”œβ”€β”€β”€β”€   admin.py
 β”‚  β”œβ”€β”€β”€β”€   apps.py
 β”‚  β”œβ”€β”€β”€β”€   __init__.py
 β”‚  └────   migrations/
 β”‚  β”‚  └────   __init__.py
 β”‚  β”œβ”€β”€β”€β”€  models.py
 β”‚  β”œβ”€β”€β”€β”€  tests.py
 β”‚  └────  views.py
 └──  authentication/
 β”‚  β”œβ”€β”€β”€β”€  asgi.py
 β”‚  β”œβ”€β”€β”€β”€  __init__.py
 β”‚  β”œβ”€β”€β”€β”€  settings.py
 β”‚  β”œβ”€β”€β”€β”€  urls.py
 β”‚  └────  wsgi.py
 β”œβ”€β”€  manage.py
 β”œβ”€β”€  Pipfile
 β”œβ”€β”€  Pipfile.lock
 β”œβ”€β”€  Procfile
 β”œβ”€β”€  setup.cfg
 └──  static/
 β”‚  └────  css/
 β”‚  β”‚  β”œβ”€β”€β”€β”€  materialize.min.css
 β”‚  β”‚  └────  style.css
 β”‚  └────  img/
 β”‚  β”‚  └────  sign-up-illustration.svg
 β”‚  └────  js/
 β”‚  β”‚  β”œβ”€β”€β”€β”€  init.js
 β”‚  β”‚  β”œβ”€β”€β”€β”€  jquery.min.js
 β”‚  β”‚  └────  materialize.min.js
 └──  templates/
 β”‚  └────  accounts/
 β”‚  β”‚  └────  index.html
 β”‚  β”œβ”€β”€β”€β”€  base.html
 β”‚  └────  includes/
 β”‚  β”‚  β”œβ”€β”€β”€β”€  _footer.html
 β”‚  β”‚  └────  _header.html

You can get this full starter template structure from github. It should be noted that the structure also contains some configurations for sending emails with gmail. Check this dev.to post for details about that.

Source code

The source code for this series is hosted on github or more expressed via:

GitHub logo Sirneij / django_real_time_validation

Django and Ajax: Robust authentication and authorization system with real-time form validations for web applications

django_real_time_validation

Django and Ajax: Robust authentication and authorization system with real-time form validations for web applications

The project is also live on heroku and can be accessed via this django-authentication-app.herokuapp.com

Step 1: Build the models.py for the users.

To start with, let's handle this part of the specification:

Build a multi-user authentication system. A user can either be a Student or a Lecturer ...

From that spec, we need a way to detect that someone is a student and another, a Lecturer. There are a variety of ways to accomplish this, however, we'll stick with the AbstractUser inheritance. This gives us the inherent robustness of the built-in django user model while allowing a nifty and clean extension. A good resource for working with this approach and others can be found in this Vitor Freitas's article.

Now to the implemetation, open up your accounts/models.py file and transform it to something like this listing:

# accounts > models.py

import uuid

from django.contrib.auth.models import AbstractUser
from django.db import models

LEVEL = (
    ("100L", "100L"),
    ("200L", "200L"),
    ("300L", "300L"),
    ("400L", "400L"),
)

ALIAS = (("Mr.", "Mr."), ("Mrs", "Mrs"), ("Dr.", "Dr."), ("Prof.", "Prof."))

GENDER = (
    ("Male", "Male"),
    ("Female", "Female"),
    ("Prefer not to mention", "Prefer not to mention"),
)


class User(AbstractUser):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    is_student = models.BooleanField(default=False)
    is_lecturer = models.BooleanField(default=False)
    alias = models.CharField(choices=ALIAS, max_length=5, null=True, blank=True)
    level = models.CharField(choices=LEVEL, max_length=11, null=True, blank=True)
    gender = models.CharField(choices=GENDER, max_length=22)
    has_logged_in = models.BooleanField(default=False)

    def __str__(self):
        return str(self.id)

Do not run migrations yet πŸ›‘. If you do, you are likely to be greeted with something like:

β”Œβ”€β”€(sirneij@sirneij)-[~/Documents/Projects/Django/django_real_time_validation]
└─$[sirneij@sirneij django_real_time_validation]$ python manage.py makemigrations
SystemCheckError: System check identified some issues:

ERRORS:
accounts.User.groups: (fields.E304) Reverse accessor for 'accounts.User.groups' clashes with reverse accessor for 'auth.User.groups'.
    HINT: Add or change a related_name argument to the definition for 'accounts.User.groups' or 'auth.User.groups'.
accounts.User.user_permissions: (fields.E304) Reverse accessor for 'accounts.User.user_permissions' clashes with reverse accessor for 'auth.User.user_permissions'.
    HINT: Add or change a related_name argument to the definition for 'accounts.User.user_permissions' or 'auth.User.user_permissions'.
auth.User.groups: (fields.E304) Reverse accessor for 'auth.User.groups' clashes with reverse accessor for 'accounts.User.groups'.
    HINT: Add or change a related_name argument to the definition for 'auth.User.groups' or 'accounts.User.groups'.
auth.User.user_permissions: (fields.E304) Reverse accessor for 'auth.User.user_permissions' clashes with reverse accessor for 'accounts.User.user_permissions'.
    HINT: Add or change a related_name argument to the definition for 'auth.User.user_permissions' or 'accounts.User.user_permissions'.

To fix this, we need to append this line to our settings.py file:

# authentication > settings.py
...
AUTH_USER_MODEL = "accounts.User"
...

Now, run migrations.

β”Œβ”€β”€(sirneij@sirneij)-[~/Documents/Projects/Django/django_real_time_validation]
└─$[sirneij@sirneij django_real_time_validation]$ python manage.py makemigrations

Migrations for 'accounts':
  accounts/migrations/0001_initial.py
    - Create model User

And then migrate.

Test your application to ensure everything works πŸ‘.

You should see something like this in your browser:

You can grab the full code for this part on github.

Let's draw the curtain here for now. See you in the next part 😎.

Outro

24