Implementing Authentication for School Management System with Django

Implementing Authentication for School Management System with Django

Overview

For the authentication system, we will start by defining our user model and then creating proxy models to differentiate between different types of users. We will also define custom managers for the models to override the create_user and get_queryset methods.

After defining our models we will be creating our custom user creation form and user change form to create and update our users. Then defining URL paths and writing view functions to process the form data and register different types of users.

For a better, understanding give the following blogs a read.

Learn about the Structure of Django Applications Here

Learn About Django Models Here

Defining Custom User model

We will be extending the AbstractBaseUser from django.contrib.auth.models and setting email as the username field for logging in users.

# authentication/models.py
from django.utils import timezone
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin

# This the user manager with function to create_user and create_superuser methods. Manager is used to perform query operations
class UserManager(BaseUserManager):
    def create_user(self, email, password=None):
        if not email or len(email) <= 0:
            raise ValueError("Email Field is required!!!!")
        if not password:
            raise ValueError("Password is required")

        user = self.model(
            email=self.normalize_email(email)
        )
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password):
        user = self.create_user(
            email=self.normalize_email(email),
            password=password
        )
        user.is_admin = True
        user.is_staff = True
        user.is_teacher = True
        user.is_superuser = True
        user.save(using=self._db)
        return user

# This is the custom user model with type attribute for differentiating user types. Default type is student
class User(AbstractBaseUser,PermissionsMixin):
    class Types(models.TextChoices):
        STUDENT = "STUDENT", "student"
        TEACHER = "TEACHER", "teacher"
    type = models.CharField(
        max_length=8, choices=Types.choices, default=Types.STUDENT)
    email = models.EmailField(unique=True, max_length=100)
    date_joined = models.DateTimeField(default=timezone.now)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)
    is_teacher = models.BooleanField(default=False)
    is_student = models.BooleanField(default=False)
    USERNAME_FIELD = 'email'
    objects = UserManager()

    def __str__(self) -> str:
        return str(self.email)

    def has_perm(self, perm, obj=None) -> bool:
        return self.is_admin

    def has_module_perms(self, app_label) -> bool:
        return True

    def save(self, *args, **kwargs):
        if not self.type or self.type == None:
            self.type = User.Types.STUDENT
        return super().save(*args, **kwargs)

Defining Proxy model for different user types

Proxy models operate on the same database table of concrete class in our case it is User but proxy models can have different behavior, permissions and meta options. We have used proxy models since both students and teachers should be able to login into the system.

# authentication/models.py
# Manager for student
class StudentManager(models.Manager):
    def create_user(self, email, password=None):
        if not email or len(email) <= 0:
            raise ValueError("Email is Required!!")
        if not password:
            raise ValueError("Password is Required!!")
        email = email.lower()
        user = self.model(
            email=self.normalize_email(email)
        )
        user.set_password(password)
        user.save(using=self._db)
        return user

    def get_queryset(self, *args, **kwargs):
        queryset = super().get_queryset(*args, **kwargs)
        queryset = queryset.filter(type=User.Types.STUDENT)
        return queryset

# Student proxy model
class Student(User):
    class Meta:
        proxy = True

    objects = StudentManager()

    def save(self, *args, **kwargs):
        self.type = User.Types.STUDENT
        self.is_student = True
        super().save(*args, **kwargs)

# Manager for Teacher model
class TeacherManager(models.Manager):
    def create_user(self, email, password=None):
        if not email or len(email) <= 0:
            raise ValueError("Email Required!!")
        if not password:
            raise ValueError("Password is Required!!")
        email = email.lower()
        user = self.model(
            email=self.normalize_email(email)
        )
        user.set_password(password)
        user.save(using=self._db)
        return user

    def get_queryset(self, *args, **kwargs):
        queryset = super().get_queryset(*args, **kwargs)
        queryset = queryset.filter(type=User.Types.TEACHER)
        return queryset

# Teacher proxy model
class Teacher(User):

    class Meta:
        proxy = True

    objects = TeacherManager()

    def save(self, *args, **kwargs):
        self.type = User.Types.TEACHER
        self.is_teacher = True
        self.is_staff = True
        return super().save(*args, **kwargs)

For each proxy model, we have overridden the save method to change the attributes as per the proxy model and save the model

Creating UserCreationForm and UserChangeForm

Now we will extend the UserCreationForm and UserChangeForm and modify them as per our Proxy model. While defining a Form in Django we define the model in the inner class Meta along with fields to be exposed through the form.

# authentication/forms.py
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from authentication.models import Student, Teacher, User

# Creation form for our Concrete Model
class CustomUserCreationForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('email',)

# Change form for updating User
class CustomUserChangeForm(UserChangeForm):
    class Meta:
        model = User
        fields = ('email',)

# Creation form for Teacher proxy model
class TeacherCreationForm(UserCreationForm):
    class Meta:
        model = Teacher
        fields = ('email',)

# Change form for updating Teacher
class TeacherChangeForm(UserChangeForm):
    class Meta:
        model = Teacher
        fields = ('email',)

# Creation form for Student proxy model
class StudentCreationForm(UserCreationForm):
    class Meta:
        model = Student
        fields = ('email',)

# Change form for updating Student
class StudentChangeForm(UserChangeForm):
    class Meta:
        model = Student
        fields = ('email',)

Updating core/settings.py and core/urls.py

The next step is to add our authentication app to the installed_apps list

# core/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
# I have added authentication app here
    'authentication',
]
# Settings to use our custom user for authentication
AUTH_USER_MODEL = 'authentication.User'
# core/urls.py
from django.contrib import admin
from django.urls import path, include
# Here we have included authentication urls to the main django project
# All path starting with 'auth/' will be looked up in authentication app urls file
urlpatterns = [
    path('admin/', admin.site.urls),
    path('auth/',include('authentication.urls'))
]

Defining URL paths and View Functions

Create a urls.py file in the authentication folder and add the following lines to it

# authentication/urls.py
from django.urls import path
from django.contrib.auth.views import LoginView
from . import views
# path( "path string/", views.function, name="optional name of path"
urlpatterns = [
path('',
    views.homepage,
    name="homepage"
    ),
path(
    'login/',
    views.loginUser,
    name="login"
    ),
path(
    'logout/',
    views.logoutUser,
    name="logout"
    ),
path(
    'register-student/'
    ,views.registerStudent,
    name="register-student"
    ),
path(
    'register-teacher/',
    views.registerTeacher,
    name="register-teacher"
     ),
]

Now we will create view functions that are triggered as per the URL path that the client accesses in our web application.

# authentication/views.py
from django.shortcuts import redirect, render
from django.contrib import messages
from authentication.forms import StudentCreationForm, TeacherCreationForm


def homepage(request):
    return render(request, "homepage.html")


def registerStudent(request):
    if request.method=='POST':
        form = StudentCreationForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request,'Registered Successfully')
        else:
            messages.error(request,'Please recheck the form data')
        return redirect('register-student')
    form = StudentCreationForm()
    title = "Student Registration"
    return render(request, 'authentication/register.html', {'form': form,'title':title})


def registerTeacher(request):
    if request.method=='POST':
        form = TeacherCreationForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request,'Registered Successfully')
        else:
            messages.error(request,'Please recheck the form data')
        return redirect('register-teacher')
    form = TeacherCreationForm()
    title = "Teacher Registration"
    return render(request, 'authentication/register.html', {'form': form,'title':title})


def loginUser(request):
    if request.method=='POST':
        email = request.POST['email']
        password = request.POST['password']
        user = authenticate(request,email=email,password=password)
        if user is not None:
            login(request,user)
            messages.success(request,"Logged in Successfully")
            return redirect('homepage')
        else:
            messages.error(request,"Invalid credentials!")
    return render(request,'authentication/login.html')


def logoutUser(request):
    logout(request)
    return redirect('login')

For using the creation form we create an instance of the form and pass it to the template for rendering. We have created different functions for each user type and are passing different creation forms as per type to the template.

Registering model for Admin panel

Now finally we will be registering our models to the admin panel

# authentication/admin.py
from django.contrib import admin
from .models import *

admin.site.register(User)
admin.site.register(Teacher)
admin.site.register(Student)

Conclusion

We have almost completed the backend of the authentication system. We created our User models, proxy models for different user types, URL routes, and views functions for registering users, logging users in and logging them out.

In the next part, we will create templates i.e. the front end of our authentication system.

Previous Part

Next Part

Follow, like, share, comment and support

Did you find this article valuable?

Support Rishabh Kumar Bahukhandi by becoming a sponsor. Any amount is appreciated!