SAMPLE USAGE: {% load handytags %} {% outliner 'generic_section.html' companies sectionizers %} {% endoutliner %} generic_section.html: {% for section in sections %}
{{ section.key }}
{{ section.html }}
{% endfor %} calling view: city_method = lambda company: company.city type_method = lambda company: company.get_membership().type.name sectionizer_dct = { 'by_type': { 'key_method': type_method, 'css_class': 'company_type', }, 'by_city': { 'key_method': city_method, 'css_class': 'company_city', }, } desired_keys = ['by_city', 'by_type'] # can dynamically set the order sectionizers = [sectionizer_dct[k] for k in desired_keys] context['companies'] = Company.objects context['sectionizers'] = sectionizers return render_with_request(...) IMPLEMENTATION: class OutlinerNode(Node): def __init__(self, nodelist, sectionizer_node, results, sectionizers): self.nodelist = nodelist self.sectionizer_node = sectionizer_node self.results = results self.sectionizers = sectionizers def render(self, context): queryset = self.results.resolve(context) sectionizers = self.sectionizers.resolve(context) def render_one_section(objs, outline_level): # expand current context? new_context = Context({ 'objs': objs, 'outline_level': outline_level, }) return self.nodelist.render(new_context) inner_method = render_one_section # we are building up decorators from the inside out, popping off # the end of the list while len(sectionizers): sectionizer = sectionizers.pop() inner_method = self.section_decorator(sectionizer, inner_method) # this call in turns works from the outside in return inner_method(queryset, '') def section_decorator(self, sectionizer, inner_method): # It is intentional not to inline this method...otherwise you # deal with all kinds of scope issues with closures. def render(objs, outline_level): return self.generic_build_sections(objs, outline_level, sectionizer, inner_method) return render def generic_build_sections(self, results, outline_level, sectionizer, inner_render): try: key_method = sectionizer['key_method'] except: key_method = sectionizer.key_method results = list(results) results.sort(key=key_method) sections = [] i = 0 prefix = outline_level if outline_level: prefix += '_' depth = len(prefix.split('_')) for key, objs in groupby(results, key_method): i += 1 # human numbering for Outline outline_level = prefix + str(i) sections.append({ 'key': key, 'html': inner_render(objs, outline_level), 'outline_level': outline_level, 'depth': depth, }) context = Context({ 'sections': sections, 'sectionizer': sectionizer, }) return self.sectionizer_node.render(context) @register.tag def outliner(parser, token): nodelist = parser.parse(('endoutliner',)) parser.delete_first_token() bits = token.split_contents() if len(bits) != 4: raise TemplateSyntaxError, "%r tag takes three arguments" % bits[0] path, results_var, sectionizers_var = bits[1:] if path[0] in ('"', "'") and path[-1] == path[0]: sectionizer_node = ConstantIncludeNode(path[1:-1]) else: sectionizer_node = IncludeNode(bits[1]) return OutlinerNode(nodelist, sectionizer_node, Variable(results_var), Variable(sectionizers_var))