SAMPLE USAGE:
{% load handytags %}
{% outliner 'generic_section.html' companies sectionizers %}
{% for company in objs %}
- {{ company.description|safe }}
{% endfor %}
{% endoutliner %}
generic_section.html:
{% for section in sections %}
{% 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))