Django Class-based Views

June 3, 2020, 5:36 p.m.

Django · 4 min read

Django Class-based Views

If you are still relatively new to Django, you most likely use function-based views (FBV) to handle requests.  Most beginner tutorials utilize function-based views given the straightforward implementation.  For example, POST and GET HTTP request methods are handled with conditional statements (if request.method =="POST":).  The logic of the function is therefore easy to grasp.  However, in recent Django versions, class-based views (CBV) have been implemented to afford programmers a degree of reusability and pre-configured functionality.  To be clear, CBV's do not replace FBV's as their superior counterpart.  Both have their own advantages as will be discussed.

 

FBV's vs CBV's

To get a better understanding of the two different ways of writing your views, here's the same example written in both formats.

views.py

from django.shortcuts import render, redirect
from .forms import PlayerForm
from django.views.generic import View


# FBV
def homepage(request):
	if request.method == "POST":
		form = PlayerForm(request.POST)
		if form.is_valid():
			form.save()
		return redirect("main:homepage")
	players = Player.objects.all()
	form = PlayerForm()
	return render(request, "home.html", {"form": form})

# CBV
class home(View):
	template_name = "home.html"
	form_class = PlayerForm

	def get(self, request, *args, **kwargs):
		form = self.form_class
		return render(request, self.template_name, {'form': form})

	def post(self, request, *args, **kwargs):
		form = self. form_class(request.POST)
		if form.is_valid():
			form.save()
		return redirect("main:home")

urls.py

from django.urls import path
from . import views

app_name = "main"   

urlpatterns = [
	path("", views.homepage, name="homepage"),
	path("home", views.home.as_view(), name="home"),
]

 

Notice for the CBV path in urls.py, we call as_view() whereas in our FBV path, we simply call the function.  Also notice that CBV's do not need a conditional statement to detect whether the request method is GET or POST.  Instead, each type of request method has its own method.  Those methods also have the parameters: self, request, *args, and *kwargs.  Self refers to the class itself and is used in the GET and POST methods to pass the template and form as context.  The request is the same HTTP request object normally used in FBV's that contains metadata on the path/user/method/etc.  *Args and *kwargs represent any arguments or keyword arguments passed as URL parameters.  For example:

views.py

class home(View):
	template_name = "home.html"
	form_class = PlayerForm

	def get(self, request, *args, **kwargs):
		form = self.form_class
		first_name = kwargs['name']
		return render(request, self.template_name, {'form': form, "first":first_name})

	def post(self, request, *args, **kwargs):
		form = self. form_class(request.POST)
		if form.is_valid():
			form.save()
		return redirect("main:home")

urls.py

from django.urls import path
from . import views

app_name = "main"   

urlpatterns = [
	path("", views.homepage, name="homepage"),
	path("home/<name>", views.home.as_view(), name="home"),
]

 

It's also possible to override the template for CBV's in the URL path as well:

urls.py

from django.urls import path
from . import views

app_name = "main"   

urlpatterns = [
	path("", views.homepage, name="homepage"),
	path("home", views.home.as_view(template_name="contact.html"), name="home"),
]

 

So how do CBV's actually work?  To gain a better understanding take a look at the View the class we inherited.  

This is an abbreviated version of the class.  See the entire class in django/views/generic/base.py

class View:
    """
    Intentionally simple parent class for all views. Only implements
    dispatch-by-method and simple sanity checking.
    """

    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

    def __init__(self, **kwargs):
        """
        Constructor. Called in the URLconf; can contain helpful extra
        keyword arguments, and other things.
        """
        # Go through keyword arguments, and either save their values to our
        # instance, or raise an error.
        for key, value in kwargs.items():
            setattr(self, key, value)

    @classonlymethod
    def as_view(cls, **initkwargs):
        """Main entry point for a request-response process."""
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError("You tried to pass in the %s method name as a "
                                "keyword argument to %s(). Don't do that."
                                % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.setup(request, *args, **kwargs)
            if not hasattr(self, 'request'):
                raise AttributeError(
                    "%s instance has no 'request' attribute. Did you override "
                    "setup() and forget to call super()?" % cls.__name__
                )
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view

...
    def dispatch(self, request, *args, **kwargs):
        # Try to dispatch to the right method; if a method doesn't exist,
        # defer to the error handler. Also defer to the error handler if the
        # request method isn't on the approved list.
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)

First, the class lists the valid HTTP request methods and then the init method assigns all keyword arguments to an instance of the class.  Next, we can see why we called the as_view() method in urls.py in our earlier CBV example.  After some brief validation, the method actually defines a function, assigns any keyword arguments, and returns the function to handle a request just like a function-based view.  In other words, the same methodology underpins both CBV's and FBV's. The goal of CBV's, however, is to promote reusability.


0
Subscribe now

Subscribe to stay current on our latest articles and promos





Post a Comment
Join the community

0 Comments