You are on page 1of 34

Alternative Views

Ben Firshman

I’d like to bring your attention to a tale of neglect in the Django community
Models

Models get a huge amount of attention - queryset refactor, multi-db


Even templates - caching, if tag, all sorts of filters and tags

Something missing...
Models

Templates

Models get a huge amount of attention - queryset refactor, multi-db


Even templates - caching, if tag, all sorts of filters and tags

Something missing...
Models

Templates

Models get a huge amount of attention - queryset refactor, multi-db


Even templates - caching, if tag, all sorts of filters and tags

Something missing...
Views

.... that’s views.

How views work - generic views in particular - have barely been touched since Django was
released.

I’m going to explain one problem with views that needs fixing
View

At their heart, they are simple

They are functions that take a request and return a response


request

View

response

At their heart, they are simple

They are functions that take a request and return a response


def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
return render_to_response(‘post_detail.html’, {
‘post’: post
})

This lets you write simple, clear, explicit views

(explain step by step)


(r’post/(\d+)/’,
‘django.views.generic.list_detail.object_detail’, {
‘queryset’: Post.objects.all()
})

Generic views let you write this less verbosely

BUT - they are very inflexible. How many people have used a generic view, then almost
immediately found they have to rewrite it?

Now say we want to fetch our posts by thread id and slug...


def post_detail(request, thread, slug):
post = get_object_or_404(Post,
thread__pk=thread,
slug=slug
)
return render_to_response(‘post_detail.html’, {
‘post’: post
})

We’ve had to rewrite the functionality of our generic view. This could be even more
complicated if we were using other generic view features

Say this view is a view in a reusable application - we’ll have to rewrite it again to change a
small bit of functionality
newforms-admin

To go on a bit of a tangent - a couple of years ago, we got newforms-admin.


* made admin work with newforms
* also added loads of hooks for customisability
class BookAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if not change:
obj.author = request.user
obj.save()

def has_change_permission(self, request, obj=None):


if not obj:
return True
if obj.author == request.user:
return True
else:
return False

admin.site.register(Book, BookAdmin)

Customisability by subclassing the admin class and overriding a bunch of methods

This is a simplified example - coincidently the same one Alex used in his talk earlier. It’s
overriding a hook in the admin that only lets users edit objects they’ve created.

Almost every decision made in the admin is done in a method that you can override. This
means you don’t have to rewrite the whole admin just to add an extra bit of functionality
Back in 2008, Joseph Kocherhans applied these ideas to views
request

View

response

With our simple model of views - I said views are a function that takes a request and returns
a response.

I lied - they are a *callable* that takes a request and returns a response.
__call__()

You can define this magic method on objects that gets called if you call the object like a
function
class PostView(DetailView):
queryset = Post.objects.all()

Joseph’s idea let you write views like this. This is equivalent of the generic view we had
earlier.

If we take an instance of PostView.....


(r’post/(?P<pk>\d+)/’, PostView())

... we can stick it in a urlconf, and it acts like a normal view.

DetailView has __call__() method which takes a request and primary key from the URL
dispatcher
class PostView(DetailView):
queryset = Post.objects.all()

def get_object(thread, slug):


return get_object_or_404(Post,
thread__pk=thread,
slug=slug
)

Now, say we want to extend it like we did before. We don’t have to rewrite the generic view,
just override a method

This is not a great example because its the same length as the simple function we had before.
But - views in functions can get very hairy very quickly - you can see how this method will
scale. You can even inherit from this class again to create a view with slightly different
functionality

There are also generic views like this for listing objects; creating, editing and deleting objects
with forms; and listing objects within certain time periods. There are even views for just
displaying something in a template, which all these views inherit from
from jingo import render_to_string, env

class JinjaMixin(object):
def render(self, names=None, context=None):
template = env.select_template(names)
return render_to_string(
self.request, template, context)

We can also do clever stuff. Mixins are really handy to alter the functionality of class-based
views.

This, for example, is a Mixin that makes any view render with Jinja instead of Django’s
templates.

TemplateView defines a method for rendering to a Django template. We can override this to
render to a Jinja template instead
class PostView(JinjaMixin, DetailView):
queryset = Post.objects.all()

If we add that mixin to our PostView we made earlier, it will now render with Jinja templates
class JsonMixin(object):
def render_to_response(self, names=None, context=None):
if self.kwargs.get('format') == 'json':
return self.get_response(
self.get_resource(context),
mimetype='application/json'
)
return super(JsonMixin, self).render_to_response(
names, context)

Outputs JSON as well as templates given a format keyword argument from the URL

Overriding a different method to the jinja example because we need to set the mimetype of
the response, but the idea’s the same
from django.core import serializers

class PostView(JsonMixin, DetailView):


queryset = Post.objects.all()

def get_resource(self, context):


return serializers.serialize('json',
context['object_list'])

Now we have a view that can output to JSON as well as templates. All the logic we’ve written
to output data to templates can be reused.

This could be used to write a really neat REST interface

Many API tools for Django mean you have to use different URL namespaces, which isn’t
RESTful
from django.core import serializers

class PostView(JinjaMixin, JsonMixin, DetailView):


queryset = Post.objects.all()

def get_resource(self, context):


return serializers.serialize('json',
context['object_list'])

and of course - if we hook in our Jinja plugin to this view, we have a view that outputs to Jinja
templates and JSON
django.views

Not just for generic views - this can become the standard way of writing views.

If everybody builds upon a single base view, cool stuff like Jinja mixins will work everywhere,
including reusable apps

That’s not saying we’re getting rid of the old way of writing views though - views are still just
a callable that takes a request and a response
class PostAdmin(admin.ModelAdmin):
def get_urls(self):
return patterns('',
(r'^custom/$', self.custom_view),
) + super(PostAdmin, self).get_urls()

These class-based ideas can also be used for other things.

One of my favourite things in the admin is that ModelAdmin is basically a class-based


urlconf. You include the admin into a urlconf like you would include a urls.py.

It lets you write urlconfs which can do clever routing, and subclass and extend them

Example - newspaper sections


class LoginConsumer(Consumer):
def do_index(self, request, extra_message=None):
        return self.do_login(request, extra_message)
    do_index.urlregex = '^$'

Simon Willison did a similar thing with django openid, but with a neater API.

You include a consumer into the urlconf like you include the admin interface, but it iterates
over all the methods on itself and finds ones with a url regex defined.

If somebody were to develop a generic way of writing class-based urlconfs, and set some
conventions, it could be a really useful pattern. Maybe the urlconfs in Django core could be
smarter too......
This class-based view stuff is all well and good, but ticket 6735 has been around for almost 3
years. I’d like to cover some of the history of this and what we need to do to get this into
Django core.

As Eric mentioned yesterday, class-based views need conventions. Once it’s a part of Django,
this will be the convention that views can be built upon.

It started with Joseph’s patches, which had a number of people working on it, but was never
finished. After 1.0, Jacob KM worked on it for 1.1, but again, never finished it.

Since then, it has drifted, and the wheel has been reinvented many times.
The problem is, class-based views are prime bike-shedding material. The details of
implementing them have been argued over many flame wars on django-dev.

It’s incredibly hard to decide on how it should work because it is one huge naming problem.
It’s designing an API - choosing method names.
Thread safety was another sensitive topic.

(explain)

This is in fact a tiny part of how class-based views work. If you don’t like how it works - just
write a mixin that changes it.
http://github.com/bfirsh/
django-class-based-views

Here is my stab at an API. It is based on Joseph and Jacob’s initial implementation, with work
from David Larlet and inspiration from Andrew Godwin.
http://github.com/bfirsh/
django-class-based-views

Please use, fork and improve. Docs and tests need writing
Questions?

@bfirsh
Credits
http://www.flickr.com/photos/gerlos/3119891607/
http://www.flickr.com/photos/buttersweet/33684613/

You might also like