diff options
Diffstat (limited to 'webapp/django/template/defaulttags.py')
-rw-r--r-- | webapp/django/template/defaulttags.py | 1108 |
1 files changed, 1108 insertions, 0 deletions
diff --git a/webapp/django/template/defaulttags.py b/webapp/django/template/defaulttags.py new file mode 100644 index 0000000000..489faa243e --- /dev/null +++ b/webapp/django/template/defaulttags.py @@ -0,0 +1,1108 @@ +"""Default tags used by the template system, available to all templates.""" + +import sys +import re +from itertools import cycle as itertools_cycle +try: + reversed +except NameError: + from django.utils.itercompat import reversed # Python 2.3 fallback + +from django.template import Node, NodeList, Template, Context, Variable +from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END +from django.template import get_library, Library, InvalidTemplateLibrary +from django.conf import settings +from django.utils.encoding import smart_str, smart_unicode +from django.utils.itercompat import groupby +from django.utils.safestring import mark_safe + +register = Library() + +class AutoEscapeControlNode(Node): + """Implements the actions of the autoescape tag.""" + def __init__(self, setting, nodelist): + self.setting, self.nodelist = setting, nodelist + + def render(self, context): + old_setting = context.autoescape + context.autoescape = self.setting + output = self.nodelist.render(context) + context.autoescape = old_setting + if self.setting: + return mark_safe(output) + else: + return output + +class CommentNode(Node): + def render(self, context): + return '' + +class CycleNode(Node): + def __init__(self, cyclevars, variable_name=None): + self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars]) + self.variable_name = variable_name + + def render(self, context): + value = self.cycle_iter.next().resolve(context) + if self.variable_name: + context[self.variable_name] = value + return value + +class DebugNode(Node): + def render(self, context): + from pprint import pformat + output = [pformat(val) for val in context] + output.append('\n\n') + output.append(pformat(sys.modules)) + return ''.join(output) + +class FilterNode(Node): + def __init__(self, filter_expr, nodelist): + self.filter_expr, self.nodelist = filter_expr, nodelist + + def render(self, context): + output = self.nodelist.render(context) + # Apply filters. + context.update({'var': output}) + filtered = self.filter_expr.resolve(context) + context.pop() + return filtered + +class FirstOfNode(Node): + def __init__(self, vars): + self.vars = map(Variable, vars) + + def render(self, context): + for var in self.vars: + try: + value = var.resolve(context) + except VariableDoesNotExist: + continue + if value: + return smart_unicode(value) + return u'' + +class ForNode(Node): + def __init__(self, loopvars, sequence, is_reversed, nodelist_loop): + self.loopvars, self.sequence = loopvars, sequence + self.is_reversed = is_reversed + self.nodelist_loop = nodelist_loop + + def __repr__(self): + reversed_text = self.is_reversed and ' reversed' or '' + return "<For Node: for %s in %s, tail_len: %d%s>" % \ + (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop), + reversed_text) + + def __iter__(self): + for node in self.nodelist_loop: + yield node + + def get_nodes_by_type(self, nodetype): + nodes = [] + if isinstance(self, nodetype): + nodes.append(self) + nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype)) + return nodes + + def render(self, context): + nodelist = NodeList() + if 'forloop' in context: + parentloop = context['forloop'] + else: + parentloop = {} + context.push() + try: + values = self.sequence.resolve(context, True) + except VariableDoesNotExist: + values = [] + if values is None: + values = [] + if not hasattr(values, '__len__'): + values = list(values) + len_values = len(values) + if self.is_reversed: + values = reversed(values) + unpack = len(self.loopvars) > 1 + # Create a forloop value in the context. We'll update counters on each + # iteration just below. + loop_dict = context['forloop'] = {'parentloop': parentloop} + for i, item in enumerate(values): + # Shortcuts for current loop iteration number. + loop_dict['counter0'] = i + loop_dict['counter'] = i+1 + # Reverse counter iteration numbers. + loop_dict['revcounter'] = len_values - i + loop_dict['revcounter0'] = len_values - i - 1 + # Boolean values designating first and last times through loop. + loop_dict['first'] = (i == 0) + loop_dict['last'] = (i == len_values - 1) + + if unpack: + # If there are multiple loop variables, unpack the item into + # them. + context.update(dict(zip(self.loopvars, item))) + else: + context[self.loopvars[0]] = item + for node in self.nodelist_loop: + nodelist.append(node.render(context)) + if unpack: + # The loop variables were pushed on to the context so pop them + # off again. This is necessary because the tag lets the length + # of loopvars differ to the length of each set of items and we + # don't want to leave any vars from the previous loop on the + # context. + context.pop() + context.pop() + return nodelist.render(context) + +class IfChangedNode(Node): + def __init__(self, nodelist_true, nodelist_false, *varlist): + self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false + self._last_seen = None + self._varlist = map(Variable, varlist) + self._id = str(id(self)) + + def render(self, context): + if 'forloop' in context and self._id not in context['forloop']: + self._last_seen = None + context['forloop'][self._id] = 1 + try: + if self._varlist: + # Consider multiple parameters. This automatically behaves + # like an OR evaluation of the multiple variables. + compare_to = [var.resolve(context) for var in self._varlist] + else: + compare_to = self.nodelist_true.render(context) + except VariableDoesNotExist: + compare_to = None + + if compare_to != self._last_seen: + firstloop = (self._last_seen == None) + self._last_seen = compare_to + context.push() + context['ifchanged'] = {'firstloop': firstloop} + content = self.nodelist_true.render(context) + context.pop() + return content + elif self.nodelist_false: + return self.nodelist_false.render(context) + return '' + +class IfEqualNode(Node): + def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): + self.var1, self.var2 = Variable(var1), Variable(var2) + self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false + self.negate = negate + + def __repr__(self): + return "<IfEqualNode>" + + def render(self, context): + try: + val1 = self.var1.resolve(context) + except VariableDoesNotExist: + val1 = None + try: + val2 = self.var2.resolve(context) + except VariableDoesNotExist: + val2 = None + if (self.negate and val1 != val2) or (not self.negate and val1 == val2): + return self.nodelist_true.render(context) + return self.nodelist_false.render(context) + +class IfNode(Node): + def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type): + self.bool_exprs = bool_exprs + self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false + self.link_type = link_type + + def __repr__(self): + return "<If node>" + + def __iter__(self): + for node in self.nodelist_true: + yield node + for node in self.nodelist_false: + yield node + + def get_nodes_by_type(self, nodetype): + nodes = [] + if isinstance(self, nodetype): + nodes.append(self) + nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype)) + nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype)) + return nodes + + def render(self, context): + if self.link_type == IfNode.LinkTypes.or_: + for ifnot, bool_expr in self.bool_exprs: + try: + value = bool_expr.resolve(context, True) + except VariableDoesNotExist: + value = None + if (value and not ifnot) or (ifnot and not value): + return self.nodelist_true.render(context) + return self.nodelist_false.render(context) + else: + for ifnot, bool_expr in self.bool_exprs: + try: + value = bool_expr.resolve(context, True) + except VariableDoesNotExist: + value = None + if not ((value and not ifnot) or (ifnot and not value)): + return self.nodelist_false.render(context) + return self.nodelist_true.render(context) + + class LinkTypes: + and_ = 0, + or_ = 1 + +class RegroupNode(Node): + def __init__(self, target, expression, var_name): + self.target, self.expression = target, expression + self.var_name = var_name + + def render(self, context): + obj_list = self.target.resolve(context, True) + if obj_list == None: + # target variable wasn't found in context; fail silently. + context[self.var_name] = [] + return '' + # List of dictionaries in the format: + # {'grouper': 'key', 'list': [list of contents]}. + context[self.var_name] = [ + {'grouper': key, 'list': list(val)} + for key, val in + groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True)) + ] + return '' + +def include_is_allowed(filepath): + for root in settings.ALLOWED_INCLUDE_ROOTS: + if filepath.startswith(root): + return True + return False + +class SsiNode(Node): + def __init__(self, filepath, parsed): + self.filepath, self.parsed = filepath, parsed + + def render(self, context): + if not include_is_allowed(self.filepath): + if settings.DEBUG: + return "[Didn't have permission to include file]" + else: + return '' # Fail silently for invalid includes. + try: + fp = open(self.filepath, 'r') + output = fp.read() + fp.close() + except IOError: + output = '' + if self.parsed: + try: + t = Template(output, name=self.filepath) + return t.render(context) + except TemplateSyntaxError, e: + if settings.DEBUG: + return "[Included template had syntax error: %s]" % e + else: + return '' # Fail silently for invalid included templates. + return output + +class LoadNode(Node): + def render(self, context): + return '' + +class NowNode(Node): + def __init__(self, format_string): + self.format_string = format_string + + def render(self, context): + from datetime import datetime + from django.utils.dateformat import DateFormat + df = DateFormat(datetime.now()) + return df.format(self.format_string) + +class SpacelessNode(Node): + def __init__(self, nodelist): + self.nodelist = nodelist + + def render(self, context): + from django.utils.html import strip_spaces_between_tags + return strip_spaces_between_tags(self.nodelist.render(context).strip()) + +class TemplateTagNode(Node): + mapping = {'openblock': BLOCK_TAG_START, + 'closeblock': BLOCK_TAG_END, + 'openvariable': VARIABLE_TAG_START, + 'closevariable': VARIABLE_TAG_END, + 'openbrace': SINGLE_BRACE_START, + 'closebrace': SINGLE_BRACE_END, + 'opencomment': COMMENT_TAG_START, + 'closecomment': COMMENT_TAG_END, + } + + def __init__(self, tagtype): + self.tagtype = tagtype + + def render(self, context): + return self.mapping.get(self.tagtype, '') + +class URLNode(Node): + def __init__(self, view_name, args, kwargs): + self.view_name = view_name + self.args = args + self.kwargs = kwargs + + def render(self, context): + from django.core.urlresolvers import reverse, NoReverseMatch + args = [arg.resolve(context) for arg in self.args] + kwargs = dict([(smart_str(k,'ascii'), v.resolve(context)) + for k, v in self.kwargs.items()]) + try: + return reverse(self.view_name, args=args, kwargs=kwargs) + except NoReverseMatch: + project_name = settings.SETTINGS_MODULE.split('.')[0] + return reverse(project_name + '.' + self.view_name, + args=args, kwargs=kwargs) + +class WidthRatioNode(Node): + def __init__(self, val_expr, max_expr, max_width): + self.val_expr = val_expr + self.max_expr = max_expr + self.max_width = max_width + + def render(self, context): + try: + value = self.val_expr.resolve(context) + maxvalue = self.max_expr.resolve(context) + except VariableDoesNotExist: + return '' + try: + value = float(value) + maxvalue = float(maxvalue) + ratio = (value / maxvalue) * int(self.max_width) + except (ValueError, ZeroDivisionError): + return '' + return str(int(round(ratio))) + +class WithNode(Node): + def __init__(self, var, name, nodelist): + self.var = var + self.name = name + self.nodelist = nodelist + + def __repr__(self): + return "<WithNode>" + + def render(self, context): + val = self.var.resolve(context) + context.push() + context[self.name] = val + output = self.nodelist.render(context) + context.pop() + return output + +#@register.tag +def autoescape(parser, token): + """ + Force autoescape behaviour for this block. + """ + args = token.contents.split() + if len(args) != 2: + raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.") + arg = args[1] + if arg not in (u'on', u'off'): + raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'") + nodelist = parser.parse(('endautoescape',)) + parser.delete_first_token() + return AutoEscapeControlNode((arg == 'on'), nodelist) +autoescape = register.tag(autoescape) + +#@register.tag +def comment(parser, token): + """ + Ignores everything between ``{% comment %}`` and ``{% endcomment %}``. + """ + parser.skip_past('endcomment') + return CommentNode() +comment = register.tag(comment) + +#@register.tag +def cycle(parser, token): + """ + Cycles among the given strings each time this tag is encountered. + + Within a loop, cycles among the given strings each time through + the loop:: + + {% for o in some_list %} + <tr class="{% cycle 'row1' 'row2' %}"> + ... + </tr> + {% endfor %} + + Outside of a loop, give the values a unique name the first time you call + it, then use that name each sucessive time through:: + + <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr> + <tr class="{% cycle rowcolors %}">...</tr> + <tr class="{% cycle rowcolors %}">...</tr> + + You can use any number of values, separated by spaces. Commas can also + be used to separate values; if a comma is used, the cycle values are + interpreted as literal strings. + """ + + # Note: This returns the exact same node on each {% cycle name %} call; + # that is, the node object returned from {% cycle a b c as name %} and the + # one returned from {% cycle name %} are the exact same object. This + # shouldn't cause problems (heh), but if it does, now you know. + # + # Ugly hack warning: This stuffs the named template dict into parser so + # that names are only unique within each template (as opposed to using + # a global variable, which would make cycle names have to be unique across + # *all* templates. + + args = token.split_contents() + + if len(args) < 2: + raise TemplateSyntaxError("'cycle' tag requires at least two arguments") + + if ',' in args[1]: + # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %} + # case. + args[1:2] = ['"%s"' % arg for arg in args[1].split(",")] + + if len(args) == 2: + # {% cycle foo %} case. + name = args[1] + if not hasattr(parser, '_namedCycleNodes'): + raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name) + if not name in parser._namedCycleNodes: + raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) + return parser._namedCycleNodes[name] + + if len(args) > 4 and args[-2] == 'as': + name = args[-1] + node = CycleNode(args[1:-2], name) + if not hasattr(parser, '_namedCycleNodes'): + parser._namedCycleNodes = {} + parser._namedCycleNodes[name] = node + else: + node = CycleNode(args[1:]) + return node +cycle = register.tag(cycle) + +def debug(parser, token): + """ + Outputs a whole load of debugging information, including the current + context and imported modules. + + Sample usage:: + + <pre> + {% debug %} + </pre> + """ + return DebugNode() +debug = register.tag(debug) + +#@register.tag(name="filter") +def do_filter(parser, token): + """ + Filters the contents of the block through variable filters. + + Filters can also be piped through each other, and they can have + arguments -- just like in variable syntax. + + Sample usage:: + + {% filter force_escape|lower %} + This text will be HTML-escaped, and will appear in lowercase. + {% endfilter %} + """ + _, rest = token.contents.split(None, 1) + filter_expr = parser.compile_filter("var|%s" % (rest)) + for func, unused in filter_expr.filters: + if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'): + raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__) + nodelist = parser.parse(('endfilter',)) + parser.delete_first_token() + return FilterNode(filter_expr, nodelist) +do_filter = register.tag("filter", do_filter) + +#@register.tag +def firstof(parser, token): + """ + Outputs the first variable passed that is not False. + + Outputs nothing if all the passed variables are False. + + Sample usage:: + + {% firstof var1 var2 var3 %} + + This is equivalent to:: + + {% if var1 %} + {{ var1 }} + {% else %}{% if var2 %} + {{ var2 }} + {% else %}{% if var3 %} + {{ var3 }} + {% endif %}{% endif %}{% endif %} + + but obviously much cleaner! + + You can also use a literal string as a fallback value in case all + passed variables are False:: + + {% firstof var1 var2 var3 "fallback value" %} + + """ + bits = token.split_contents()[1:] + if len(bits) < 1: + raise TemplateSyntaxError("'firstof' statement requires at least one" + " argument") + return FirstOfNode(bits) +firstof = register.tag(firstof) + +#@register.tag(name="for") +def do_for(parser, token): + """ + Loops over each item in an array. + + For example, to display a list of athletes given ``athlete_list``:: + + <ul> + {% for athlete in athlete_list %} + <li>{{ athlete.name }}</li> + {% endfor %} + </ul> + + You can loop over a list in reverse by using + ``{% for obj in list reversed %}``. + + You can also unpack multiple values from a two-dimensional array:: + + {% for key,value in dict.items %} + {{ key }}: {{ value }} + {% endfor %} + + The for loop sets a number of variables available within the loop: + + ========================== ================================================ + Variable Description + ========================== ================================================ + ``forloop.counter`` The current iteration of the loop (1-indexed) + ``forloop.counter0`` The current iteration of the loop (0-indexed) + ``forloop.revcounter`` The number of iterations from the end of the + loop (1-indexed) + ``forloop.revcounter0`` The number of iterations from the end of the + loop (0-indexed) + ``forloop.first`` True if this is the first time through the loop + ``forloop.last`` True if this is the last time through the loop + ``forloop.parentloop`` For nested loops, this is the loop "above" the + current one + ========================== ================================================ + + """ + bits = token.contents.split() + if len(bits) < 4: + raise TemplateSyntaxError("'for' statements should have at least four" + " words: %s" % token.contents) + + is_reversed = bits[-1] == 'reversed' + in_index = is_reversed and -3 or -2 + if bits[in_index] != 'in': + raise TemplateSyntaxError("'for' statements should use the format" + " 'for x in y': %s" % token.contents) + + loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',') + for var in loopvars: + if not var or ' ' in var: + raise TemplateSyntaxError("'for' tag received an invalid argument:" + " %s" % token.contents) + + sequence = parser.compile_filter(bits[in_index+1]) + nodelist_loop = parser.parse(('endfor',)) + parser.delete_first_token() + return ForNode(loopvars, sequence, is_reversed, nodelist_loop) +do_for = register.tag("for", do_for) + +def do_ifequal(parser, token, negate): + bits = list(token.split_contents()) + if len(bits) != 3: + raise TemplateSyntaxError, "%r takes two arguments" % bits[0] + end_tag = 'end' + bits[0] + nodelist_true = parser.parse(('else', end_tag)) + token = parser.next_token() + if token.contents == 'else': + nodelist_false = parser.parse((end_tag,)) + parser.delete_first_token() + else: + nodelist_false = NodeList() + return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate) + +#@register.tag +def ifequal(parser, token): + """ + Outputs the contents of the block if the two arguments equal each other. + + Examples:: + + {% ifequal user.id comment.user_id %} + ... + {% endifequal %} + + {% ifnotequal user.id comment.user_id %} + ... + {% else %} + ... + {% endifnotequal %} + """ + return do_ifequal(parser, token, False) +ifequal = register.tag(ifequal) + +#@register.tag +def ifnotequal(parser, token): + """ + Outputs the contents of the block if the two arguments are not equal. + See ifequal. + """ + return do_ifequal(parser, token, True) +ifnotequal = register.tag(ifnotequal) + +#@register.tag(name="if") +def do_if(parser, token): + """ + The ``{% if %}`` tag evaluates a variable, and if that variable is "true" + (i.e., exists, is not empty, and is not a false boolean value), the + contents of the block are output: + + :: + + {% if athlete_list %} + Number of athletes: {{ athlete_list|count }} + {% else %} + No athletes. + {% endif %} + + In the above, if ``athlete_list`` is not empty, the number of athletes will + be displayed by the ``{{ athlete_list|count }}`` variable. + + As you can see, the ``if`` tag can take an option ``{% else %}`` clause + that will be displayed if the test fails. + + ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of + variables or to negate a given variable:: + + {% if not athlete_list %} + There are no athletes. + {% endif %} + + {% if athlete_list or coach_list %} + There are some athletes or some coaches. + {% endif %} + + {% if athlete_list and coach_list %} + Both atheletes and coaches are available. + {% endif %} + + {% if not athlete_list or coach_list %} + There are no athletes, or there are some coaches. + {% endif %} + + {% if athlete_list and not coach_list %} + There are some athletes and absolutely no coaches. + {% endif %} + + ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag, + because the order of logic would be ambigous. For example, this is + invalid:: + + {% if athlete_list and coach_list or cheerleader_list %} + + If you need to combine ``and`` and ``or`` to do advanced logic, just use + nested if tags. For example:: + + {% if athlete_list %} + {% if coach_list or cheerleader_list %} + We have athletes, and either coaches or cheerleaders! + {% endif %} + {% endif %} + """ + bits = token.contents.split() + del bits[0] + if not bits: + raise TemplateSyntaxError("'if' statement requires at least one argument") + # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d'] + bitstr = ' '.join(bits) + boolpairs = bitstr.split(' and ') + boolvars = [] + if len(boolpairs) == 1: + link_type = IfNode.LinkTypes.or_ + boolpairs = bitstr.split(' or ') + else: + link_type = IfNode.LinkTypes.and_ + if ' or ' in bitstr: + raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'" + for boolpair in boolpairs: + if ' ' in boolpair: + try: + not_, boolvar = boolpair.split() + except ValueError: + raise TemplateSyntaxError, "'if' statement improperly formatted" + if not_ != 'not': + raise TemplateSyntaxError, "Expected 'not' in if statement" + boolvars.append((True, parser.compile_filter(boolvar))) + else: + boolvars.append((False, parser.compile_filter(boolpair))) + nodelist_true = parser.parse(('else', 'endif')) + token = parser.next_token() + if token.contents == 'else': + nodelist_false = parser.parse(('endif',)) + parser.delete_first_token() + else: + nodelist_false = NodeList() + return IfNode(boolvars, nodelist_true, nodelist_false, link_type) +do_if = register.tag("if", do_if) + +#@register.tag +def ifchanged(parser, token): + """ + Checks if a value has changed from the last iteration of a loop. + + The 'ifchanged' block tag is used within a loop. It has two possible uses. + + 1. Checks its own rendered contents against its previous state and only + displays the content if it has changed. For example, this displays a + list of days, only displaying the month if it changes:: + + <h1>Archive for {{ year }}</h1> + + {% for date in days %} + {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %} + <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a> + {% endfor %} + + 2. If given a variable, check whether that variable has changed. + For example, the following shows the date every time it changes, but + only shows the hour if both the hour and the date have changed:: + + {% for date in days %} + {% ifchanged date.date %} {{ date.date }} {% endifchanged %} + {% ifchanged date.hour date.date %} + {{ date.hour }} + {% endifchanged %} + {% endfor %} + """ + bits = token.contents.split() + nodelist_true = parser.parse(('else', 'endifchanged')) + token = parser.next_token() + if token.contents == 'else': + nodelist_false = parser.parse(('endifchanged',)) + parser.delete_first_token() + else: + nodelist_false = NodeList() + return IfChangedNode(nodelist_true, nodelist_false, *bits[1:]) +ifchanged = register.tag(ifchanged) + +#@register.tag +def ssi(parser, token): + """ + Outputs the contents of a given file into the page. + + Like a simple "include" tag, the ``ssi`` tag includes the contents + of another file -- which must be specified using an absolute path -- + in the current page:: + + {% ssi /home/html/ljworld.com/includes/right_generic.html %} + + If the optional "parsed" parameter is given, the contents of the included + file are evaluated as template code, with the current context:: + + {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %} + """ + bits = token.contents.split() + parsed = False + if len(bits) not in (2, 3): + raise TemplateSyntaxError("'ssi' tag takes one argument: the path to" + " the file to be included") + if len(bits) == 3: + if bits[2] == 'parsed': + parsed = True + else: + raise TemplateSyntaxError("Second (optional) argument to %s tag" + " must be 'parsed'" % bits[0]) + return SsiNode(bits[1], parsed) +ssi = register.tag(ssi) + +#@register.tag +def load(parser, token): + """ + Loads a custom template tag set. + + For example, to load the template tags in + ``django/templatetags/news/photos.py``:: + + {% load news.photos %} + """ + bits = token.contents.split() + for taglib in bits[1:]: + # add the library to the parser + try: + lib = get_library("django.templatetags.%s" % taglib) + parser.add_library(lib) + except InvalidTemplateLibrary, e: + raise TemplateSyntaxError("'%s' is not a valid tag library: %s" % + (taglib, e)) + return LoadNode() +load = register.tag(load) + +#@register.tag +def now(parser, token): + """ + Displays the date, formatted according to the given string. + + Uses the same format as PHP's ``date()`` function; see http://php.net/date + for all the possible values. + + Sample usage:: + + It is {% now "jS F Y H:i" %} + """ + bits = token.contents.split('"') + if len(bits) != 3: + raise TemplateSyntaxError, "'now' statement takes one argument" + format_string = bits[1] + return NowNode(format_string) +now = register.tag(now) + +#@register.tag +def regroup(parser, token): + """ + Regroups a list of alike objects by a common attribute. + + This complex tag is best illustrated by use of an example: say that + ``people`` is a list of ``Person`` objects that have ``first_name``, + ``last_name``, and ``gender`` attributes, and you'd like to display a list + that looks like: + + * Male: + * George Bush + * Bill Clinton + * Female: + * Margaret Thatcher + * Colendeeza Rice + * Unknown: + * Pat Smith + + The following snippet of template code would accomplish this dubious task:: + + {% regroup people by gender as grouped %} + <ul> + {% for group in grouped %} + <li>{{ group.grouper }} + <ul> + {% for item in group.list %} + <li>{{ item }}</li> + {% endfor %} + </ul> + {% endfor %} + </ul> + + As you can see, ``{% regroup %}`` populates a variable with a list of + objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the + item that was grouped by; ``list`` contains the list of objects that share + that ``grouper``. In this case, ``grouper`` would be ``Male``, ``Female`` + and ``Unknown``, and ``list`` is the list of people with those genders. + + Note that `{% regroup %}`` does not work when the list to be grouped is not + sorted by the key you are grouping by! This means that if your list of + people was not sorted by gender, you'd need to make sure it is sorted + before using it, i.e.:: + + {% regroup people|dictsort:"gender" by gender as grouped %} + + """ + firstbits = token.contents.split(None, 3) + if len(firstbits) != 4: + raise TemplateSyntaxError, "'regroup' tag takes five arguments" + target = parser.compile_filter(firstbits[1]) + if firstbits[2] != 'by': + raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") + lastbits_reversed = firstbits[3][::-1].split(None, 2) + if lastbits_reversed[1][::-1] != 'as': + raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" + " be 'as'") + + expression = parser.compile_filter(lastbits_reversed[2][::-1]) + + var_name = lastbits_reversed[0][::-1] + return RegroupNode(target, expression, var_name) +regroup = register.tag(regroup) + +def spaceless(parser, token): + """ + Removes whitespace between HTML tags, including tab and newline characters. + + Example usage:: + + {% spaceless %} + <p> + <a href="foo/">Foo</a> + </p> + {% endspaceless %} + + This example would return this HTML:: + + <p><a href="foo/">Foo</a></p> + + Only space between *tags* is normalized -- not space between tags and text. + In this example, the space around ``Hello`` won't be stripped:: + + {% spaceless %} + <strong> + Hello + </strong> + {% endspaceless %} + """ + nodelist = parser.parse(('endspaceless',)) + parser.delete_first_token() + return SpacelessNode(nodelist) +spaceless = register.tag(spaceless) + +#@register.tag +def templatetag(parser, token): + """ + Outputs one of the bits used to compose template tags. + + Since the template system has no concept of "escaping", to display one of + the bits used in template tags, you must use the ``{% templatetag %}`` tag. + + The argument tells which template bit to output: + + ================== ======= + Argument Outputs + ================== ======= + ``openblock`` ``{%`` + ``closeblock`` ``%}`` + ``openvariable`` ``{{`` + ``closevariable`` ``}}`` + ``openbrace`` ``{`` + ``closebrace`` ``}`` + ``opencomment`` ``{#`` + ``closecomment`` ``#}`` + ================== ======= + """ + bits = token.contents.split() + if len(bits) != 2: + raise TemplateSyntaxError, "'templatetag' statement takes one argument" + tag = bits[1] + if tag not in TemplateTagNode.mapping: + raise TemplateSyntaxError("Invalid templatetag argument: '%s'." + " Must be one of: %s" % + (tag, TemplateTagNode.mapping.keys())) + return TemplateTagNode(tag) +templatetag = register.tag(templatetag) + +def url(parser, token): + """ + Returns an absolute URL matching given view with its parameters. + + This is a way to define links that aren't tied to a particular URL + configuration:: + + {% url path.to.some_view arg1,arg2,name1=value1 %} + + The first argument is a path to a view. It can be an absolute python path + or just ``app_name.view_name`` without the project name if the view is + located inside the project. Other arguments are comma-separated values + that will be filled in place of positional and keyword arguments in the + URL. All arguments for the URL should be present. + + For example if you have a view ``app_name.client`` taking client's id and + the corresponding line in a URLconf looks like this:: + + ('^client/(\d+)/$', 'app_name.client') + + and this app's URLconf is included into the project's URLconf under some + path:: + + ('^clients/', include('project_name.app_name.urls')) + + then in a template you can create a link for a certain client like this:: + + {% url app_name.client client.id %} + + The URL will look like ``/clients/client/123/``. + """ + bits = token.contents.split(' ', 2) + if len(bits) < 2: + raise TemplateSyntaxError("'%s' takes at least one argument" + " (path to a view)" % bits[0]) + args = [] + kwargs = {} + if len(bits) > 2: + for arg in bits[2].split(','): + if '=' in arg: + k, v = arg.split('=', 1) + k = k.strip() + kwargs[k] = parser.compile_filter(v) + else: + args.append(parser.compile_filter(arg)) + return URLNode(bits[1], args, kwargs) +url = register.tag(url) + +#@register.tag +def widthratio(parser, token): + """ + For creating bar charts and such, this tag calculates the ratio of a given + value to a maximum value, and then applies that ratio to a constant. + + For example:: + + <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' /> + + Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in + the above example will be 88 pixels wide (because 175/200 = .875; + .875 * 100 = 87.5 which is rounded up to 88). + """ + bits = token.contents.split() + if len(bits) != 4: + raise TemplateSyntaxError("widthratio takes three arguments") + tag, this_value_expr, max_value_expr, max_width = bits + try: + max_width = int(max_width) + except ValueError: + raise TemplateSyntaxError("widthratio final argument must be an integer") + return WidthRatioNode(parser.compile_filter(this_value_expr), + parser.compile_filter(max_value_expr), max_width) +widthratio = register.tag(widthratio) + +#@register.tag +def do_with(parser, token): + """ + Adds a value to the context (inside of this block) for caching and easy + access. + + For example:: + + {% with person.some_sql_method as total %} + {{ total }} object{{ total|pluralize }} + {% endwith %} + """ + bits = list(token.split_contents()) + if len(bits) != 4 or bits[2] != "as": + raise TemplateSyntaxError("%r expected format is 'value as name'" % + bits[0]) + var = parser.compile_filter(bits[1]) + name = bits[3] + nodelist = parser.parse(('endwith',)) + parser.delete_first_token() + return WithNode(var, name, nodelist) +do_with = register.tag('with', do_with) |