Django class-based views
Introduction
Django 1.3 added class-based views, but neglected to provide
documentation to explain what they were or how to use them. So here's
a basic introduction.
Example of a very basic class-based view
Let's start with an example of a very basic class-based view.
urls.py:
...
url(r'^/$', MyViewClass.as_view(), name='myview'),
...
views.py:
from django.views.generic.base import TemplateView
class MyViewClass(TemplateView):
template_name = "index.html"
def get(self, request, *args, **kwargs):
context = # compute what you want to pass to the template
return self.render_to_response(context)
This will render your template index.html with the context
you computed and return it as the content of an HttpResponse.
Introduction to class-based views
Now that we've seen the obligatory example, how about some instructions?
To create a class-based view, start by creating a class that inherits
from django.views.generic.View or one of its subclasses.
In your URLconf, specify the view method as the name of the new
class, plus .as_view():
url(r'urlpattern', MyViewClass.as_view(), ...)
In your class, write a get method that takes as arguments self
(as always), request (the HttpRequest), and any other arguments
from the request as specified in your URLconf.
In your get method, use the same logic you'd have used in an old
view, except that you can assume the request method is GET. Return an
HttpResponse as usual.
If you need to handle POST, write a post method, just like your get
method except that you can assume the request method is POST.
Any request method that you don't write a handler method for will
automatically get back a "method not allowed" response; you don't have
to do anything special.
Example:
from django.views.generic import View
from django.shortcuts import render
class MyViewClass(View):
def get(self, request, arg1, keyword=value):
return do_something()
def post(self, request, arg1, keyword=value):
return do_something_else()
Handy subclasses of View
Django comes with a number of useful subclasses of View that provide
some of the function that often ends up as boilerplate in views, just
by inheriting from them. You saw TemplateView being used already.
You'll probably want to base your views on TemplateView almost
anytime you're generating the content for a response.
Another useful one is RedirectView. This can be used to redirect
all requests. Example:
from django.core.urlresolvers import reverse
from django.views.generic import RedirectView
class MyRedirectView(RedirectView):
url = reverse(...)
That is a complete view, and will return a redirect to url on any
GET, POST, or HEAD request.
You can optionally set permanent = False to return a temporary
redirect instead of the default permanent redirect, and query_string
= True to include any query string from the incoming request on the
redirect URL:
from django.core.urlresolvers import reverse
from django.views.generic import RedirectView
class MyRedirectView(RedirectView):
url = reverse(...)
permanent = False
query_string = True
Decorators
Unfortunately, using decorators with class-based views isn't quite as
simple as using them with the old method-based views.
Maybe you're used to doing this:
from django.contrib.auth.decorators import login_required
@login_required
def myview(request):
context = ...
return render(request, 'index.html', context)
With class-based views, you have to decorate the .dispatch() method of
the class view, which means you have to override it just to decorate
it. And you need to decorate the decorator, because the decorators
provided by Django expect to be decorating method-based views, not
class-based ones:
from django.contrib.auth.decorators import login_required
from django.views.generic.base import View
from django.views.utils.decorators import method_decorator
class MyViewClass(View):
def get(self, request, **kwargs):
context = ...
return render(request, 'index.html', context)
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(MyViewClass, self).dispatch(*args, **kwargs)
This is an area of class-based views that could use some improvement.
You could apply the decorator in urls.py without needing so much
extra code:
urls.py:
from django.contrib.auth.decorators import login_required
...
url(r'^/$', login_required(MyViewClass.as_view()), name='myview'),
...
but that moves the policy from the view code to the URLconf, which is
not where people will be expecting to have to look for it, so I
wouldn't recommend it.
Passing arguments to the view
The method signature for get(), post(), etc. in a view class is:
def get(self, request, *args, **kwargs)
Any unnamed values captured in the URLconf regular expression are
passed in args, and any named values are passed in kwargs, just
like before.
You can pass extra arguments to your view using the third element
of your URLconf, the same as before, or using a new technique -- passing
them to the .as_view() call in your url settings. E.g.
...
url(r'^/$', MyViewClass.as_view(extra_arg=3), name='myview'),
...
One warning - don't accidently write MyViewClass(extra_arg=3).as_view().
That'll still appear to work, but that extra_arg is just thrown away.
Where's the beef?
So far, all we've done is the same behavior, written using a different syntax. But
class-based views enable a whole new level of function.
Suppose you've got a view that displays some data on a web page, and you write it
as a class-based view. Maybe something like this:
from django.views.generic.base import TemplateView
class MyViewClass(TemplateView):
template_name = 'index.html'
def get(self, request, **kwargs):
# Lots of complex logic in here to compute 'context'
self.render_to_response(context)
Now you're asked to provide an HTTP API that returns the same data in json.
Start by refactoring your existing class slightly, moving your business
logic out of the get() method:
from django.views.generic.base import TemplateView
class MyViewClass(TemplateView):
template_name = 'index.html'
def compute_context(self, request, **kwargs):
# Lots of complex logic in here to compute 'context'
return context
def get(self, request, **kwargs):
self.render_to_response(self.compute_context(request, kwargs))
Now, write a new class that subclasses your original class, uses the
same method to compute the data, but overrides get() with different
rendering code:
class MyJsonViewClass(MyViewClass):
def get(self, request, **kwargs):
data = self.compute_context(request, **kwargs)
# Very naive way to put your data into json, but a good starting place
content = json.dumps(data)
return HttpResponse(content, content_type='application/json')
Add a new URL to urls.py pointing to your new class-based view, and you're done. All
the logic you worked out earlier is still in use, and the power of subclassing let you
provide the data in a new format almost effortlessly.
Class-based views for common policy
The previous example was still something you could have done almost as
easily with method-based views, by refactoring your code into separate
methods and calling them from all your views.
A more powerful use of the new class-based views is to provide common
function for many views. If you have a site with many views, and they
all inherit from a common view, then you have the potential to change
behavior across the site by changing that one view.
Previously, you would probably have used middleware for this kind of thing.
The problem with middleware is that it's completely hidden from the view
code. When working on your view, you won't even know middleware is affecting
things unless you go look at the settings and track down each piece of
middleware configured there.
Furthermore, middleware affects every request, not just the views you
really wanted it for.
With a common class-based view, every view affected is declared to
inherit from that view, making it obvious that we're inheriting
behavior from elsewhere. With a good IDE, you can even jump straight
to that superclass to inspect it. Any view that doesn't need the
common behavior doesn't have to inherit it.
References
The only documentation page that really discussed class-based views
in Django 1.3 is this one:
https://docs.djangoproject.com/en/1.3/topics/class-based-views/
Some of the rationale for the current design of class-based views,
and pros and cons of some alternatives that were considered, are
documented here:
https://code.djangoproject.com/wiki/ClassBasedViews
Beyond that, the best advice I can give is to go read the code. The
code for the base View is surprisingly small, and can be found at
django/views/generic/base.py.