Skip to content

Instantly share code, notes, and snippets.

@ollieglass
Created April 10, 2013 16:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ollieglass/5356332 to your computer and use it in GitHub Desktop.
Save ollieglass/5356332 to your computer and use it in GitHub Desktop.
Macros for Django - this snippet, with unused imports removed http://djangosnippets.org/snippets/363/
#
# templatetags/macros.py - Support for macros in Django templates
#
# Author: Michal Ludvig <michal@logix.cz>
# http://www.logix.cz/michal
#
"""
Tag library that provides support for "macros" in
Django templates.
Usage example:
0) Save this file as
<yourapp>/taglibrary/macros.py
1) In your template load the library:
{% load macros %}
2) Define a new macro called 'my_macro' with
parameter 'arg1':
{% macro my_macro arg1 %}
Parameter: {{ arg1 }} <br/>
{% endmacro %}
3) Use the macro with a String parameter:
{% usemacro my_macro "String parameter" %}
or with a variable parameter (provided the
context defines 'somearg' variable, e.g. with
value "Variable parameter"):
{% usemacro my_macro somearg %}
The output of the above code would be:
Parameter: String parameter <br/>
Parameter: Variable parameter <br/>
4) Alternatively save your macros in a separate
file, e.g. "mymacros.html" and load it to the
current template with:
{% loadmacros "mymacros.html" %}
Then use these loaded macros in {% usemacro %}
as described above.
Macros can take zero or more arguments and both
context variables and macro arguments are resolved
in macro body when used in {% usemacro ... %} tag.
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 resolve_variable, FilterExpression
from django.template.loader import get_template, get_template_from_string, find_template_source
from django.conf import settings
import re
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 = args
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:
raise template.TemplateSyntaxError, "'%s' tag requires at least one argument (macro name)" % token.contents.split()[0]
# TODO: check that 'args' are all simple strings ([a-zA-Z0-9_]+)
r_valid_arg_name = re.compile(r'^[a-zA-Z0-9_]+$')
for arg in args:
if not r_valid_arg_name.match(arg):
raise template.TemplateSyntaxError, "Argument '%s' to macro '%s' contains illegal characters. Only alphanumeric characters and '_' are allowed." % (arg, macro_name)
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:
raise template.TemplateSyntaxError, "'%s' tag requires at least one argument (macro name)" % token.contents.split()[0]
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, filter_expressions):
self.nodelist = macro.nodelist
self.args = macro.args
self.filter_expressions = filter_expressions
def render(self, context):
for (arg, fe) in [(self.args[i], self.filter_expressions[i]) for i in range(len(self.args))]:
context[arg] = fe.resolve(context)
return self.nodelist.render(context)
@register.tag(name="usemacro")
def do_usemacro(parser, token):
try:
args = token.split_contents()
tag_name, macro_name, values = args[0], args[1], args[2:]
except IndexError:
raise template.TemplateSyntaxError, "'%s' tag requires at least one argument (macro name)" % token.contents.split()[0]
try:
macro = parser._macros[macro_name]
except (AttributeError, KeyError):
raise template.TemplateSyntaxError, "Macro '%s' is not defined" % macro_name
if (len(values) != len(macro.args)):
raise template.TemplateSyntaxError, "Macro '%s' was declared with %d parameters and used with %d parameter" % (
macro_name,
len(macro.args),
len(values))
filter_expressions = []
for val in values:
if (val[0] == "'" or val[0] == '"') and (val[0] != val[-1]):
raise template.TemplateSyntaxError, "Non-terminated string argument: %s" % val[1:]
filter_expressions.append(FilterExpression(val, parser))
return UseMacroNode(macro, filter_expressions)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment