Code

Ticket #5908: 5908-resetcycle.4.diff

File 5908-resetcycle.4.diff, 7.7 KB (added by b.schube@…, 14 months ago)

Updated patch to current trunk

Line 
1diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
2index 04e7a37..b84ed4e 100644
3--- a/django/template/defaulttags.py
4+++ b/django/template/defaulttags.py
5@@ -65,6 +65,9 @@ class CycleNode(Node):
6         self.variable_name = variable_name
7         self.silent = silent
8         self.escape = escape        # only while the "future" version exists
9+       
10+    def reset(self, context):
11+        context.render_context[self] = itertools_cycle(self.cyclevars)
12 
13     def render(self, context):
14         if self not in context.render_context:
15@@ -80,6 +83,13 @@ class CycleNode(Node):
16             value = mark_safe(value)
17         return render_value_in_context(value, context)
18 
19+class ResetCycleNode(Node):
20+    def __init__(self, node):
21+        self.node = node
22+    def render(self, context):
23+        self.node.reset(context)
24+        return ''
25+
26 class DebugNode(Node):
27     def render(self, context):
28         from pprint import pformat
29@@ -578,7 +588,7 @@ def cycle(parser, token, escape=False):
30     # Ugly hack warning: This stuffs the named template dict into parser so
31     # that names are only unique within each template (as opposed to using
32     # a global variable, which would make cycle names have to be unique across
33-    # *all* templates.
34+    # *all* templates. Also keeps last unnamed node in the parser.
35 
36     args = token.split_contents()
37 
38@@ -623,7 +633,36 @@ def cycle(parser, token, escape=False):
39     else:
40         values = [parser.compile_filter(arg) for arg in args[1:]]
41         node = CycleNode(values, escape=escape)
42+        parser._lastUnnamedCycleNode = node
43     return node
44+   
45+@register.tag
46+def resetcycle(parser, token):
47+    """
48+    Resets a cycle tag.
49+
50+    If an argument is given, resets the last rendered cycle tag whose name
51+    matches the argument, else resets last rendered unnamed cycle tag.
52+
53+    """
54+    args = token.split_contents()
55+
56+    if len(args) > 2:
57+        raise TemplateSyntaxError("%r tag accepts at most one argument."
58+                                  % args[0])
59+    if len(args) == 2:
60+        name = args[1]
61+        try:
62+            return ResetCycleNode(parser._namedCycleNodes[name])
63+        except AttributeError:
64+            raise TemplateSyntaxError("No named cycles in template. "
65+                                      "%r is not defined" % name)
66+        except KeyError:
67+            raise TemplateSyntaxError("Named cycle %r does not exist" % name)
68+    try:
69+        return ResetCycleNode(parser._lastUnnamedCycleNode)
70+    except AttributeError:
71+        raise TemplateSyntaxError("No unnamed cycles in template.")
72 
73 @register.tag
74 def csrf_token(parser, token):
75diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
76index 287fd4f..86f5d0f 100644
77--- a/docs/ref/templates/builtins.txt
78+++ b/docs/ref/templates/builtins.txt
79@@ -149,7 +149,10 @@ string literals, while values without quotes are treated as template variables.
80 
81 Note that currently the variables included in the cycle will not be escaped.
82 Any HTML or Javascript code contained in the printed variable will be rendered
83-as-is, which could potentially lead to security issues.
84+as-is, which could potentially lead to security issues.86
85+
86+You can use the `resetcycle`_ tag to reset a ``{% cycle %}`` tag to restart
87+from its first value when it's next encountered.
88 
89 For backwards compatibility, the ``{% cycle %}`` tag supports the much inferior
90 old syntax from previous Django versions. You shouldn't use this in any new
91@@ -893,6 +896,44 @@ attribute, allowing  you to group on the display string rather than the
92 ``{{ country.grouper }}`` will now display the value fields from the
93 ``choices`` set rather than the keys.
94 
95+.. templatetag:: resetcycle
96+
97+resetcycle
98+^^^^^^^^^^
99+
100+Resets a previous `cycle`_ so that it restarts from its first item at its next
101+invocation.  Without arguments, ``{% resetcycle %}`` will reset the last
102+unnamed ``{% cycle %}`` defined in the template.
103+
104+Example usage::
105+
106+    {% for coach in coach_list %}
107+        <h1>{{ coach.name }}</h1>
108+        {% for athlete in coach.athlete_set.all %}
109+            <p class="{% cycle 'odd' 'even' %}">{{ athlete.name }}</p>
110+        {% endfor %}
111+        {% resetcycle %}
112+    {% endfor %}
113+
114+The athlete list for every coach starts with ``class="odd"``.  Without the
115+``{% resetcycle %}`` tag, the first athlete of a coach might be rendered with
116+``class="even"`` if the last athlete of the previous coach had ``class="odd"``.
117+
118+You can also reset named cycle tags::
119+
120+    {% for item in list %}
121+        <p class="{% cycle 'odd' 'evn' as stripe %} {% cycle 'majr' 'minr' 'minr' 'minr' 'minr' as tick %}">
122+           {{ item.data }}
123+       </p>
124+        {% ifchanged item.category %}
125+            <h1>{{ item.category }}</h1>
126+            {% if not forloop.first %}{% resetcycle tick %}{% endif %}
127+        {% endifchanged %}
128+    {% endfor %}
129+
130+In this example we have both the alternating odd/even rows and a "major" row
131+every fifth row.  Only the five-row cycle is reset when a category changes.
132+
133 .. templatetag:: spaceless
134 
135 spaceless
136diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py
137index 2aeaee9..c2b3a76 100644
138--- a/tests/template_tests/tests.py
139+++ b/tests/template_tests/tests.py
140@@ -818,6 +818,20 @@ class Templates(TestCase):
141             'cycle26': ('{% load cycle from future %}{% cycle a b as ab %}{% cycle ab %}', {'a': '<', 'b': '>'}, '&lt;&gt;'),
142             'cycle27': ('{% load cycle from future %}{% autoescape off %}{% cycle a b as ab %}{% cycle ab %}{% endautoescape %}', {'a': '<', 'b': '>'}, '<>'),
143             'cycle28': ('{% load cycle from future %}{% cycle a|safe b as ab %}{% cycle ab %}', {'a': '<', 'b': '>'}, '<&gt;'),
144+           
145+            ### RESETCYCLE TAG ########################################################
146+            'resetcycle01': ("{% resetcycle %}", {}, template.TemplateSyntaxError),
147+            'resetcycle02': ("{% resetcycle undefinedcycle %}", {}, template.TemplateSyntaxError),
148+            'resetcycle03': ("{% cycle 'a' 'b' 'c' as abc %}{% resetcycle %}", {}, template.TemplateSyntaxError),
149+            'resetcycle04': ("{% cycle 'a' 'b' 'c' %}{% resetcycle undefinedcycle %}", {}, template.TemplateSyntaxError),
150+            'resetcycle05': ("{% cycle 'a' 'b' 'c' as abc %}{% resetcycle undefinedcycle %}", {}, template.TemplateSyntaxError),
151+            'resetcycle06': ("{% for i in test %}{% cycle 'a' 'b' %}{% resetcycle %}{% endfor %}", {'test': range(5)}, 'aaaaa'),
152+            'resetcycle07': ("{% cycle 'a' 'b' 'c' as abc %}{% for i in test %}{% cycle abc %}{% cycle '-' '+' %}{% resetcycle %}{% endfor %}", {'test': range(5)}, 'ab-c-a-b-c-'),
153+            'resetcycle08': ("{% cycle 'a' 'b' 'c' as abc %}{% for i in test %}{% resetcycle abc %}{% cycle abc %}{% cycle '-' '+' %}{% endfor %}", {'test': range(5)}, 'aa-a+a-a+a-'),
154+            'resetcycle09': ("{% for i in outer %}{% for j in inner %}{% cycle 'a' 'b' %}{% endfor %}{% resetcycle %}{% endfor %}", {'outer': range(2), 'inner': range(3)}, 'abaaba'),
155+            'resetcycle10': ("{% for i in outer %}{% cycle 'a' 'b' %}{% for j in inner %}{% cycle 'X' 'Y' %}{% endfor %}{% resetcycle %}{% endfor %}", {'outer': range(2), 'inner': range(3)}, 'aXYXbXYX'),
156+            'resetcycle11': ("{% for i in test %}{% cycle 'X' 'Y' 'Z' as XYZ %}{% cycle 'a' 'b' 'c' as abc %}{% ifequal i 1 %}{% resetcycle abc %}{% endifequal %}{% endfor %}", {'test': range(5)}, 'XaYbZaXbYc'),
157+            'resetcycle12': ("{% for i in test %}{% cycle 'X' 'Y' 'Z' as XYZ %}{% cycle 'a' 'b' 'c' as abc %}{% ifequal i 1 %}{% resetcycle XYZ %}{% endifequal %}{% endfor %}", {'test': range(5)}, 'XaYbXcYaZb'),
158 
159             ### EXCEPTIONS ############################################################
160