public
Last active

Django Template macros with args and kwargs

  • Download Gist
kwacros.py
Python
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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
#
# templatetags/kwacros.py - Support for macros in Django templates
#
# Based on snippet by
# Author: Michal Ludvig <michal@logix.cz>
# http://www.logix.cz/michal
#
# modified for args and kwargs by Skylar Saveland http://skyl.org
#
 
"""
 
Usage example:
 
0) Save this file as <yourapp>/templatetags/kwacros.py
 
1) In your template load the library:
{% load kwacros %}
 
2) Define a new macro called 'my_macro' that takes two args and a kwarg.
All will be optional.
 
{% kwacro test2args1kwarg arg1 arg2 baz="Default baz" %}
{% firstof arg1 "default arg1" %}
{% if arg2 %}{{ arg2 }}{% else %}default arg2{% endif %}
{{ baz }}
{% endkwacro %}
 
3) Use the macro with a string parameters or context variables::
 
{% usekwacro test2args1kwarg "foo" "bar" baz="KW" %}
<br>
{% usekwacro test2args1kwarg num_pages "bar" %}
<br>
{% usekwacro test2args1kwarg %}
<br>
{% usekwacro test2args1kwarg "new" "foobar"|join:"," baz="diff kwarg" %}
 
renders like
 
foo bar KW
77 bar Default baz
default arg1 default arg2 Default baz
new f,o,o,b,a,r diff kwarg
 
4) Alternatively save your macros in a separate
file, e.g. "mymacros.html" and load it to the
current template with:
{% loadkwacros "mymacros.html" %}
Then use these loaded macros in {% usekwacro %}
as described above.
 
Bear in mind that defined and loaded kwacros are local
to each template file and are not inherited
through {% extends ... %} tags.
"""
 
from django import template
from django.template import FilterExpression
from django.template.loader import get_template
 
register = template.Library()
 
 
def _setup_macros_dict(parser):
## Metadata of each macro are stored in a new attribute
## of 'parser' class. That way we can access it later
## in the template when processing 'usemacro' tags.
try:
## Only try to access it to eventually trigger an exception
parser._macros
except AttributeError:
parser._macros = {}
 
 
class DefineMacroNode(template.Node):
def __init__(self, name, nodelist, args):
 
self.name = name
self.nodelist = nodelist
self.args = []
self.kwargs = {}
for a in args:
if "=" not in a:
self.args.append(a)
else:
name, value = a.split("=")
self.kwargs[name] = value
 
def render(self, context):
## empty string - {% macro %} tag does no output
return ''
 
 
@register.tag(name="kwacro")
def do_macro(parser, token):
try:
args = token.split_contents()
tag_name, macro_name, args = args[0], args[1], args[2:]
except IndexError:
m = ("'%s' tag requires at least one argument (macro name)"
% token.contents.split()[0])
raise template.TemplateSyntaxError, m
# TODO: could do some validations here,
# for now, "blow your head clean off"
nodelist = parser.parse(('endkwacro', ))
parser.delete_first_token()
 
## Metadata of each macro are stored in a new attribute
## of 'parser' class. That way we can access it later
## in the template when processing 'usemacro' tags.
_setup_macros_dict(parser)
parser._macros[macro_name] = DefineMacroNode(macro_name, nodelist, args)
return parser._macros[macro_name]
 
 
class LoadMacrosNode(template.Node):
def render(self, context):
## empty string - {% loadmacros %} tag does no output
return ''
 
 
@register.tag(name="loadkwacros")
def do_loadmacros(parser, token):
try:
tag_name, filename = token.split_contents()
except IndexError:
m = ("'%s' tag requires at least one argument (macro name)"
% token.contents.split()[0])
raise template.TemplateSyntaxError, m
if filename[0] in ('"', "'") and filename[-1] == filename[0]:
filename = filename[1:-1]
t = get_template(filename)
macros = t.nodelist.get_nodes_by_type(DefineMacroNode)
## Metadata of each macro are stored in a new attribute
## of 'parser' class. That way we can access it later
## in the template when processing 'usemacro' tags.
_setup_macros_dict(parser)
for macro in macros:
parser._macros[macro.name] = macro
return LoadMacrosNode()
 
 
class UseMacroNode(template.Node):
 
def __init__(self, macro, fe_args, fe_kwargs):
self.macro = macro
self.fe_args = fe_args
self.fe_kwargs = fe_kwargs
 
def render(self, context):
 
for i, arg in enumerate(self.macro.args):
try:
fe = self.fe_args[i]
context[arg] = fe.resolve(context)
except IndexError:
context[arg] = ""
 
for name, default in self.macro.kwargs.iteritems():
if name in self.fe_kwargs:
context[name] = self.fe_kwargs[name].resolve(context)
else:
context[name] = FilterExpression(default,
self.macro.parser
).resolve(context)
 
return self.macro.nodelist.render(context)
 
 
@register.tag(name="usekwacro")
def do_usemacro(parser, token):
try:
args = token.split_contents()
tag_name, macro_name, values = args[0], args[1], args[2:]
except IndexError:
m = ("'%s' tag requires at least one argument (macro name)"
% token.contents.split()[0])
raise template.TemplateSyntaxError, m
try:
macro = parser._macros[macro_name]
except (AttributeError, KeyError):
m = "Macro '%s' is not defined" % macro_name
raise template.TemplateSyntaxError, m
 
fe_kwargs = {}
fe_args = []
 
for val in values:
if "=" in val:
# kwarg
name, value = val.split("=")
fe_kwargs[name] = FilterExpression(value, parser)
else: # arg
# no validation, go for it ...
fe_args.append(FilterExpression(val, parser))
 
macro.parser = parser
return UseMacroNode(macro, fe_args, fe_kwargs)

This is not pep8, #fail

is pep8 now, removed unused imports (that are no longer any good in Django 1.4 anyways)

Thank you for sharing, nice piece of code! :)

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.