|

Django’s Class Based Views

A guest post by Garry Polley, a Python developer devoted to creating remarkable apps for the healthcare industry, and the creator of an online mathematics curriculum called GeckoMathematics. Education is a thriving passion for Garry and it’s what he would do if he were not a developer.

In the Django community there has been a long running dispute since version 1.3 around class based views. I plan to give some insight into how you should use class based views. This is not an article aimed at function based views vs class based views.

Why should I use class based views?

Here are some reasons:

Over the course of working on many projects we all tend to do some of the same repetitive tasks. Hey, that’s one reason we all love to use Django. For most applications the primary layers that change are the data, and how that data is displayed on the client. In most of our applications we just want to sling that data to the UI for our end users, and class based views provide the catapult to send our data to the client. In this post we’ll work through an example of a simple shopping cart to help illustrate this point. A mixin will be used later to help show just how easy it is to incorporate shopping cart contents on every request. Now let’s get started.

For most of our Django applications we’ll have a folder layout similar to the following:

/shopping
   __init__.py
   views.py
   models.py
   urls.py

urls.py and models.py usually look different for each of our applications. It’s in views.py we can get a lot of code reuse. Take this simple example that allows us to show all orders in our shopping cart:

# models.py

class ShoppingCart(models.Model):
    items = models.OneToManyField('Item')
    owner = models.ForeignKey(settings.AUTH_USER_MODEL)


# views.py

class ShoppingCartView(ListView):
    template_name = 'shopping/shopping_cart_list.py'

    def get_queryset(self):
        # This should have some logic to protect against errors,
        # keeping it simple for example.
        return ShoppingCart.objects.get(owner=self.request.user)

Once you hook up the view in your urls.py file and create the template shopping/shopping_cart_list.py, you’ll have a page ready for your users. By using the generic ListView, you can easily grab and supply a set of data. Now, this is a very simple case and shows the first possible bit of potential. By way of example we will see how easy it is to extend and test class based views, while generating less code. This leads us to…

How do I use class based views?

Here are three ways:

  1. Classy Class-Based Views
  2. Django source
  3. Django doc

Each of the resources above is a great place to look when you have questions about how class based views work. I recommend starting with the Classy Class-Based Views site after you’ve read the simple Django docs. From there look at the other sources, as well as Google for more information.

The first step when trying to figure out what generic class to use, is to figure out what data you want to display. If you want to show a grouping of one specific model, then ListView is a good option. For the most part, each generic class based view has its use-case built into its name. Here are some of those classes:

With all these options mentioned above, where does one start? Honestly you can use only the generic View and you’ll have plenty of advantages. However, I recommend you focus on these 3 class based views to start: TemplateView, FormView, and View.

Let’s start looking at the most basic View first. It has all the http methods: get, post, head, options, put, delete, and trace. We’ll only be looking at get and post since they are the more common use cases. You have probably seen a urls.py file like this one:

# urls.py
from django.conf.urls import patterns, url
from .url_helpers import dispatch

urlpatterns = patterns('',
    url(r'^users/$', dispatch(get='site.users_get', post='site.users_post')),
    url(r'^articles/$', dispatch(get='site.articles_get', post='site.articles_post)),
)

Notice the dispatch function? A lot of people have a custom implementation or use a library to achieve this routing of URL to view for a POST or GET call. Or they take care of this by having if else logic in their view, which isn’t very DRY and leads to a lot of boiler plate code. In comes one of the advantages of using generic views. Your urls.py just became this:

# urls.py
from django.conf.urls import patterns, url
from .views import UserView, ArticleView

urlpatterns = patterns('',
    url(r'^users/$', UserView.as_view()),
    url(r'^articles/$', ArticleView.as_view()),
)

So far this may not seem like a big win, however, let’s take a look at what our views would look like:

# views.py
from django.views.generic import View


class UserView(View):

    def get(self):
        # process get
        return render_to_response(context_dict, 'path-to-template-file')


class ArticleView(View):

    def get(self):
        # process get
        return render_to_response(context_dict, 'path-to-template-file')

The above views are very simple and I know exactly where my HTTP verb maps. I have a get method, which represents the GET HTTP request. Each method defined on the class is mapped directly via urls.py. If you don’t have a method defined, the user automatically gets a 405 response code, which is very nice when creating APIs. If this view is just serving a static file I recommend using TemplateView.

# views.py
from django.views.generic import TemplateView


class UserView(TemplateView):
    template_name = "path_to_template"


class ArticleView(TemplateView):
    template_name = "path_to_template"

In a nutshell, the above examples show how easy it is to hook up class based views into Django’s plumbing. Now we can get on to the good stuff – exactly how to harness the power.

Harnessing the power

In order for class based views to be extra useful, you’ll begin creating mixins. A mixin is a class you use in conjunction with the built in Django generic class based views, such as View. Adding mixins to a project helps reduce logic, and can make coding much easier. There is a great project that has a lot of mixins already, known as django-braces. So before you go and create your own mixins, make sure it doesn’t already exist in that project.

As an example mixin, you may need to write out data to a logging framework (think graphite, statsd, splunk, or whatever other data logger you may want to use). Here is a simple way to accomplish that goal:

# views.py

from functools import wraps
from django.utils.decorators import method_decorator

from .my_loggin_models import UserPageView


class LogViewMixin(object):
    """Mixin for loggin when a user has viewed a page"""
    page_name = None

    def dispatch(self, request, *args, **kwargs):

        if hasattr(request, 'user'):
            UserPageView.add(request.user, self.page_name)

        return super(LogViewMixin, self).dispatch(
            request, *args, **kwargs)

Now I’d use this mixin by simply creating a view like this:

class ShoppingCart(LogViewMixin, View):
    page_name = 'user-shopping-cart'

    def get(self, *args, **kwargs):
         # do stuff

With that bit of code I now know who has been looking at their shopping carts. This is a plain example but shows something we can do with a simple mixin. Now imagine we want to have the shopping cart’s content accessible on all of our pages. Here is a mixin that can do that:

from shopping.models import ShoppingCart

class ShoppingCartMixin(object):

    def get_context_data(self, *args, **kwargs):
        context = super(ShoppingCartMixin, self).get_context_data(*arg, **kwargs)

        context['shopping_cart'] = ShoppingCart.objects.get_or_none(owner=self.
          request.user)
        return context

Now any time you create a view (that uses get_context_data, which is any view that sub-classes content-mixin, TemplateView for example) you get the shopping cart for free. Using a mixin like this keeps you from having to create custom context processors that are run all of the time. This allows finer grained control and minimizes query costs.

For more details about Django, see the resources referenced below.

Not a subscriber? Sign up for a free trial.

Safari Books Online has the content you need

Introduction to Django is a video where you’ll learn the fundamentals of development with Django, generate a Django data model, and put together a simple web site using the framework.
Pro Django, Second Edition goes above and beyond other books, leaving the basics far behind and showing how Django can do things even its core developers never expected. By drawing on the dynamic nature of Python, you can write Django applications that stretch the limits of what you thought possible!
Instant Django 1.5 Application Development Starter will get you learning Django by creating a web application step by step. Offering you a quick but comprehensive immersion into the world of Python development, this book begins with a practical exploration of the framework’s powerful mechanisms and tools and finishes your journey by taking you through the creation of one sample application.

About the author

garry_headshot Garry Polley is a Python developer devoted to creating remarkable apps for the healthcare industry. Before joining his current employer, Garry helped to modify and create an online mathematics curriculum called GeckoMathematics. Education is a thriving passion for Garry and it’s what he would do if he were not a developer. He can be reached at garrypolley.com.

About Safari Books Online

Safari Books Online is an online learning library that provides access to thousands of technical, engineering, business, and digital media books and training videos. Get the latest information on topics like Windows 8, Android Development, iOS Development, Cloud Computing, HTML5, and so much more – sometimes even before the book is published or on bookshelves. Learn something new today with a free subscription to Safari Books Online.
|

One Response to Django’s Class Based Views

  1. bc says:

    I found this a really useful introduction to class-based views. I had not understood their purspose before. Thanks.