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.

I'm a developer out of San Francisco CA working at a startup.

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

Finally, you should follow me on twitter.

Ruminations

  • "generic z-pak <a href=http://sefsa.org>buy azithromycin</a>"
    at 7:53p.m. Aug. 27, 2010 | permalink

  • "How do i come up with cash from online gambling? <img>http://shrtn.info/smile/ref.php</img>"
    at 2:50a.m. Aug. 25, 2010 | permalink

  • "http://needman.ru замуж за иностранца <a href=http://needman.ru>знакомства с иностранцами</a>"
    at 12:59p.m. May 18, 2010 | permalink

  • "Yebhewjw <a href="http://yebhewjw.de">yebhewjw</a> http://yebhewjw.de yebhewjw http://yebhewjw.de"
    at 11:41p.m. April 29, 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

  • "maybe you are right. but how often robots.txt is actually accessed? and how much overhead there is? I'm curious - quantitatively - how big of ..."
    at 7:13p.m. Dec. 12, 2009 | permalink

  • "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. ..."
    at 9:14a.m. Nov. 2, 2009 | permalink

  • "That was an inspiring post, I think Drupal is great! how could you hate it so much, Thanks for writing, most people don't bother."
    at 11:14a.m. Oct. 28, 2009 | permalink

  • "@Evgeniy. Yes at: http://code.google.com/p/django-alfresco/"
    at 10:42a.m. Oct. 22, 2009 | permalink

  • "Is this released as an open source project?"
    at 1:21a.m. Oct. 22, 2009 | permalink

  • "Interesting, thanks for the examples that you have shared, these are great... Anyway, thanks for the post"
    at 7:55a.m. Oct. 16, 2009 | permalink

  • "Quite inspiring, looks pretty easy aswell, as you have laid it out in such a way, great work, keep it up Thanks for bringing this ..."
    at 10:01a.m. Oct. 8, 2009 | permalink