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))
|
Comments