Code

Ticket #5908: cycle.py

File cycle.py, 3.5 KB (added by Simon Litchfield <simon@…>, 6 years ago)

New safe_cycle tag which respects for loop nesting

Line 
1from django.utils.translation import ungettext, ugettext as _
2from django.utils.encoding import force_unicode
3from django import template
4from django.template import defaultfilters
5from django.template import Node, Variable
6from django.conf import settings
7from itertools import cycle as itertools_cycle
8
9register = template.Library()
10
11
12   
13class SafeCycleNode(Node):
14    def __init__(self, cyclevars, variable_name=None):
15        self.cyclevars = cyclevars
16        self.cycle_iter = itertools_cycle(cyclevars)
17        self.variable_name = variable_name
18
19    def render(self, context):
20        if context.has_key('forloop'):
21            if not context.get(self):
22                context[self] = True
23                self.cycle_iter = itertools_cycle(self.cyclevars)
24        value = self.cycle_iter.next()
25        value = Variable(value).resolve(context)
26        if self.variable_name:
27            context[self.variable_name] = value
28        return value
29
30
31
32#@register.tag
33def safe_cycle(parser, token):
34    """
35    Cycles among the given strings each time this tag is encountered.
36
37    Within a loop, cycles among the given strings each time through
38    the loop::
39
40        {% for o in some_list %}
41            <tr class="{% cycle 'row1' 'row2' %}">
42                ...
43            </tr>
44        {% endfor %}
45
46    Outside of a loop, give the values a unique name the first time you call
47    it, then use that name each sucessive time through::
48
49            <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
50            <tr class="{% cycle rowcolors %}">...</tr>
51            <tr class="{% cycle rowcolors %}">...</tr>
52
53    You can use any number of values, seperated by spaces. Commas can also
54    be used to separate values; if a comma is used, the cycle values are
55    interpreted as literal strings.
56    """
57
58    # Note: This returns the exact same node on each {% cycle name %} call;
59    # that is, the node object returned from {% cycle a b c as name %} and the
60    # one returned from {% cycle name %} are the exact same object.  This
61    # shouldn't cause problems (heh), but if it does, now you know.
62    #
63    # Ugly hack warning: this stuffs the named template dict into parser so
64    # that names are only unique within each template (as opposed to using
65    # a global variable, which would make cycle names have to be unique across
66    # *all* templates.
67
68    args = token.split_contents()
69
70    if len(args) < 2:
71        raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
72
73    if ',' in args[1]:
74        # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
75        # case.
76        args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
77
78    if len(args) == 2:
79        # {% cycle foo %} case.
80        name = args[1]
81        if not hasattr(parser, '_namedCycleNodes'):
82            raise TemplateSyntaxError("No named cycles in template."
83                                      " '%s' is not defined" % name)
84        if not name in parser._namedCycleNodes:
85            raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
86        return parser._namedCycleNodes[name]
87
88    if len(args) > 4 and args[-2] == 'as':
89        name = args[-1]
90        node = SafeCycleNode(args[1:-2], name)
91        if not hasattr(parser, '_namedCycleNodes'):
92            parser._namedCycleNodes = {}
93        parser._namedCycleNodes[name] = node
94    else:
95        node = SafeCycleNode(args[1:])
96    return node
97safe_cycle = register.tag(safe_cycle)