Shivling : Avoiding boilerplate in views in Django
I have been unable to find time to continue my Shiv series, however, a chat with a friend(@arihersh) who has just started learning django reminded me of a small piece of code I had written sometime back. The chat went something like this
I'd like to have html "widgets" that have certain actions, without having to create an artificial url + view to control the widget.
Maybe that's what your framework does?
I want to create a search bar that goes on any page.
To do this in Django, I think I need to: (1) create a search form, (2) put it into a template, (3) include the template into any other template.
I also need to create a view for the search bar, with its own url, right?
you need a search result view
So if I have the search bar on page x, I'll have to call the search bar's url from page x.
i.e. the url that the searcdh form will post to
Yes, actually that's the issue:
I found that on each page I include the search form, I'm writing a lot of boilerplate code into *each* view of the pages that use the search form.
What I'd like is to write the template and view for a unit once (e.g. a search form). Then only have to include it in the other page's template, without making any changes to the views in order to pass variables around
At first I suggested Shiv to make the views more modular, but it was kinda overkill for the particular project. Then I remembered a piece of code that I had written well after Shiv which can be viewed as a motivation for Shiv (though it was written after Shiv).
It basically consists of 2 files
This is for ajax views. Its almost identical to the one in Shiv. You will need to create a url entry in urls.py and follow some sort of url pattern so that the registered name can be passed.
Every ajax view contains a key member (func.key) which can be used to generate the desired url. You can look at the ajax_view function below to understand how the required view is called.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound class AjaxRegistry(object): _instance = None _registry = {} _index = 0 def __new__(cls, *args, **kwargs): if cls != type(cls._instance): cls._instance = object.__new__(cls, *args, **kwargs) return cls._instance def register(self, func): key = str(self._index)+func.func_name def wrap(*args, **kwargs): return func(*args, **kwargs) self._registry[key] = wrap self._index+=1 wrap.key=key wrap.name = func.func_name return wrap def ajax_view(request, view_name): if request.is_ajax(): return_type = "template" if request.GET: if "return_type" in request.GET: return_type = request.GET['return_type'] ar = AjaxRegistry() func = ar._registry[view_name] ret = func(request) ret['key'] = view_name if return_type == "template": return HttpResponse(get_template(ret['template']).render(RequestContext(request, ret))) else: json = simplejson.dumps(ret) return HttpResponse(json, mimetype='application/javascript') else: return HttpResponseNotFound()
This is where the main action happens. There are two kind of functions here. The ones which are called by urls.py directly and the other which are used by the first one to create the response.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
from django.contrib.auth.decorators import login_required from django.shortcuts import render_to_response from django.template import RequestContext from django.template.loader import get_template from django.http import HttpResponse from element_registry import AjaxRegistry ar = AjaxRegistry() def fragment(template): template = get_template(template) def wrapper(func): def inner(request, *args, **kwargs): di = func(request, *args, **kwargs) di['key'] = func.key if di.has_key('extra_key'): di['key'] = di['key'] + '+' + str(di['extra_key']) if request.is_ajax() and ajax_is_json: return di c = RequestContext(request, di) return template.render(c) inner.name = getattr(func, 'name', func.func_name) return inner return wrapper def make_and_render(template=None, mimetype="text/html", auto_render_sidebar=False): def wrapper(func): template = get_template(template) def inner(request, *args, **kwargs): ret = func(request, *args, **kwargs) args = len(ret) > 2 and ret[2] or args kwargs = len(ret) > 3 and ret[3] or kwargs centre = dict([(e.name, e(request, *args, **kwargs)) for e in ret[0]]) sidebar = dict([(e.name, e(request, *args, **kwargs)) for e in ret[1]]) context_vars = {'centre': centre, 'sidebar': sidebar, 'auto_render_sidebar': auto_render_sidebar} for k in kwargs: context_vars[k] = kwargs[k] c = RequestContext(request, context_vars) return template.render(c) return inner return wrapper ### ### Example Usage ### # This is the main view. It will include several fragments. Fragments can be divided in a datastructure based upon the need. # In the example below we are using 2 lists as needed by make_and_render wrapper. If you need a different datastructure, # make changes to make_and_render wrapper first @make_and_render(template='home.html') def home(request): centre = [frag1] sidebar = [frag2] return centre, sidebar # Below are 2 fragments @fragment(template='elem1.html') def frag1(request, *args, **kwargs): context = {} return context @fragment(template='elem2.html') @ar.register def frag2(request, *args, **kwargs): context = {} return {}
For example, suppose I have got a sidebar containing my Menu and the centre portion containing a header, search bar, and either some static text or search result. In this case I will make two main views corresponding to two main pages (The one with static content in the centre and the other one with search result) and 5 fragments. The entire key mechanism of the ajax functions is a bit involved andneeds to be understood and its interfacing with the urls.py and how it is passed to the html and from there to javascript. I am no expert of JS but I will try to outline the basics of it. I don't think I have done a good job of explaining it here and I am sorry for that. Ping me if you would like to know more.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
# This returns a list of links and their title, along with a boolean indicating if its the current page # Determination of current page can also be done in template, and perhaps thats a better place. @render(template='menu.html') def menu(request, *args, **kwargs): items = [('Home', '/', request.path == '/'), ('Search', '/', request.path == '/search')] return {'items': items} # Again, user authentication test might be done in template. # However, there could be other info that could be sent to header from here @render(template='header.html') def header(request, *args, **kwargs): return {'logged_in': request.user.is_authenticated()} @render(template = 'search_bar.html') def search_bar(request, *args, **kwargs): return {'q': request.GET.get('q', '')} @render(template = 'static_box.html') def static_box(request, *args, **kwargs): return {} # Dummy search view. As an example we are using a model called MyModel with a text field called body. # For rendering, we will require the number of results found and the page number along with the results. # This fragment can also be called via ajax. @render(template = 'search_result.html') @ar.register def search_result(request, *args, **kwargs): result = MyModel.objects.filter(body__contains=request.GET.get('q')) count = result.count() page = request.GET.get('page', 0) return {'items': result[page:page+10], 'count': count, 'page': page} @make_and_render(template='home.html') def home(request, *args, **kwargs): centre = [header, search_bar, static_box] sidebar = [menu] return centre, sidebar @make_and_render(template='search.html') def search(request, *args, **kwargs): centre = [header, search_bar, search_result] sidebar = [menu] return centre, sidebar, [], {'search_ajax_key': search_result.key} # We have passed search_result.key to this view's template. This can now be used to make ajax calls # We can put the key in some hidden element inside the html and then use it to construct our ajax-url # Our urls.py will have something like urlpatterns = patterns('', url('ajax-view/(?P<view_name>[\w-]+)/', 'ajax_view', {}, name="ajax_view"), ... )
And the html can create a hidden element like
$('#next').live('click', function(){ $.get($('#ajax-url').html(), somefunction); });
While the Javascript will override the Next link
<div id="ajax-url" style="display:none">/ajax-view/{{search_ajax_key}}</div>
Next, I will get back to talking more about Shiv. I hope the above helps in explaining the reason I started writing Shiv.