screeley.com

Class Based Views and Reusable Apps

April1

One of the major pain points of using reusable apps is modifying the view logic. No matter how many options you can pass into the view function, someone is going to want to do something different with it. I've been using Pinax for a little while now and modifying views is the only thing I don't love about it. Enter class based views. There is currently a ticket out there to make them part of Django 1.2 and there is a great example out on djangosnippets.org. Instead of trying to deal with everything in the url conf you get a class object with functions that you can override. The simple user will not know the difference between the two, while the advanced user can create custom views easily, without repeating logic. Who likes DRY?

A view can be broken down into a few parts. * Get the form. * Get the template * Get the context * Main logic function.

I have never really found it that usefully to change the logic for the view function, but I often need to change the context. Adding based on the request or the results, not just generic extra_context that most good views handle. Here is a very simple example of a generic detail class view:

class ClassDetailView(object):

    def __init__(self, model, template):
        self.model = model
        self.template = template

    def __call__(self, request, id, template_name=None, 
                                           extra_context={}):
        return self.main(request, id, template_name, 
                                           extra_context={})

    def main(self, request, id, template_name=None, 
                                           extra_context={}):
        #Logic
        obj = get_object_or_404(self.model, id=id)
        
        # Get Context
        context = self.get_context(request, obj)
        apply_extra_context(extra_context, context)
    
        #Get Template
        template = self.get_template(request, template_name)
        
        #Render Template
        rendered_template = template.render(context)
        return HttpResponse(rendered_template) 
      
    def get_template(self, request, template_name): 
        """ 
        Returns the loaded Template
        """
        if not template_name:
            template_name = self.template
        return loader.get_template(template_name)
    
    def get_context(self, request, obj): 
        """ 
        Returns the Context
        """ 
        return RequestContext(request, {'object': obj}) 

The call method is where the magic lives, it turns our class into a function, so once an instance has be instantiated, it can be called like one. You definitely don't need a main here, I just find it easier to override later. We would use is like such.

from feeds.models import Feed
from misc.views import ClassDetailView

detail = ClassDetailView(Feed, 'feeds/feed_detail.html')

You then set up the urls conf as such:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    # Feeds
    url(r'^(?P<id>\w+)/$', 'feed.views.detail', 
                                     name='feed_detail'),
)
</id>

Let's go back to reusable apps. I was working with solango last night and found the need to add variables to the context. The initial implementation of the view didn't let me do this. I would have had to copy the view, wrap it or do something else clever that I had no interest in doing. Enter class based views. I changed the view to a class and added a get_context function. The code is here, it's too long to put in this post.

This makes it much easier to mess with the view. I have an API of sorts, that needs certain variables in the context. Yes I know that I could create another template, but I really didn't want to have to keep the two in sync. By being lazy am I making my life harder? You decide. Here's the code.

from solango.views import SearchView

class APISearch(SearchView):
        
    def get_context(self, request, paginator, facets, 
                                              sort_links, form): 
        return RequestContext(request,
                 { "url": request.get_full_path(),
                   "object_list" : paginator.results.documents,
                   "current_page" : paginator.page,}) 

search = APISearch(template_name='feeds/feed_list.json')

Personally this is a much more elegant solution than copying the view, or some other hack. So for all you reusable app maintainers out there, use Class Based Views and make your users happy.

Comments

So can you bring a full example with code on how to override the original view with your new APISearch view class?

This is awesome, it would make working with reusable apps so much easier! Thanks for sharing.

Thanks for this! I knew this could be done, but had never seen it before. This could be very useful to share code between apps that do very similar things.

I'm curious though, it seems like this technique can be used regardless of Django. What changes are being proposed to Django to support this technique?

@Erik. You could override the original view by in your root urls.py. Just put this view before the original view.

@Brian. Check out the ticket I listed in the post. Basically it's going to make the generic views even easier to change/reuse.

Interesting,

thanks for the examples that you have shared, these are great...

Anyway, thanks for the post

Lovely idea! Thanks for sharing. I'm gonna have a closer look at the patch for Django 1.2. This could help switching template engines a lot. Make reusable apps work with Jinja2 is currently a really time intensive job.

Post Your Comment

I'm a developer out of Boston MA and I work for a consulting firm specializing in open source technologies.

This space will deal with the work I've participated in using the Django framework to build applications for enterprise clients.

Finally, I hate the word blog and Drupal.

Ruminations

  • "А сегодня день архивного работника. У вас на сайте есть "Архив"? Можете праздновать! :))"
    at 1:49p.m. March 10, 2010 | permalink

  • "А интересно, сам автор читает комментарии к этому сообщению. Или мы тут сами для себя пишем? :)"
    at 4:58a.m. March 9, 2010 | permalink

  • "Прошу прощения за оффтопик. Вы продаете сквозные ссылки с сайта? Если да, свяжитесь со мной, плз!"
    at 8:06p.m. March 8, 2010 | permalink

  • "Об этом уже писал кто-то из моих ЖЖ-френдов :("
    at 10:29a.m. March 8, 2010 | permalink

  • "У Вас долго загружается блог - видимо, хостинг плоховат"
    at 9:41p.m. March 6, 2010 | permalink

  • "I just discovered <a href=http://bit.ly/bMGrYw>SatelliteTV</a> on my PC! Ultra cheap at only $50 once off to get the software and an account on the Internet. ..."
    at 5:20p.m. March 4, 2010 | permalink

  • "Логотип мне нравится:)"
    at 8:47a.m. March 4, 2010 | permalink

  • "Девушки из твоих грёз на твоём рабочем столе. 1.Полностью бесплатно 2.100% безопасность вашего ПК 3.Новые девушки каждый день <a href=http://blogs.mail.ru/mail/erorulez/6605707A18ACC7D6.html>смотреть стриптиз бесплатно</a> http://blogs.mail.ru/mail/erorulez/6605707A18ACC7D6.html эгоистка стриптиз ..."
    at 5:08a.m. March 4, 2010 | permalink

  • "uh.. strange .."
    at 11:54p.m. March 3, 2010 | permalink

  • "Hi guys, I know this might be a bit off topic but seeing that a bunch of you own websites, where would the best place ..."
    at 11:12p.m. March 3, 2010 | permalink

  • "Thanks for this, unbelievable our developer has a robots no follow tag on our site, no wonder it wasn't being found by the search engines ..."
    at 7:40a.m. March 2, 2010 | permalink

  • "В Вашей RSS нельзя получать полные тексты записей, что ли?"
    at 9:37p.m. March 1, 2010 | permalink