Let's say you have a dataset and you want to render a page with sections/subsections/subsubsections down to some arbitrary depth and with arbitrary keys. For example, you might want to show cars broken out by year/price_range or price_range/year or price_range/manufacturer/model. The outliner template tag allows you to support multiple breakdowns of your data in a DRY sort of way.
In order to use it, you supply four things:
- a template for surrounding each subsection (think turtles all the way down)
- a queryset (really anything iterable)
- sectionizer dictionary/objects (see sample code, you must supply key_method)
- inner html to render the lowest subsections
The template provides the following:
- It recursively uses each of your sectionizers' key methods to divvy up data sets.
- It surrounds each section with templates of your choosing.
- It renders the inner template for all the "leaf" elements of your tree.
- It supplies some handy context vars for things like depth and outline ids.
What this is not:
- this is not for arbitrary tree data--think of the tree as fixed depth
- this is not as simple as the regroup tag--if you have a concrete organization to your data, you should keep it simple and hand-code your templates
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | SAMPLE USAGE:
{% load handytags %}
{% outliner 'generic_section.html' companies sectionizers %}
<ul>
{% for company in objs %}
<li>{{ company.description|safe }}</li>
{% endfor %}
</ul>
{% endoutliner %}
generic_section.html:
{% for section in sections %}
<div class="depth{{ section.depth }}">
<h{{ section.depth }} class="{{ sectionizer.css_class }}" id="header{{section.outline_level}}">
{{ section.key }}
</h{{ section.depth }}>
<div id="outline{{section.outline_level}}">
{{ section.html }}
</div>
</div>
{% 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))
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 11 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 3 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
Please login first before commenting.