Code

Ticket #5908: t5908.diff

File t5908.diff, 7.9 KB (added by apollo13, 11 months ago)
Line 
1diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
2index 04e7a37..b6e0ecc 100644
3--- a/django/template/defaulttags.py
4+++ b/django/template/defaulttags.py
5@@ -66,6 +66,9 @@ class CycleNode(Node):
6         self.silent = silent
7         self.escape = escape        # only while the "future" version exists
8 
9+    def reset(self, context):
10+        context.render_context[self] = itertools_cycle(self.cyclevars)
11+
12     def render(self, context):
13         if self not in context.render_context:
14             # First time the node is rendered in template
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 node in the parser.
35 
36     args = token.split_contents()
37 
38@@ -623,9 +633,38 @@ 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._lastCycleNode = 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._lastCycleNode)
70+    except AttributeError:
71+        raise TemplateSyntaxError("No cycles in template.")
72+
73+@register.tag
74 def csrf_token(parser, token):
75     return CsrfTokenNode()
76 
77diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
78index 287fd4f..0df17af 100644
79--- a/docs/ref/templates/builtins.txt
80+++ b/docs/ref/templates/builtins.txt
81@@ -151,6 +151,9 @@ 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 
85+You can use the `resetcycle`_ tag to reset a ``{% cycle %}`` tag to restart
86+from its first value when it's next encountered.
87+
88 For backwards compatibility, the ``{% cycle %}`` tag supports the much inferior
89 old syntax from previous Django versions. You shouldn't use this in any new
90 projects, but for the sake of the people who are still using it, here's what it
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+``{% 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/docs/releases/1.6.txt b/docs/releases/1.6.txt
137index 9888925..01b6a36 100644
138--- a/docs/releases/1.6.txt
139+++ b/docs/releases/1.6.txt
140@@ -231,6 +231,9 @@ Minor features
141   `PIL`_ is pending deprecation (support to be removed in Django 1.8).
142   To upgrade, you should **first** uninstall PIL, **then** install Pillow.
143 
144+* Added a :ttag:`resetcycle` template tag which allows the user to reset the
145+  sequence of the :ttag:`cycle` template tag.
146+
147 .. _`Pillow`: https://pypi.python.org/pypi/Pillow
148 .. _`PIL`: https://pypi.python.org/pypi/PIL
149 
150diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py
151index 2aeaee9..0d90394 100644
152--- a/tests/template_tests/tests.py
153+++ b/tests/template_tests/tests.py
154@@ -819,6 +819,19 @@ class Templates(TestCase):
155             'cycle27': ('{% load cycle from future %}{% autoescape off %}{% cycle a b as ab %}{% cycle ab %}{% endautoescape %}', {'a': '<', 'b': '>'}, '<>'),
156             'cycle28': ('{% load cycle from future %}{% cycle a|safe b as ab %}{% cycle ab %}', {'a': '<', 'b': '>'}, '<&gt;'),
157 
158+            ### RESETCYCLE TAG ########################################################
159+            'resetcycle01': ("{% resetcycle %}", {}, template.TemplateSyntaxError),
160+            'resetcycle02': ("{% resetcycle undefinedcycle %}", {}, template.TemplateSyntaxError),
161+            'resetcycle04': ("{% cycle 'a' 'b' 'c' %}{% resetcycle undefinedcycle %}", {}, template.TemplateSyntaxError),
162+            'resetcycle05': ("{% cycle 'a' 'b' 'c' as abc %}{% resetcycle undefinedcycle %}", {}, template.TemplateSyntaxError),
163+            'resetcycle06': ("{% for i in test %}{% cycle 'a' 'b' %}{% resetcycle %}{% endfor %}", {'test': range(5)}, 'aaaaa'),
164+            'resetcycle07': ("{% cycle 'a' 'b' 'c' as abc %}{% for i in test %}{% cycle abc %}{% cycle '-' '+' %}{% resetcycle %}{% endfor %}", {'test': range(5)}, 'ab-c-a-b-c-'),
165+            '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-'),
166+            'resetcycle09': ("{% for i in outer %}{% for j in inner %}{% cycle 'a' 'b' %}{% endfor %}{% resetcycle %}{% endfor %}", {'outer': range(2), 'inner': range(3)}, 'abaaba'),
167+            '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'),
168+            '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'),
169+            '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'),
170+
171             ### EXCEPTIONS ############################################################
172 
173             # Raise exception for invalid template name