Django Class-Based Views, Mixins, LoginRequiredMixin and Custom Mixins

In this post, we’ll be learning about Django Class-Based Views, Mixins, LoginRequiredMixin and Custom Mixins.

Table of Contents

Introduction

Django Class-based views make it easy to handle business logic inside the class and to take it further and expanding its functionality by inheriting other classes which provide a solution to specify purpose mixins were introduced.
These mixins can also be tailored with specified requirement or developers can create custom mixins.

Built-in Mixins

Some of the built-in mixins provided are :

  • Simple Mixins
  • Single object Mixins
  • Multiple object Mixins
  • Editing Mixins
  • Date-based Mixins
  • Authentication Mixins : AccessMixin, LoginRequiredMixin, PermissionRequiredMixin.

Click here for Django official for docs on mixins.

AccessMixin Mixins

The AccessMixin is an abstract class for mixins and is inherited by LoginRequiredMixin and PermissionRequiredMixin.

Attributes

  • login_url: This is a redirect URL to the login page.
  • permission_denied_message: Outputs message if permission is denied.
  • raise_exception: Raises an exception if a user has no permission.
  • redirect_field_name: This gets the URL where the user must be redirected.

Methods

  • get_login_url(): Returns login_url if not specified than returns settings.LOGIN_URL.
  • get_permission_denied_message: Returns given permission_denied_message attribute.
  • get_redirect_field_name: Returns given redirect_field_name attribute.
  • handle_no_permission: This method is called when the user has no access permission and must be redirected to login URL.

Snippet from Django Documentation

Below is a snippet of AccessMixin taken from Django docs. This is to show you what logic is performed inside the AccessMixin class.

class AccessMixin:
    """
    Abstract CBV mixin that gives access mixins the same customizable
    functionality.
    """
    login_url = None
    permission_denied_message = ''
    raise_exception = False
    redirect_field_name = REDIRECT_FIELD_NAME

    def get_login_url(self):
        """
        Override this method to override the login_url attribute.
        """
        login_url = self.login_url or settings.LOGIN_URL
        if not login_url:
            raise ImproperlyConfigured(
                '{0} is missing the login_url attribute. Define {0}.login_url, settings.LOGIN_URL, or override '
                '{0}.get_login_url().'.format(self.__class__.__name__)
            )
        return str(login_url)

    def get_permission_denied_message(self):
        """
        Override this method to override the permission_denied_message attribute.
        """
        return self.permission_denied_message

    def get_redirect_field_name(self):
        """
        Override this method to override the redirect_field_name attribute.
        """
        return self.redirect_field_name

    def handle_no_permission(self):
        if self.raise_exception or self.request.user.is_authenticated:
            raise PermissionDenied(self.get_permission_denied_message())
        return redirect_to_login(self.request.get_full_path(), self.get_login_url(), self.get_redirect_field_name())

This snippet is taken from Django documentation click here to view its details in Django docs.

LoginRequiredMixin Mixins

This mixin checks if the requested user is logged-in. It inherits AccessMixin and over-writes dispatch with request.user.is_authenticated condition.
To use this mixin import LoginRequiredMixin from from django.contrib.auth.mixins import LoginRequiredMixin.

Example

In views.py.

from django.contrib.auth import authenticate, login, logout

class LoginView(View):
    def get(self, request, *args, **kwargs):
        user = authenticate(request, username='root', password='123456')
        if user is not None:
            login(request, user)

        return HttpResponse("Login View")

class LoginOutView(View):
    def get(self, request, *args, **kwargs):
        logout(request)
        return HttpResponse("Log-out View")


from django.contrib.auth.mixins import LoginRequiredMixin

class ProfileView(LoginRequiredMixin, View):
    login_url = '/cbs/login'
    def get(self, request, *args, **kwargs):
        return HttpResponse("User Profile View")

In urls.py.

from django.urls import path, include
from class_based_views import views

app_name = 'class_based_views'

urlpatterns = [
    path('login', views.LoginView.as_view(), name='login'),
    path('logout', views.LoginOutView.as_view(), name='logout'),
    path('user-profile', views.ProfileView.as_view(), name='user-profile'),
]  

The below code is a simple snippet for how we log in a user. In order to understand we have made it simple and clean as this is for demonstration for LoginRequiredMixin.
You have ProfileView which inherits LoginRequiredMixin and View now to see profile one must be logged in and to check whether the user is logged-in is determined by LoginRequiredMixin.

The LoginRequiredMixin just overrides the dispatch() method of View class.

PermissionRequiredMixin Mixins

This mixin check permission for every user with permission_required attribute. The attribute permission_required contains the permissions users must have in order to access this view.

Example

permission_required = 'blogmodel.view_blogmodel'

The above permission blogmodel.view_blogmodel requires the user to have view_blogmodel permission and blogmodel is the model name.

Attributes

  • permission_required: This attribute takes a string as well as a tuple for specifying multiple permissions.

Methods

  • get_permission_required(): Returns the attribute permission_required.
  • has_permission(): Checks if users have all the permissions specified in permission_required and internally calls self.request.user.has_perms() methods.
  • dispatch(): Returns parent dispatch() method is user has permission.

Example: PermissionRequiredMixin Mixins

In views.py.

from django.contrib.auth.mixins import PermissionRequiredMixin

class EditBlogView(PermissionRequiredMixin, View):
    permission_required = 'blogmodel.view_blogmodel'

    def get_permission_required(self):
        perms = super(EditBlogView, self).get_permission_required()
        return perms

    def has_permission(self):
        perms = super(EditBlogView, self).has_permission()
        return perms

    def get(self, request, *args, **kwargs):
        return HttpResponse("Edit Blog View")

In urls.py.

from django.urls import path
from class_based_views import views

app_name = 'class_based_views' 

urlpatterns = [
    path('edit-blog', views.EditBlogView.as_view(), name='edit-blog'),
]  

We have specified our permission_required and defined blogmodel.view_blogmodel which means that the user can view the blog.
Methods such as get_permission_required and has_permission can be overridden and we can specify our conditions in those extending its functionality.
If a user does not have permissions than he will be redirected to login_url or settings.LOGIN_URL if login_url is None.

Creating Custom Mixin

In our post, on Django Class-based views | Decorators, Methods, Template and Redirect view you have submitted form using class-based view and you have created a function that would handle file upload.
Here we’ll consider the same snippet as an example and refactor it by creating our own custom mixin for file upload.

In mixins.py.

from django.conf import settings
import os
from django.core.files.storage import FileSystemStorage

class FileUploadMixin(object):

    upload_path = "/static/uploads"

    def upload(self, file_list, allowed_mime_types=[]):

        if not os.path.exists("{}/{}".format(settings.BASE_DIR,dir_path)):
            os.makedirs("{}/{}".format(settings.BASE_DIR,self.upload_path),exist_ok = True)
    
        if not isinstance(file_list, list):
            file_list=list(file_list)

        directory = os.path.join(settings.BASE_DIR, dir_path)

        info={
            "files" : []
        }
        
        for file in file_list:
            file_name= file._name
            file_mime = file.content_type.split('/')[1]
            path= "{}{}".format(directory, file_name)

            is_allowed_to_upload=False
            #check allowed mime types if that file does not belong to mime type remove it
            if len(allowed_mime_types) > 0:
                if file_mime in  allowed_mime_types:
                    is_allowed_to_upload=True
            else:
                is_allowed_to_upload=True

            if is_allowed_to_upload is True:
                with open(path, 'wb+') as destination:
                    for chunk in file.chunks():
                        destination.write(chunk)
                    info["files"].append(file_name)

        return info

The FileUploadMixin is a mixin which takes python object as a parameter. It has an attribute upload_path which is used to upload file to that path user can also specify their custom path.

The Method upload takes a few arguments they are:

  • file_list: which has list files to be uploaded.
  • allowed_mime_types: This is a validation check and only match extensions will be uploaded.

The other miscellaneous logic performs operations such as checking directory exists, mime type validation checks and returning uploaded files are done by upload method.
This helps to reduce code redundancy.

In views.py.

from class_based_views.mixins import FileUploadMixin

class NewBlogView(FileUploadMixin, View):

    template_path = "cbs/new_blog_view.html"
    upload_path = "/static/uploads"

    def get(self, request):
        ctx={
            "users" : User.objects.values(),
            "status" : BlogModel.BLOG_STATUS
        }
        return render(request, self.template_path,ctx);

    def post(self, request):
        result={}

        if request.FILES.get("path") is not None:
            file_list = request.FILES.getlist("path")
            result = self.upload(file_list, allowed_mime_types=[])

        id = request.POST.get('blog_id') or None

        blog, created = BlogModel.objects.update_or_create(
            pk=id,
            defaults={
            'user' : User.objects.filter(pk=request.POST["user"]).get(),
            'title' : request.POST["title"],
            'content' : request.POST["content"],
            'status' : "PUBLISH",
        })

        if result.get('files'):
            for file in result.get('files'):
                blog.blogs.create(**{
                    "blog" : blog,
                    "path" : file,
                })

        return redirect(reverse("class_based_views:new-blog-view"))
        

In view NewBlogView we have first added FileUploadMixin and after that View. This is an important use-case and the View classes must be always in MRO order.
First, there must be mixins and View last.

In urls.py.

from django.urls import path
from class_based_views import views

app_name = 'class_based_views' 
urlpatterns = [
    # custom mixins
    path('new-blog-view', views.NewBlogView.as_view(), name='new-blog-view'),
]

Conclusion

So you have come to the conclusion part of our Django Class-Based Views, Mixins, LoginRequiredMixin and Custom Mixins post.
If you like this then please share and for queries comment below. We’ll reach to you soon.

Related Posts

Summary
Review Date
Reviewed Item
Django Class-Based Views, Mixins, LoginRequiredMixin and Custom Mixins
Author Rating
51star
1star1star1star1star
Software Name
Django Web Framework
Software Name
Windows Os, Mac Os, Ubuntu Os
Software Category
Web Development