From f034c79cbc4f1a235e53d4ec666885ab8e9af8d9 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 2 Mar 2010 07:42:51 +0000 Subject: [PATCH] Fixed #6510 -- Refactored the way child nodes are found in template nodes to avoid potential inconsistencies. Thanks to SmileyChris for the patch. git-svn-id: http://code.djangoproject.com/svn/django/trunk@12654 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/template/__init__.py | 7 +++-- django/template/defaulttags.py | 24 ++++++----------- tests/regressiontests/templates/nodelist.py | 30 +++++++++++++++++++++ tests/regressiontests/templates/tests.py | 27 +++++++++++++++++++ 4 files changed, 70 insertions(+), 18 deletions(-) create mode 100644 tests/regressiontests/templates/nodelist.py diff --git a/django/template/__init__.py b/django/template/__init__.py index 7fb01f05b6..33ea0aa60f 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -770,6 +770,7 @@ class Node(object): # Set this to True for nodes that must be first in the template (although # they can be preceded by text nodes. must_be_first = False + child_nodelists = ('nodelist',) def render(self, context): "Return the node rendered as a string" @@ -783,8 +784,10 @@ class Node(object): nodes = [] if isinstance(self, nodetype): nodes.append(self) - if hasattr(self, 'nodelist'): - nodes.extend(self.nodelist.get_nodes_by_type(nodetype)) + for attr in self.child_nodelists: + nodelist = getattr(self, attr, None) + if nodelist: + nodes.extend(nodelist.get_nodes_by_type(nodetype)) return nodes class NodeList(list): diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 69afd84c43..4b0a4d1fa6 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -97,6 +97,8 @@ class FirstOfNode(Node): return u'' class ForNode(Node): + child_nodelists = ('nodelist_loop', 'nodelist_empty') + def __init__(self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None): self.loopvars, self.sequence = loopvars, sequence self.is_reversed = is_reversed @@ -118,14 +120,6 @@ class ForNode(Node): for node in self.nodelist_empty: yield node - def get_nodes_by_type(self, nodetype): - nodes = [] - if isinstance(self, nodetype): - nodes.append(self) - nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype)) - nodes.extend(self.nodelist_empty.get_nodes_by_type(nodetype)) - return nodes - def render(self, context): if 'forloop' in context: parentloop = context['forloop'] @@ -181,6 +175,8 @@ class ForNode(Node): return nodelist.render(context) class IfChangedNode(Node): + child_nodelists = ('nodelist_true', 'nodelist_false') + def __init__(self, nodelist_true, nodelist_false, *varlist): self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false self._last_seen = None @@ -211,6 +207,8 @@ class IfChangedNode(Node): return '' class IfEqualNode(Node): + child_nodelists = ('nodelist_true', 'nodelist_false') + def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): self.var1, self.var2 = var1, var2 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false @@ -227,6 +225,8 @@ class IfEqualNode(Node): return self.nodelist_false.render(context) class IfNode(Node): + child_nodelists = ('nodelist_true', 'nodelist_false') + def __init__(self, var, nodelist_true, nodelist_false=None): self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false self.var = var @@ -240,14 +240,6 @@ class IfNode(Node): for node in self.nodelist_false: yield node - def get_nodes_by_type(self, nodetype): - nodes = [] - if isinstance(self, nodetype): - nodes.append(self) - nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype)) - nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype)) - return nodes - def render(self, context): if self.var.eval(context): return self.nodelist_true.render(context) diff --git a/tests/regressiontests/templates/nodelist.py b/tests/regressiontests/templates/nodelist.py new file mode 100644 index 0000000000..89fac97c66 --- /dev/null +++ b/tests/regressiontests/templates/nodelist.py @@ -0,0 +1,30 @@ +from unittest import TestCase +from django.template.loader import get_template_from_string +from django.template import VariableNode + + +class NodelistTest(TestCase): + + def test_for(self): + source = '{% for i in 1 %}{{ a }}{% endfor %}' + template = get_template_from_string(source) + vars = template.nodelist.get_nodes_by_type(VariableNode) + self.assertEqual(len(vars), 1) + + def test_if(self): + source = '{% if x %}{{ a }}{% endif %}' + template = get_template_from_string(source) + vars = template.nodelist.get_nodes_by_type(VariableNode) + self.assertEqual(len(vars), 1) + + def test_ifequal(self): + source = '{% ifequal x y %}{{ a }}{% endifequal %}' + template = get_template_from_string(source) + vars = template.nodelist.get_nodes_by_type(VariableNode) + self.assertEqual(len(vars), 1) + + def test_ifchanged(self): + source = '{% ifchanged x %}{{ a }}{% endifchanged %}' + template = get_template_from_string(source) + vars = template.nodelist.get_nodes_by_type(VariableNode) + self.assertEqual(len(vars), 1) diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 31c9e249a9..0d283dd9b7 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -24,6 +24,7 @@ from context import context_tests from custom import custom_filters from parser import token_parsing, filter_parsing, variable_parsing from unicode import unicode_tests +from nodelist import NodelistTest from smartif import * try: @@ -886,6 +887,32 @@ class Templates(unittest.TestCase): # Inheritance from a template with a space in its name should work. 'inheritance29': ("{% extends 'inheritance 28' %}", {}, '!'), + # Base template, putting block in a conditional {% if %} tag + 'inheritance30': ("1{% if optional %}{% block opt %}2{% endblock %}{% endif %}3", {'optional': True}, '123'), + + # Inherit from a template with block wrapped in an {% if %} tag (in parent), still gets overridden + 'inheritance31': ("{% extends 'inheritance30' %}{% block opt %}two{% endblock %}", {'optional': True}, '1two3'), + 'inheritance32': ("{% extends 'inheritance30' %}{% block opt %}two{% endblock %}", {}, '13'), + + # Base template, putting block in a conditional {% ifequal %} tag + 'inheritance33': ("1{% ifequal optional 1 %}{% block opt %}2{% endblock %}{% endifequal %}3", {'optional': 1}, '123'), + + # Inherit from a template with block wrapped in an {% ifequal %} tag (in parent), still gets overridden + 'inheritance34': ("{% extends 'inheritance33' %}{% block opt %}two{% endblock %}", {'optional': 1}, '1two3'), + 'inheritance35': ("{% extends 'inheritance33' %}{% block opt %}two{% endblock %}", {'optional': 2}, '13'), + + # Base template, putting block in a {% for %} tag + 'inheritance36': ("{% for n in numbers %}_{% block opt %}{{ n }}{% endblock %}{% endfor %}_", {'numbers': '123'}, '_1_2_3_'), + + # Inherit from a template with block wrapped in an {% for %} tag (in parent), still gets overridden + 'inheritance37': ("{% extends 'inheritance36' %}{% block opt %}X{% endblock %}", {'numbers': '123'}, '_X_X_X_'), + 'inheritance38': ("{% extends 'inheritance36' %}{% block opt %}X{% endblock %}", {}, '_'), + + # The super block will still be found. + 'inheritance39': ("{% extends 'inheritance30' %}{% block opt %}new{{ block.super }}{% endblock %}", {'optional': True}, '1new23'), + 'inheritance40': ("{% extends 'inheritance33' %}{% block opt %}new{{ block.super }}{% endblock %}", {'optional': 1}, '1new23'), + 'inheritance41': ("{% extends 'inheritance36' %}{% block opt %}new{{ block.super }}{% endblock %}", {'numbers': '123'}, '_new1_new2_new3_'), + ### I18N ################################################################## # {% spaceless %} tag