Code

Ticket #5865: cycle.diff

File cycle.diff, 6.7 KB (added by munhitsu, 6 years ago)

v2

Line 
1Index: django/template/defaulttags.py
2===================================================================
3--- django/template/defaulttags.py      (revision 6716)
4+++ django/template/defaulttags.py      (working copy)
5@@ -39,15 +39,67 @@
6 
7 class CycleNode(Node):
8     def __init__(self, cyclevars, variable_name=None):
9-        self.cycle_iter = itertools_cycle(cyclevars)
10+        self.cycle_iter = None
11         self.variable_name = variable_name
12+        self.cyclevars = cyclevars
13+        self.resolve = None
14+        self.collection = None
15 
16+    def render_init(self,context):
17+        if len(self.cyclevars) == 1:
18+        #startning from syntax check
19+            if not context.has_key(self.cyclevars[0]):
20+                raise TemplateSyntaxError("Named cycle '%s' does not exist" % self.cyclevars[0])
21+        try:
22+            i = iter(Variable(self.cyclevars[0]).resolve(context))
23+            isiterable = True
24+        except TypeError:
25+            isiterable = False
26+        except NameError:
27+            isiterable = False
28+        if len(self.cyclevars) == 1 and context.has_key(self.cyclevars[0]) and isiterable:
29+            #the {% cycle colors %} case
30+            self.resolve = False
31+            self.collection = Variable(self.cyclevars[0]).resolve(context)
32+            self.cycle_iter = itertools_cycle(self.collection)
33+        else:
34+            self.resolve = True
35+            self.collection = self.cyclevars
36+            self.cycle_iter = itertools_cycle(self.collection)
37+        context['cycle'] = {
38+            # cycle internals
39+            'collection': self.collection,
40+            'name':self.variable_name,
41+            'value':None,
42+        }
43+       
44+    def __repr__(self):
45+        return "<Cycle Node: cycle_iter %s, variable_name %s, cyclevars %s, resolve %s, collection %s>" % \
46+            (self.cycle_iter, self.variable_name, self.cyclevars, self.resolve, self.collection)
47+   
48     def render(self, context):
49-        value = self.cycle_iter.next()
50-        value = Variable(value).resolve(context)
51+        if self.collection == None:
52+            #first execution of render, so finally we will know the context
53+            self.render_init(context)
54+        #all is initiated
55+        if self.resolve:
56+            value = self.cycle_iter.next()
57+            try:
58+                value = Variable(value).resolve(context)
59+            except NameError:
60+                pass
61+        else:
62+            value = self.cycle_iter.next()
63+
64         if self.variable_name:
65             context[self.variable_name] = value
66-        return value
67+        context['cycle'] = {
68+            # cycle internals
69+            'collection': self.collection,
70+            'name':self.variable_name,
71+            'value':value,
72+        }
73+        return value       
74 
75 class DebugNode(Node):
76     def render(self, context):
77@@ -456,7 +508,16 @@
78 
79     You can use any number of values, seperated by spaces. Commas can also
80     be used to separate values; if a comma is used, the cycle values are
81-    interpreted as literal strings.
82+    interpreted as literal strings.
83+   
84+    Additionally when you pass a single value, the cycle tag resolves it from
85+    context and than iterates over context value::
86+
87+        {'colors': ['red', 'blue', 'green']}
88+
89+        {% for row in mydata %}
90+        <tr class="{% cycle colors %}">...</tr>
91+        {% endfor %}
92     """
93 
94     # Note: This returns the exact same node on each {% cycle name %} call;
95@@ -480,16 +541,22 @@
96         args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
97 
98     if len(args) == 2:
99-        # {% cycle foo %} case.
100+        #two cases:
101+        # {% cycle foo %}
102+        # {% cycle foos %} where foos are an iterable type defined in context
103         name = args[1]
104         if not hasattr(parser, '_namedCycleNodes'):
105-            raise TemplateSyntaxError("No named cycles in template."
106-                                      " '%s' is not defined" % name)
107-        if not name in parser._namedCycleNodes:
108-            raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
109-        return parser._namedCycleNodes[name]
110+            #if not a table than we will throw an exception at rendering
111+            parser._namedCycleNodes = {}
112+            node = CycleNode(args[1:2])
113+            parser._namedCycleNodes[name] = node
114+        else:
115+            if not name in parser._namedCycleNodes:
116+                raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
117+            node = parser._namedCycleNodes[name]
118+        return node
119 
120-    if len(args) > 4 and args[-2] == 'as':
121+    if len(args) >= 4 and args[-2] == 'as':
122         name = args[-1]
123         node = CycleNode(args[1:-2], name)
124         if not hasattr(parser, '_namedCycleNodes'):
125Index: tests/regressiontests/templates/tests.py
126===================================================================
127--- tests/regressiontests/templates/tests.py    (revision 6716)
128+++ tests/regressiontests/templates/tests.py    (working copy)
129@@ -394,6 +394,17 @@
130             'cycle13': ("{% for i in test %}{% cycle 'a' 'b' %}{{ i }},{% endfor %}", {'test': range(5)}, 'a0,b1,a2,b3,a4,'),
131             'cycle14': ("{% cycle one two as foo %}{% cycle foo %}", {'one': '1','two': '2'}, '12'),
132             'cycle13': ("{% for i in test %}{% cycle aye bee %}{{ i }},{% endfor %}", {'test': range(5), 'aye': 'a', 'bee': 'b'}, 'a0,b1,a2,b3,a4,'),
133+            # List format
134+            'cycle20': ("{% cycle colors %}", {'colors': 'r'}, 'r'),
135+            'cycle21': ("{% cycle colors %}{% cycle colors %}", {'colors': ['r', 'g', 'b']}, 'rg'),
136+#            'cycle22': ("{% cycle colors %}{% cycle colors %}", {'colors': {'r': 1, 'g': 2}}, 'rg'), not reliable test
137+            'cycle23': ("{% cycle colors %}{% cycle colors %}{% cycle colors %}", {'colors': ['r', 'g', 'b']}, 'rgb'),
138+            'cycle24': ("{% cycle colors %}{% cycle colors %}{% cycle colors %}{% cycle colors %}", {'colors': ['r', 'g', 'b']}, 'rgbr'),
139+            'cycle25': ("{% cycle colors as color %}{% cycle color %}", {'colors': ['r', 'g', 'b']}, 'rg'),
140+            'cycle26': ("{% cycle colors as color %}{% cycle color %}{% cycle color %}", {'colors': ['r', 'g', 'b']}, 'rgb'),
141+            'cycle27': ("{% cycle colors as color %}{% cycle color %}{% cycle color %}{% cycle color %}", {'colors': ['r', 'g', 'b']}, 'rgbr'),
142+            'cycle28': ("{% for i in test %}{% cycle colors %}{{ cycle.value }}{{ i }},{% endfor %}", {'test': range(5),'colors': ['r', 'g', 'b']}, 'rr0,gg1,bb2,rr3,gg4,'),
143+            'cycle29': ("{% for i in test %}{% cycle colors %}{% cycle colors %}{{ i }},{% endfor %}", {'test': range(5),'colors': ['r', 'g', 'b']}, 'rg0,br1,gb2,rg3,br4,'),
144 
145             ### EXCEPTIONS ############################################################
146