fíam

(rhymes with liam)

  • Shortcutting render_to_response

    June 9, 2008 at 21:12:49 CEST

    render_to_response is [supposed to be] a shortcut. But when you are using RequestContext, you end up with lots of lines reading like this:

    render_to_response('template.html', variables_dict, context_instance=RequestContext(request))
    

    I can type fast, but I don't like typing the same code again and again. Maybe I'm a DRY fanboy, but let's hope that's not the case.

    Over the last few months I've been using some techniques for avoiding typing context_instance=RequestContext(request) after every return statement, however I still haven't found the perfect one, since there's always a tradeoff between flexibility and the number of characters you have to type. So I think it's time to make an entry about it, get some feedback and post the results to django-dev.

    The most simple approach: wrapper function

    It's not pretty, nor pythonic, but it's simple and effective as hell. Just define a wrapper function and call render_to_response from it:

    def my_render(template, variables_dict, request):
       return render_to_response(template, variables_dict, context_instance=RequestContext(request))
    

    The object oriented way: inheriting from HttpResponse

    This is one of my favourite approaches, since it doesn't depend on render_to_response.

    class RenderedResponse(HttpResponse):
        def __init__(self, template, request, items = {}, **kwargs):
            t = loader.get_template(template)
            super(RenderedResponse, self).__init__(t.render(RequestContext(request, items)), **kwargs)
    

    In addition, this lets you add some template caching very easily:

    class RenderedResponse(HttpResponse):
        def __init__(self, template, request, items = {}, **kwargs):
            key = 'template_%s' % template
            t = cache.get(key) or cache_set(key, loader.get_template(template))
            super(RenderedResponse, self).__init__(t.render(RequestContext(request, items)), **kwargs)
    

    Code for cache_set (note the underscore) is in the second codeblock in Low-level cache decorators for Django.

    The uncommon way: using a class for the view

    I also like this approach, but for other cases. Sometimes, when a view gets too complicated, I like to split GET and POST into two different methods using a class. For example, I do this in the submit view for ffloat.it (btw, I'd be very happy if people started using the English version, it hasn't got any attention :( )

    class BaseView(object):
        def __call__(self, request, *args, **kwargs):
             # Exception handling omitted for clarity
             # Should return HTTP 501 if getattr() fails
             return getattr(self, request.method)(request, *args, **kwargs)
    
    class SubmitView(BaseView):
        def GET(self, request):
            ...
            return render_to_response...
    
        def POST(self, request):
            ...
            return render_to_response...
    
     #urls.py
     from ffloat.views import SubmitView
     urlpatterns = patterns(...
         (r'^submit/$', SubmitView()),
     ...)
    

    With this class hierarchy in place, we can insert render_to_response inside __call__ and let the view method return a tuple consisting of (template_name, items):

    class BaseView(object):
        def __call__(self, request, *args, **kwargs):
             # Exception handling omitted for clarity
             # Should return HTTP 501 if getattr() fails
             template_name, items = getattr(self, request.method)(request, *args, **kwargs)
             return render_to_response(template_name, items, context_instance=RequestContext(request))
    
    class SubmitView(BaseView):
        def GET(self, request):
            ...
            return ('template.html', {'foo': 'bar'})
    

    The magic way: decorators

    This approach is similar to the previous one, however it works on functions instead of classes. Use the following decorator and return the (template_name, items) tuple in the view function:

    def rendered(func):
        def rendered_func(request, *args, **kwargs):
             template_name, items = func(request, *args, **kwargs)
             return render_to_response(template_name, items, context_instance=RequestContext(request))
    
        return rendered_func
    

    You can even go a step further if the view is always rendered on the same template.

    class rendered_with(object):
        def __init__(self, template_name):
            self.template_name = template_name
    
        def __call__(self, func):
            def rendered_func(request, *args, **kwargs):
                items = func(request, *args, **kwargs)
                return render_to_response(self.template_name, items, context_instance=RequestContext(request))
    
            return rendered_func
    
        ...
        @rendered_with('template.html')  
        def some_view(request, foo_id):
            return SomeModel.objects.filter(foo=foo_id)
    

    A final tip

    This is not strictly related to render_to_response, but it's a good way to make your code shorter. Some people may find it obvious, but since Django tutorial examples always define a new dictionary when creating the context object, I didn't realize of locals() at first. For newbies, locals() returns a dictionary containing all the variables defined in the current scope. So you can change this:

    def some_view(request, foo_id, bar_id):
        obj1 = SomeModel.objects.get(foo=foo_id)
        obj2 = SomeOtherMode.objects.get(bar=bar_id)
    
        return render_to_response('template.html', {'obj1': obj1, 'obj2': obj2})
    

    To this:

        def some_view(request, foo_id, bar_id):
            obj1 = SomeModel.objects.get(foo=foo_id)
            obj2 = SomeOtherMode.objects.get(bar=bar_id)
    
            return render_to_response('template.html', locals())
    

    Do you like this approaches? Do you use something similar? Your feedback is greatly appreciated.

233 comments for "Shortcutting render_to_response"

(Won't be published)

Notify me of followup comments via e-mail

HTML is escaped, links are automatically converted