Django class-based views
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.
... url(r'^/$', MyViewClass.as_view(), name='myview'), ...
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
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.Viewor one of its subclasses.
In your URLconf, specify the view method as the name of the new class, plus
url(r'urlpattern', MyViewClass.as_view(), ...)
In your class, write a
getmethod that takes as arguments
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
postmethod, just like your
getmethod 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.
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
= True to include any query string from the incoming request on the
from django.core.urlresolvers import reverse from django.views.generic import RedirectView class MyRedirectView(RedirectView): url = reverse(...) permanent = False query_string = True
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
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:
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
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
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
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
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.
The only documentation page that really discussed class-based views in Django 1.3 is this one:
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:
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