Reuse blocks of template code and content as macros.
This is a small extension of https://gist.github.com/skyl/1715202 (which was based on http://djangosnippets.org/snippets/363/) to support rendering macro output into context variables.
See comments for details.
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 200 201 202 203 204 205 206 207 208 209 210 211 212 | #
# templatetags/macros.py - Support for macros in Django templates
#
# Based on snippet by
# Michal Ludvig <[email protected]>
# http://www.logix.cz/michal
#
# Extended for args and kwargs into templatetags/kwacro.py by
# Skylar Saveland http://skyl.org
# https://gist.github.com/skyl/1715202
#
# Modified to support rendering into context by [email protected]
#
"""
Usage example:
0) Save this file as <yourapp>/templatetags/macros.py
1) In your template load the library:
{% load macros %}
2) Define a new macro called 'my_macro' that takes args and/or kwargs
All will be optional.
{% macro my_macro arg1 arg2 baz="Default baz" %}
{% firstof arg1 "default_arg1" %}
{% if arg2 %}{{ arg2 }}{% else %}default_arg2{% endif %}
{{ baz }}
{% endmacro %}
3) Use the macro with string parameters or context variables:
{% usemacro my_macro "foo" "bar" baz="KW" %}
<br>
{% usemacro my_macro num_pages "bar" %}
<br>
{% setmacro my_macro %} {{ my_macro }}
renders like
foo bar KW
77 bar Default baz
default_arg1 default_arg2 Default baz
4) Alternatively save your macros in a separate file, e.g. "mymacro.html"
and load it to the current template with:
{% loadmacros "mymacros.html" %}
Then use these loaded macros in as described above.
Bear in mind that defined and loaded macros 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="macro")
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(('endmacro', ))
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="loadmacros")
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, context_only):
self.macro = macro
self.fe_args = fe_args
self.fe_kwargs = fe_kwargs
self.context_only = context_only
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)
# Place output into context variable
context[self.macro.name] = self.macro.nodelist.render(context)
return '' if self.context_only else context[self.macro.name]
def parse_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.name = macro_name
macro.parser = parser
return macro, fe_args, fe_kwargs
@register.tag(name="usemacro")
def do_usemacro(parser, token):
return UseMacroNode(*parse_usemacro(parser, token), context_only=False)
@register.tag(name="setmacro")
def do_setmacro(parser, token):
return UseMacroNode(*parse_usemacro(parser, token), context_only=True)
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
Hi
Thanks for your work.
I created a python module/django app with this snippet:
https://github.com/twidi/django-templates-macros https://pypi.python.org/pypi/django-templates-macros
#
Please login first before commenting.