Django Class-Based Views (how we do it)

There has been some talk about using “class based views” in Django to make view code more reusable. Apparently, there was even a presentation given about it. At Divvyshot, our code base is growing quickly and we are starting to reuse view code a lot. We’ve been refactoring all of our view code into classes, which makes them much easier to customize and mash together. Today I worked on some pretty exciting stuff that makes harnessing class-based views a snap.

Here’s a scenario we run into a lot.

  1. We have a view that displays information about a person with a url like /people/{id}/ where the id is the person object’s id field
  2. We have another view that displays information about an event with a url like /event/{slug}/ where the slug is some small number of alphanumeric characters uniquely identifying the event.
  3. We have a third view that shows information about an event relating to a person with a url like /event/{event_slug}/person/{person_id}/

The third piece to the above combination is where class-based views really pay off. We already have a bunch of code for working with a person’s data and a bunch of code for working with an event’s data. Wouldn’t it be great if we could just magically combine those two pieces of code and get all the data about both an event and a person and their relationship spit out onto a page? Well, we can and here is a simplified example of how it would look in our code base.


First there is the code for displaying a page about a person. I’ll explain in detail what’s going on.

class PersonDetail(Handler):
    template = "myapp/person/detail.html"
    person = fromurl("id").model(Person)
    def update(self):
        # do a bunch of stuff with self.person, for example
        if self.request.user.get_person() == self.person:
            self.context['page_title'] = "This is you"
        else:
            self.context['page_title'] = "%s %s" % (self.person.first_name, self.person.last_name)

In Detail

First you’ll notice that PersonDetail is a class and not a function. Django does not require views to be functions, just to be callable. PersonDetail subclasses Handler, which provides the __call__ method that’s necessary to make an instance of PersonDetail callable. In case you are jumping to conclusions, we do not use an instance of PersonDetail directly as a callable view for thread safety reasons that I will explain later.

The next thing you’ll see is that the template is specified as a class attribute with the path used by a template loader. The actual template rendering with a proper request context and all that jazz is abstracted away for us by a render method defined in the Handler class.

The next cool thing is the line person = fromurl("id").model(Person) which declaratively spells out the mapping from a url parameter to a Person model object. In particular, this says to pull out the id from the keyword arguments passed to the view function (based on the regex in the url conf) and use it to look up a Person object. By default, a 404 response is returned if no such object is found. This is sort of a replacement for person = get_object_or_404(Person, id=some_id) that works better with class-based views.

Next we have an update method, which gets called before the template is rendered. The purpose of the update method is just to prepare the view, and not to render a template to a response. That means adding stuff to the template context, adding additional attributes to the view instance, creating and processing forms, handling post data, etc. By putting all this logic in a standalone method, it is easy to modify the views behavior without having to worry about how the HttpResponse is created.

In this example, we put variables that should be made available to the template into self.context, which is just a dictionary. Alternatively, we could set attributes on the view instance itself, which is made available to the template. For example, having {{view.person.name}} in the template would yield the desired result. The request is also made available as the self.request instance attribute. By setting attributes in the view instance, it becomes much easier to share data between multiple helper methods of the view instance. For example, you might have a method that processes a GET request and a separate one for POST requests. Subclasses of your view can then selectively override just one of the methods and all the while you don’t have to worry about passing around any required data, like the request object itself.


Next we have the code for displaying stuff about an event. This is a lot like the PersonDetail class. The only thing to note is that the event attribute has an additional piece of metadata which says that the "slug" url parameter corresponds to the "url_slug" field of the Event model.

class EventDetail(Handler):
    template = "myapp/event/detail.html"
    event = fromurl("slug").model(Event, "url_slug")
    def update(self):
        # do a bunch of stuff with self.event
        self.context['page_title'] = self.event.name

As the final section of the scenario I outlined above, we will combine these two classes using python’s multiple inheritance support. Strictly speaking, it’s not necessary to use multiple inheritance to combine the functionality of the previous two classes, and frankly I haven’t decided yet whether it is a good idea. But as long as you are careful and know what’s going on in the base classes, it should be OK. This is python after all and we don’t do hand holding.

class EventForPerson(EventDetail, PersonDetail):
    template = "myapp/event/person.html"
    def update(self):
        # do a bunch more stuff with self.event and self.person
        EventDetail.update(self)
        PersonDetail.update(self)
        self.context['page_title'] = "%s and %s" % (self.person.first_name,
                                                     self.event.name)

This example is a bit contrived because the only thing any of the update methods do is set the same variable in the template context to something different. But the idea you should take home from this is that the views could have arbitrarily complex business logic that can be easily extended and customized through subclassing, just as can be done with Model objects, admin views, HttpResponse objects, or anything else that is object oriented. With the multiple inheritance setup we have, our template, myapp/event/person.html can access the person object, the event object, and anything else provided by the update methods from EventDetail and PersonDetail. We could even {% include %} the other two templates in myapp/event/person.html and they would just work. In creating the EventForPerson class, we didn’t even have to worry about how the Event and Person objects get looked up from the parameterized url. If we refactor the object lookup later (for example, switching from person ids to person slugs), we’ll only have to change the code in one place.

Url confs

Now for a quick note about how these get hooked up in a url conf file. You might be tempted to do something like this:

urlpatterns += patterns('',
    url(r'^event/(?P<slug>[\d\w\-]+)/person/(?P<id>\d+)/', EventForPerson()),
)

where the EventForPerson class is instantiated so as to provide the url conf with a callable object. But this means you would have one instance of EventForPerson for every request that gets processed. Besides this not being thread safe, it’s just plain confusing because the update methods might “dirty up” the instance while processing one request, and that might affect the next request that gets processed. To avoid that, our urlconf looks like this:

urlpatterns += patterns('',
    url(r'^event/(?P<slug>[\d\w\-]+)/person/(?P<id>\d+)/', EventForPerson.view),
)

where EventForPerson.view is just a class method that instantiates and calls a brand new instance of EventForPerson for each request, passing in whatever parameters it receives and returning whatever result it gets. Unfortunately, due to a limitiation of Django, you cannot use the handy string notation url(r'^some-regex', "myapp.views.EventForPerson.view") to achieve the same result. So you have to import the view classes into the url conf.

Dealing with conflicting regex groups in a urlconf

The last feature I want to briefly mention is how we deal with conflicting groups in a urlconf. Suppose that both our base classes, PersonDetail and EventDetail looked up objects based on a regex group named “id”. If we wanted to combine the these two view classes into one, the url regex pattern would have to use different group names. The pattern might look like ^event/(?P<event_id>\d+)/person/(?P<person_id>\d+)/. Even though the base classes are looking for the “id” group, we can override their behavior in a subclass. It would look like this:

class EventForPerson(EventDetail, PersonDetail):
    template = "myapp/event/person.html"
    event = EventDetail.event.fromurl("event_id")
    person = PersonDetail.person.fromurl("person_id")
    def update(self):
        # do a bunch more stuff with self.event and self.person

Without having to know which models are used to look up person and event, I can still reconfigure which parts of the url get used to look them up.

Conclusion

If you don’t need to reuse your view code, you shouldn’t bother writing them as classes. If you do need to reuse view code, writing them as classes is the only sane way to do it. The utility classes we use at Divvyshot for all our class-based views are still baked into the code base but I hope to open source the useful bits soon. If you are interested in using a similar class-based view implementation, let me know and I’ll move the open sourcing of these utilities higher up on my to-do list.

About these ads
7 comments
  1. Hi, I love the approach you have taken here and can already see a whole plethora of uses for it in the projects I work on. I dont suppose you’re able to share the content of the Handler method are you? Would love to see how you’re handling things in there.

    Thanks in advance… my fingers are crossed :)

  2. You can take a look at http://bitbucket.org/pcardune/sweetness/src. I started factoring out that part of our code so it was usable by other people… this little python package is how far I got. The best files to look at are:

    http://bitbucket.org/pcardune/sweetness/src/tip/src/sampleapp/views.py (fairly well documented examples)
    http://bitbucket.org/pcardune/sweetness/src/tip/src/paulo/sweetness/__init__.py (the microframework itself, usable as a single file)

    I don’t have time these days to work on this more, but would be excited for someone else to take over.

  3. Hi. Sorry to annoy you with a setup related question here but i am trying to use your class based view src but keep getting the following error when attempting to reference the view classmethod from my view in urls.py (i.e. views.ViewName.view).

    The error i am getting is “‘function’ object has no attribute ‘view’”

    Does that mean anything to you? If, in the shell, I create a reference to the Handler class i can easily reference the view method… but inheriting my actual view from Handler, doesnt seem to make the ‘view’ classmethod visible / available. I dont suppose you have any bright ideas do you?

    Thanks

    Ben

  4. nicolas said:

    pretty cool. i dont know the python ecosystem so much, has there been any emerging standard solution in the meantime, or do you still recommend using your framework ?

    thanks
    Nicolas

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 76 other followers

%d bloggers like this: