Login

Decorate Template Tag (In-Line include and extend with local context)

Author:
rhomber
Posted:
January 8, 2010
Language:
Python
Version:
1.1
Tags:
templatetags extend include decorate
Score:
1 (after 1 ratings)

I had an issue with the django templating system, where by if I included several different files at the same level on one template.. and all the included templates extended the same base template. It would render the defined blocks for the first inclusion for all. i.e. everything in the parent that is being extended would be not updated for the subsequent inclusion.

So, if anyone else has this problem. I have a solution that I sorta wanted for other reasons anyway. It's called a decorator tag, you can include and extend a template at the same time - in-line, with local context scoping so it only affects that tag.

This is also a nice way to avoid creating .html files for simple extending purposes.

  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
Inside main template:

{% block step_1 %}
    <!-- Step 1: Item Details -->
    {% with wizard.step_1 as step %}
        {% decorate "item/wizard/ajax_step_decorator.html" %}
            {% localblock step_ready_js %}
                $(this).standardWizardStepFormBind(step, formId);
            {% endlocalblock %}
        {% enddecorate %}
    {% endwith %}
    <!-- / Step 1 -->
{% endblock %}

{% block step_2 %}
    <!-- Step 2: Category Select -->
    {% with wizard.step_2 as step %}
        {% decorate "item/wizard/ajax_step_decorator.html" %}
            {% localblock step_ready_js %}
                $(this).categorySelectLiveQueryActions();
            {% endlocalblock %}
        {% enddecorate %}
    {% endwith %}
    <!-- / Step 2 -->
{% endblock %}


Decorator: item/wizard/ajax_step_decorator.html

{% load conversation %}
{% load common_webdesign %}

<form id="{{ step.get_form_id }}" name="{{ step.get_form_name }}" action="{{ step.get_action }}" method="POST" class="standard-form">{% cid_hidden %}
    <div id="{{ step.get_div_id }}">
        <div id="{{ step.get_inner_div_id }}">
        {% if step.available %}
            {% localblock step_available_body %}
                {% include step.template_name %}
            {% endlocalblock %}
        {% else %}
            {% localblock step_unavailable_body %}
                {% include "item/wizard/disabled_step_block.html" %}
            {% endlocalblock %}
        {% endif %}
        </div>
    </div>
</form>

<script language="javascript">
    $(this).ready(function() {
        // Provides: step, formName, formId & disabledId
        {% include "item/wizard/step_javascript_vars.html" %}

        {% localblock step_ready_js %}{% endlocalblock %}
    });
</script>

Implementation (templatetags):

def decorate(parser, token):
    nodelist = parser.parse(('enddecorate',))
    parser.delete_first_token()

    try:
        tag_name, template_name = token.split_contents()
    except ValueError:
        raise TemplateSyntaxError, "%r tag requires one argument" % \
            token.contents.split()[0]

    decorateNode = DecorateNode(nodelist, parser.compile_filter(template_name))

    return decorateNode

class DecorateNode(Node):
    def __init__(self, nodelist, template_name):
        self.nodelist = nodelist
        self.template_name = template_name
        self.blocks = dict([(n.name, n) for n in nodelist.get_nodes_by_type(LocalBlockNode)])

    def render(self, context):
        template_name = self.template_name.resolve(context)
        t = get_template(template_name)
        context['localblock'] = self.blocks

        return t.render(context)

class LocalBlockNode(Node):
    def __init__(self, name, nodelist, parent=None):
        self.name, self.nodelist, self.parent = name, nodelist, parent

    def __repr__(self):
        return "<LocalBlock Node: %s. Contents: %r>" % (self.name, self.nodelist)

    def render(self, context):
        try:
            if self.name and 'localblock' in context:
                local_blocks = context['localblock']
                if local_blocks and self.name in local_blocks:
                    block = local_blocks[self.name]
                    return block.nodelist.render(context)
        except Except, e:
            logger.error("Failed to render LocalBlockNode")
            logger.error(e)
            raise

        # Failed to render contextual element, render this one:
        return self.nodelist.render(context)

def localblock(parser, token):
    """
    Define a block that can be overridden by child templates.
    """
    bits = token.contents.split()
    if len(bits) != 2:
        raise TemplateSyntaxError, "'%s' tag takes only one argument" % bits[0]
    block_name = bits[1]
    nodelist = parser.parse(('endlocalblock', 'endlocalblock %s' % block_name))
    parser.delete_first_token()
    return LocalBlockNode(block_name, nodelist)

register.tag(localblock)
register.tag(decorate)

More like this

  1. Silently-failing include tag by brutasse 5 years, 1 month ago
  2. "Partial Templates" - an alternative to "include" by vigrid 6 years, 6 months ago
  3. Template Tag Render Decorator by stephen_mcd 5 years, 5 months ago
  4. Private Context Decorator by acdha 5 years, 11 months ago
  5. {% renderonce %} template tag by corrr 5 years, 1 month ago

Comments

mogga (on March 3, 2010):

I think this is what I need... but i don't really understand what it does from your example... probably because it's hard to read with the javascript interspersed... something simple for simpletons please!

#

Please login first before commenting.