Decorators are incredibly helpful tools that allow you to dynamically change the functionality of a function, method, or class without having to utilize subclasses directly or modify the function’s source code. The decorator’s login_required, require HTTP methods, require POST, and others are needed when working with Django views.
But there are also situations where we want to utilize our own, uniquely designed decorators that function exactly how we want them to. Making decorators can also be useful if you apply the same logic in several places and find that it is simple to remove and add the decorator after your changes are complete.
Create Custom Decorator in Django
Here, we’ll set up a mechanism for students to access the site. In order to prevent students, teachers, and principals from accessing the pages that are available to other users. These privileges will be managed by the custom decorators we will create, and we will return various results/responses on each page access so that we can see various ways you can redirect or return a response for not accessible page.
Stepwise Implementation
Step 1: Create your virtual environment and Install Django.
python -m venv custom_decorator_venv
Step 2: Create a project named CustomDecorator
django-admin startproject CustomDecorator
Step 3: Move to the CustomDecorator folder and, then create an app named user in the project
cd CustomDecorator
python manage.py startapp user
File Structure
This is the final file structure after following all the steps.
Step 4: Add a user app to the INSTALLED_APPS.
Python3
INSTALLED_APPS = [ 'user.apps.UserConfig' , 'django.contrib.admin' , 'django.contrib.auth' , 'django.contrib.contenttypes' , 'django.contrib.sessions' , 'django.contrib.messages' , 'django.contrib.staticfiles' , |
Step 5: Set this model to be your default auth model in settings.py
Python3
AUTH_USER_MODEL = "user.UserAccount" |
Step 6: We will create UserAccountManager as the manager for the UserAccount model. In the UserAccount model, there will be five functions: create user, create superuser, create teacher, create student, and create principal. If the student boolean is True, the user is a student; if the is teacher boolean is True, the user is a teacher; and the same is true for the principal.
users/models.py
Python3
from django.db import models from django.contrib.auth.models import AbstractBaseUser, BaseUserManager class UserAccountManager(BaseUserManager): def create_user( self , email, username, password = None ): if not email: raise ValueError( "Email field is required !" ) if not username: raise ValueError( "Username field is required !" ) if not password: raise ValueError( "Password field is required !" ) user = self .model( email = email, username = username ) user.set_password(password) user.save(using = self ._db) return user def create_superuser( self , email, username, password): user = self .create_user( email = email, username = username, password = password) user.is_superuser = True user.is_staff = True user.is_admin = True user.save() return user def create_student( self , email, username, password): user = self .create_user(email, username, password) user.is_student = True user.save() return user def create_teacher( self , email, username, password): user = self .create_user(email, username, password) user.is_teacher = True user.save() return user def create_principal( self , email, username, password): user = self .create_user(email, username, password) user.is_principal = True user.save() return user class UserAccount(AbstractBaseUser): username = models.CharField(max_length = 200 , blank = False , null = False ) email = models.CharField( max_length = 200 , blank = False , null = False , unique = True ) is_active = models.BooleanField(default = True ) is_staff = models.BooleanField(default = False ) is_admin = models.BooleanField(default = False ) is_superuser = models.BooleanField(default = False ) is_student = models.BooleanField(default = False ) is_teacher = models.BooleanField(default = False ) is_principal = models.BooleanField(default = False ) objects = UserAccountManager() USERNAME_FIELD = "email" def __unicode__( self ): return str ( self .username) def has_perm( self , perm, obj = None ): return self .is_admin def has_module_perms( self , app_label): return True |
Step 7: Create a templates/user folder in the user app which will hold the following templates:
- user/templates/user/homePage.html
This is for the home page of the site.
HTML
<!DOCTYPE html> < html > < body > {% if messages %} {% for message in messages %} < div style = "color: red;" >{{message}}</ div > {% endfor %} < br > < br > {% endif %} {% if request.user.is_authenticated %} < h1 > {{request.user.email}}</ h1 > < br > {% if request.user.is_student %} You are a student ... {% elif request.user.is_teacher %} You are a teacher ... {% elif request.user.is_principal %} You are a principal ... {% else %} You don't have any privilege ... {% endif %} {% endif %} < br > < br > < a href = "{% url 'user_urls:login-user' %}" >Login </ a > < br > < br > < a href = "{% url 'user_urls:student-view' %}" >Student View </ a > < br > < br > < a href = "{% url 'user_urls:teacher-view' %}" >Teacher View</ a > < br > < br > < a href = "{% url 'user_urls:principal-view' %}" >Principal View</ a > < br > < br > </ body > </ html > |
- user/templates/user/loginView.html
This is the home page telling which user you are and there are links for the other pages to the site, we will create those links and views and it also displays the messages if there are some.
HTML
{% if messages %} {% for message in messages %} < div style = "color: red ;" >{{message}}</ div > < br > {% endfor %} {% endif %} < form method = "post" > {% csrf_token %} {{form.as_p}} < br > < br > < input type = "submit" value = "Login" /> </ form > |
- user/templates/user/principalView.html
This is the view that will only be accessed by the user who is the principal.
HTML
< h1 > {{request.user.email}}</ h1 > < br > < h1 > You are a principal and you can access the page ! </ h1 > |
- user/templates/user/studentView.html
This is the student view and can be accessed by the student only.
HTML
< h1 > {{request.user.email}}</ h1 > < br > < h1 > You are a student and you can access the page ! </ h1 > |
- user/templates/user/teacherView.html
This is the teacher’s view which can be only viewed by the teacher.
HTML
< h1 > {{request.user.email}}</ h1 > < br > < h1 > You are a teacher and you can access the page ! </ h1 > |
Step 8: user/decorators.py
Understanding the concept of decorators for this Project
Before creating our real decorator it is necessary to understand how to build a decorator, this is a pseudo code/skeleton of the decorator.
We have a function called test_function that accepts the necessary arguments depending on the circumstance and returns True or False. However, you can write your own function and define whatever statement you like. The decorator, either accepts or rejects the argument depending on the situation. The @wraps from the functool module take the function name that is to be decorated by the wrapper function.
The _wrapped_view(request, *args, **kwargs) is the exact view in which the decorator will be wrapped, this is like a clone of the view around which the decorator will be wrapped, now you can perform any action like you can redirect, return any kind of response, or move on.
Python3
from functool import wraps def test_function_works( * args, * * kwargs): if args[ 0 ] = = True : return True return False def some_decorator( * decorator_args , * * decorator_kwargs): def decorator(view_function): @wraps (view_function) def _wrapped_view(request, * view_args, * * view_kwargs): print ("The required actions will be taken here ! Well, \ actually inside the _wrapped_view function") if not test_function_works( * arguments , * * keyword_arguments): print ("The necessary operation that will be taken if \ the test case fails !") return view_function(request, * args, * * kwargs) return _wrapped_view return decorator |
Creating decorator
We have created three functions ( student_test_function, teacher_test_function, principal_test_function ) which return True if the user is the following user in each case. These test_functions are to be used by decorators.
- student_access_only decorator: It wraps a view, if the currently logged-in user is not a student then he is returned a HttpResponse, that you are not a student and you cannot access the page.
- teacher_access_only decorator: It requires the input view_to_return, to which the user will be redirected if they are not teachers (we will generate the URLs).
- principal_access_only decorator: it takes a message which will be sent to a certain page telling that you are not a principal and you cannot access the page and then you will be redirected to the login page.
Python3
from functools import wraps from django.contrib import messages from django.shortcuts import redirect from django.http import HttpResponse def student_test_function(user): if user.is_student: return True return False def teacher_test_function(user): if user.is_teacher: return True return False def principal_test_function(user): if user.is_principal: return True return False def student_access_only(): def decorator(view): @wraps (view) def _wrapped_view(request, * args, * * kwargs): if not student_test_function(request.user): return HttpResponse("You are not a student and \ you are not allowed to access this page !") return view(request, * args, * * kwargs) return _wrapped_view return decorator def teacher_access_only(view_to_return = "user_urls:home-page" ): def decorator(view): @wraps (view) def _wrapped_view(request, * args, * * kwargs): if not teacher_test_function(request.user): messages.error(request, "You cannot access \ the teachers page !") return redirect(view_to_return) return view(request, * args, * * kwargs) return _wrapped_view return decorator def principal_access_only(message_to_deliver = "Not allowed to \ access the principal's page , login as principal !"): def decorator(view): @wraps (view) def _wrapped_view(request, * args, * * kwargs): if not principal_test_function(request.user): messages.error(request, message_to_deliver) return redirect( "user_urls:login-user" ) return view(request, * args, * * kwargs) return _wrapped_view return decorator |
Step 9: user/views.py
We have four views that just render out the templates.
Python3
from django.shortcuts import render from .decorators import ( student_access_only, teacher_access_only, principal_access_only ) def homePage(request, * args, * * kwargs): context = {} return render(request, "user/homePage.html" , context) @student_access_only () def studentView(request, * args, * * kwargs): context = {} return render(request, "user/studentView.html" , context) @teacher_access_only () def teacherView(request, * args, * * kwargs): context = {} return render(request, "user/teacherView.html" , context) @principal_access_only () def principalView(request, * args, * * kwargs): context = {} return render(request, "user/principalView.html" , context) |
Step 10: user/urls.py
These URLs help to route to the respective views.
Python3
from django.urls import path from user import views as user_views from django.contrib.auth.views import LoginView app_name = "user_urls" urlpatterns = [ path(" ", user_views.homePage, name=" home - page"), path( "login-user/" , LoginView.as_view(template_name = "user/loginView.html" ), name = "login-user" ), path( "student-view/" , user_views.studentView, name = "student-view" ), path( "teacher-view/" , user_views.teacherView, name = "teacher-view" ), path( "principal-view/" , user_views.principalView, name = "principal-view" ), ] |
Step 11: CustomDecorator/settings.py
Also, set up the LOGIN_REDIRECT_URL for the login in settings.py
Python3
LOGIN_REDIRECT_URL = "user_urls:home-page" |
Step 12: CustomDecorator/urls.py
Now set up the project urls.py to direct them to our user app.
Python3
from django.contrib import admin from django.urls import path, include urlpatterns = [ path( 'admin/' , admin.site.urls), path(" ", include(" user.urls ", namespace=" user_urls")), ] |
Step 13: Now, migrate the data to the database using the following command.
python manage.py makemigrations python manage.py migrate
Step 14: Open the Django shell and create three users, one student, one teacher, and one principal.
python manage.py shell
from user.models import UserAccount
UserAccount.objects.create_student(username = “student” , email = “studentmail@gmail.com” , password = “testing123”)
UserAccount.objects.create_teacher(username = “teacher” , email = “teachermail@gmail.com” , password = “testing123”)
UserAccount.objects.create_principal(username = “principal” , email = “principalmail@gmail.com” , password = “testing123”)
Step 19: Now run the server to test your project.
python manage.py runserver
Output: