Context in django views, DRY, reusable apps
DRY (Don't repeat yourself) is always a good coding practice I try to follow, at least whenever deadlines allow me to do so. Even for a bunch of code bits, it helps keeping clean and modular.
Talking about views, I find myself using different techniques and approaches to write a single function that centralizes all the logic and, at the same time, behave differently, depending on who is calling it.
Sometimes it is good to overload the number of arguments it receives, and use them as indicators and flags to switch to the desired behavior. A simple example, widely used in lots of Django apps and projects, is to pass a template variable from the kwargs positional argument in a URLconf:
url(
regex=r'^$',
view='list_posts',
name='list_posts',
kwargs={'template': 'yourtemplatepath/templates/yourtemplate.html'},
)
In your view, you receive the parameter and render either the template value passed with the 'template' parameter, or the default template otherwise:
def your_view(request, template='templates/default.html'):
...
return render_to_response(template, {}, context_instance=RequestContext(request))
If you want to add functionality to certain cases, instead of modifying the base view, you better create a wrapper function, which will be called before, adding all the stuff you need to execute before or after. For instance, another trivial example would be to check something before calling the view from the example above:
def wrapper_view(request):
if request.method == 'POST':
# raise error, or whatever
...
else:
return your_view(request, template='templates/another_template.html')
While integrating my personal blog application django-nomadblog (as a good djangonaut I use my own blogging system) into this blog (Nomadblue), I needed to pull out the RequestContext creation and population done in the django-nomadblog from the rest of the logic done in the view. In this scenario, the view from django-nomadblog is the function that contains all the code logic, including the creation and population of the RequestContext object, so a simple wrapper like the one in the previous example doesn't work. I don't want to modify the view to include extra arguments, because this hack obviously would break the philosophy of the app -- that is, be reusable and modular.
A good neat solution I ended up implementing is to split each view in django-nomadblog in two, the first acting as the regular django view receiving the request and rendering the response, and the second handling all the logic related to RequestContext creating and update. Let me put an example in order to get a better picture.:
def _nomadblog_context_view(request, context=None):
"""This is the function in charge of the context"""
ctxt_dict = {
'title': 'Jazz from hell',
'author': wazoo_function(), # and so on
...
}
context = RequestContext(request, ctxt_dict) if context is None \
else context.update(ctxt_dict)
return context
def nomadblog_view(request, template='templates/default.html'):
"""call the context view before rendering the response"""
context = _nomadblog_context_view(request)
# render to response using context
return render_to_response(template, {}, context_instance=context)
The only thing left was to create a view in my nomadblue project that calls the _nomadblog_context_view. This way, I didn't need to rewrite anything, repeat myself, or hack any existing pristine app. If I am planning to use django-nomadblog in other projects, I can keep a single copy of it, let's say in a global site-packages for example, and have each project importing the same code. One day I update the version of my blog app with new features or bug fixings, and automatically all the projects get the benefits.
To have a more complete -- and probably more self-explanatory of what I tried to explain in this post -- take a lot at the views.py. You can see four views (list_posts, show_post, list_categories and list_posts_by_category) which have their related context view function respectively, equally named with a prepended underscore.
