Login

outliner template tag

Author:
showell
Posted:
April 22, 2009
Language:
Python
Version:
1.0
Tags:
template tree outline recursive
Score:
0 (after 0 ratings)

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:

  1. a template for surrounding each subsection (think turtles all the way down)
  2. a queryset (really anything iterable)
  3. sectionizer dictionary/objects (see sample code, you must supply key_method)
  4. inner html to render the lowest subsections

The template provides the following:

  1. It recursively uses each of your sectionizers' key methods to divvy up data sets.
  2. It surrounds each section with templates of your choosing.
  3. It renders the inner template for all the "leaf" elements of your tree.
  4. It supplies some handy context vars for things like depth and outline ids.

What this is not:

  1. this is not for arbitrary tree data--think of the tree as fixed depth
  2. 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

  1. testdata tag for templates by showell 6 years, 4 months ago
  2. Complex Formsets by smagala 6 years, 7 months ago
  3. Mobilize your Django site by stevena0 6 years, 4 months ago
  4. Repeat blocks with new context / simple Jinja-like macro system by miracle2k 8 years, 1 month ago
  5. Twin column model admin index by richardbolt 7 years, 5 months ago

Comments

Please login first before commenting.