From 3ae3a1f9fad800d73892b14ac8a576e817a2da55 Mon Sep 17 00:00:00 2001 From: Alexander Schrijver Date: Sat, 16 Jul 2016 22:55:40 +0200 Subject: [PATCH] Fixed #26830 -- Prevented the 'with' templatetag from resetting the cycle variable to its initial state. --- django/template/context.py | 12 +++++++ django/template/defaulttags.py | 2 +- .../template_tests/syntax_tests/test_cycle.py | 22 +++++++++++++ tests/template_tests/test_context.py | 31 +++++++++++++++++++ 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/django/template/context.py b/django/template/context.py index 361f3503ca..1e1c391229 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -67,6 +67,18 @@ class BaseContext(object): "Set a variable in the current context" self.dicts[-1][key] = value + def set_upward(self, key, value): + """ + Set a variable in one of the higher contexts if it exists there, + otherwise in the current context. + """ + context = self.dicts[-1] + for d in reversed(self.dicts): + if key in d.keys(): + context = d + break + context[key] = value + def __getitem__(self, key): "Get a variable's value, starting at the current context and going upward" for d in reversed(self.dicts): diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index bfe00ca83d..87ac5e2d83 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -82,7 +82,7 @@ class CycleNode(Node): cycle_iter = context.render_context[self] value = next(cycle_iter).resolve(context) if self.variable_name: - context[self.variable_name] = value + context.set_upward(self.variable_name, value) if self.silent: return '' return render_value_in_context(value, context) diff --git a/tests/template_tests/syntax_tests/test_cycle.py b/tests/template_tests/syntax_tests/test_cycle.py index e98a7ec509..b5712b54bb 100644 --- a/tests/template_tests/syntax_tests/test_cycle.py +++ b/tests/template_tests/syntax_tests/test_cycle.py @@ -145,3 +145,25 @@ class CycleTagTests(SimpleTestCase): """ output = self.engine.render_to_string('cycle29', {'values': [1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 9, 9]}) self.assertEqual(output, 'bcabcabcccaa') + + @setup({ + 'cycle30': "{% cycle 'a' 'b' 'c' as cycler silent %}" + "{% for x in values %}" + "{% with doesnothing=irrelevant %}" + "{% ifchanged x %}" + "{% cycle cycler %}{{ cycler }}" + "{% else %}" + "{{ cycler }}" + "{% endifchanged %}" + "{% endwith %}" + "{% endfor %}"}) + def test_cycle30(self): + """ + A {% with %} tag shouldn't reset the {% cycle %} variable. + """ + output = self.engine.render_to_string( + 'cycle30', { + 'irrelevant': 1, + 'values': [1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 9, 9] + }) + self.assertEqual(output, 'bcabcabcccaa') diff --git a/tests/template_tests/test_context.py b/tests/template_tests/test_context.py index 94011fb0f1..c061610a19 100644 --- a/tests/template_tests/test_context.py +++ b/tests/template_tests/test_context.py @@ -204,6 +204,37 @@ class ContextTests(SimpleTestCase): self.assertEqual(str(warns[2].message), msg2) self.assertEqual(str(warns[3].message), msg2) + def test_set_upward(self): + c = Context({'a': 1}) + c.set_upward('a', 2) + self.assertEqual(c.get('a'), 2) + + def test_set_upward_empty_context(self): + empty_context = Context() + empty_context.set_upward('a', 1) + self.assertEqual(empty_context.get('a'), 1) + + def test_set_upward_with_push(self): + """ + The highest context which has the given key is used. + """ + c = Context({'a': 1}) + c.push({'a': 2}) + c.set_upward('a', 3) + self.assertEqual(c.get('a'), 3) + c.pop() + self.assertEqual(c.get('a'), 1) + + def test_set_upward_with_push_no_match(self): + """ + The highest context is used if the given key isn't found. + """ + c = Context({'b': 1}) + c.push({'b': 2}) + c.set_upward('a', 2) + self.assertEqual(len(c.dicts), 3) + self.assertEqual(c.dicts[-1]['a'], 2) + class RequestContextTests(SimpleTestCase):