Django Class-based views | Decorators, Methods, Template, Redirect view

In this post, we’ll be learning about Django Class-based views | Decorators, Methods, Template and Redirect view.

Table of Contents

Introduction to Class-based views

The Django Class-based views as an improvement over Function-based views in-terms of code reusability. On request Class-based views returns function corresponds to the request method that will be dispatch and rendered to HTML.

Advantages of using Class-based views

  • The Class-based views allow to structure view and return associated function with respect to request method.
  • Code reusability is extended by using inheritance.
  • The functionality of Class-based views can be improved using mixins,method_decorator or inheriting classes.
  • Managing a business login is convenient in Class-based views.

Create a Simple Class-based View

The Class-based view can be created in apps views.py file.
Django provides three base views they are:

  • View: To use this you must import from django.views import View
  • TemplateView: Used for displaying HTML Template
  • RedirectView: For redirecting to another view.

Example

class MyView(View):
    counter=100
    def get(self,request):
        self.counter+=1
        return HttpResponse("counter : {}".format(self.counter))

class MyView inherits View which provides basic functionalities of Class-based views.Base View has only selected/approved method that you could call and they are get, post, put or patch, delete.

To add a class-based view in urls.py use with the .as_view() method this will prepare the class to send callable view in response.

from class_based_views import views
urlpatterns = [
    path('example-1', views.MyView.as_view(), name='example-1'),
]

Example by Snippet

It takes practise to get used to Class-based views. So we have working example where we create a blog with images and save user who created them.
Let us get started by creating models BlogModel and BlogFilesModel which saves a file.

In models.py.

from datetime import datetime
from django.db import models
from django.contrib.auth.models import User

class BlogModel(models.Model):

    BLOG_STATUS = (
        ('PUBLISH', 'Publish'),
        ('DRAFT', 'Draft'),
    )

    blog_id = models.AutoField(primary_key=True)
    user = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name='blogs')
    title = models.CharField(max_length=255)
    content = models.TextField(blank=True, null=True)
    status = models.CharField(max_length=7, choices=BLOG_STATUS)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta():
        db_table = 'blogs'
        verbose_name = 'Blog'
        verbose_name_plural = 'Blogs'

    def __str__(self):
        return self.title


def blog_directory_path(instance, filename):
    return 'uploads/blogs/{}'.format(filename+str(datetime.now()))

class BlogFilesModel(models.Model):
    blog_files_id = models.AutoField(primary_key=True)
    blog = models.ForeignKey(
        BlogModel, on_delete=models.CASCADE, related_name='blogs',blank=True)
    path = models.FileField(blank=False, null=False,upload_to=blog_directory_path)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta():
        db_table = 'blog_files'
        verbose_name = 'Blog'
        verbose_name_plural = 'Blogs'

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

In views.py.

from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.views import View
from class_based_views.models import BlogModel, BlogFilesModel
from django.contrib.auth.models import User
import os
from django.conf import settings
from django.urls import reverse

class BlogView(View):
    template_path = "cbs/simple_view.html"
    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 = file_upload_interface(request, file_list, dir_path="static/uploads/", 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:blog-view-1"))

Now to upload the file we have created function file_upload_interface this will upload files and return their names. Put this code below BlogView.

#This function uploads multiple files and returns the name of file
def file_upload_interface(request, file_list, dir_path = "uploads/", allowed_mime_types=[]):
    if not os.path.exists("{}/{}".format(settings.BASE_DIR,dir_path)):
        os.makedirs("{}/{}".format(settings.BASE_DIR,"/static/uploads"),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

Now it’s time to create URLs to our BlogView. Append below code in urls.py file.

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

app_name = 'class_based_views' 

urlpatterns = [
    path('blog-view-1', views.BlogView.as_view(), name='blog-view-1'),
]

We need to create template in-order to store post data to BlogView.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Django Class-base views</title>
</head>
<body>
    <div>
        <form action="{% url 'class_based_views:blog-view-1' %}" method="post" enctype="multipart/form-data">
            {% csrf_token %}
            <div>
                <label for="user" >User</label>
                <select id="user" name="user" >
                    <option value="">Choose</option>
                    {% for user in users %}
                    <option value="{{user.id}}">{{user.first_name}}</option>
                    {% endfor %}
                </select>
            </div>

            <div>
                <label for="title" >Blog Title</label>
                <input type="hidden" id="blog_id" name="blog_id" value="{% firstof blog.id '' %}" >
                <input type="text" id="title" name="title" value="{% firstof blog.title 'sample title' %}" >
            </div>
            

            <div>
                <label for="content" >Blog Content</label>
                <textarea type="text" id="content" name="content">{% firstof blog.content 'sample description' %}</textarea>
            </div>

            <div>
                <label for="status" >Status</label>
                <select id="status" name="status" >
                        <option value="">Choose</option>
                        {% for a,b in status %}
                        <option value="{{a}}">{{b}}</option>
                        {% endfor %}
                </select>
            </div>

            <div>
                <label for="path" >Files</label>
                <input type="file" id="path" name="path" multiple>
            </div>

            <div>
                <input type="submit" value="Submit" >
            </div>

        </form>
    </div>
</body>
</html>

This is how our final output looks like.

Django Class-based View blog post form

Class-based views decorators and class methods

Just like normal functions, we can add decorators to class methods that we want to run before we call that function.
To use decorators on methods we must import method_decorator from from django.utils.decorators import method_decorator.

The method_decorator takes a function as an argument and returns a callable function. You can add multiple of these decorators one below the other.

def custom_decorator():
    def decorator(func):
        def wrapper(request, *args, **kwargs):
            print("custom_decorator")
            return func(request, *args, **kwargs)
        return wrapper
    return decorator    

The custom_decorator is a function that will be called before the class-based view method. It has an inner function decorator which takes a function as an argument and wrapper function takes request, *args, **kwargs as argument which belongs to class-based view method.
It is required that decorator must return the callable method after conditional checking or else redirect to another URL.

Django also provides built-in decorators which are login_required, permission_required and never_cache.

Note

The process of applying these decorators is explained below.

Applying Multiple Decorators to method

from django.utils.decorators import method_decorator

def custom_decorator():
    def decorator(func):
        def wrapper(request, *args, **kwargs):
            print("custom_decorator")
            return func(request, *args, **kwargs)
        return wrapper
    return decorator

def second_custom_decorator():
    def decorator(func):
        def wrapper(request, *args, **kwargs):
            print("second_custom_decorator")
            return func(request, *args, **kwargs)
        return wrapper
    return decorator

class CustomView(View):
    @method_decorator(custom_decorator())
    @method_decorator(second_custom_decorator())
    def get(self,request):
        return HttpResponse("ok")

Class Methods

In order to return a proper callable function in response, Django class-based views have few classonlymethod as_views().

The as_views() a method is an entry point for a request-response process. This method binds request object to class and has an inner method view(request, *args, **kwargs) this method return dispatch() method which will determine which method must be a triggered for a given request.
If the request method does not match then it returns Method Not Allowed exception.

Note

Some of the available request methods are : get, post, put or patch, delete. They are also called approved HTTP list.

The Method as_views() is called in urls.py file as .as_view().

Class-based view dispatch() method

The dispatch() method responsible for dispatching right method which corresponds to the request method. The Method Not Allowed with status_code=405 is triggered by HTTP method not available in approved lists.

Class-based view TemplateView

The TemplateView is for the generic purpose where you want to work on a specific view. To use this you have to import TemplateView from from django.views.generic.base import TemplateView.
This view in proper suited for a static template. But you can also use to pass context data to the template by overriding method get_context_data(self, **kwargs).

Real-Life Example

In views.py.

from django.views.generic.base import TemplateView
class HomeView(TemplateView):
    template_name = "cbs/home.html"

    def get_context_data(self, **kwargs):
        context = super(HomeView,self).get_context_data(**kwargs);
        context["blogs"] = BlogModel.objects.all()
        return context

In template/cbs/home.html.

<div>
    <h1 style="text-align: center;" >Welcome to Home</h1>
    <div>
        <h3 style="text-align: center;" >Blog List</h3>
        <table border="1" style="width: 40%;border-collapse:collapse;">
            <thead>
                <tr>
                    <th>User</th>
                    <th>Title</th>
                    <th>Status</th>
                </tr>
            </thead>

            <tbody>
                {% for blog in blogs %}
                <tr>
                    <td>{{blog.user.first_name}} {{blog.user.last_name}}</td>
                    <td>{{blog.title}}</td>
                    <td>{{blog.status}}</td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
</div>

This is how our final output looks like.
Dajngo class-based views - List of Blog Posts

Class-based view RedirectView

The Django RedirectView is used to redirect to external or internal URL. This view inherits Base View.
The RedirectView view provides a convenient way to redirect to 301 permanent and 302 temporary URL.

To uses this view import RedirectView from from django.views.generic.base import RedirectView.

Attributes

  • permanent: If set to True than redirects with 301 permanent redirect else with 302 temporary redirects.
  • URL: Specify the URL to redirect. example: home-page.
  • pattern_name: You can specify URL name and it implicitly adds it to reverse() function. example : class_based_views:home-page.
  • query_string: If set to True redirects with a query string. Get query string from request.META.get('QUERY_STRING', '').

Example

In views.py.

from django.views.generic.base import RedirectView
class HomeRedirectView(RedirectView):
    permanent = False
    url = None
    pattern_name = 'class_based_views:home-page'
    query_string = True

In urls.py.

app_name = 'class_based_views' 
urlpatterns = [
    path('home-page', views.HomeView.as_view(), name='home-page'),
    path('home-redirect', views.HomeRedirectView.as_view(), name='home-redirect'),
] 

You can visit Django official docs section here here

Conclusion

So we have come to the conclusion part of our Django Class-based views | Decorators, Methods, Template and Redirect view 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 | Decorators, Methods, Template, Redirect view
Author Rating
51star1star1star1star1star
Software Name
Django Web Framework
Software Name
Windows Os, Mac Os, Ubuntu Os
Software Category
Web Development