mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Switched {% cycle %} and {% firstof %} tags to auto-escape their variables per deprecation timeline.
refs #17906.
This commit is contained in:
		| @@ -17,7 +17,6 @@ from django.template.base import (Node, NodeList, Template, Context, Library, | |||||||
|     render_value_in_context) |     render_value_in_context) | ||||||
| from django.template.smartif import IfParser, Literal | from django.template.smartif import IfParser, Literal | ||||||
| from django.template.defaultfilters import date | from django.template.defaultfilters import date | ||||||
| from django.utils.deprecation import RemovedInDjango18Warning |  | ||||||
| from django.utils.encoding import force_text, smart_text | from django.utils.encoding import force_text, smart_text | ||||||
| from django.utils.safestring import mark_safe | from django.utils.safestring import mark_safe | ||||||
| from django.utils.html import format_html | from django.utils.html import format_html | ||||||
| @@ -65,11 +64,10 @@ class CsrfTokenNode(Node): | |||||||
|  |  | ||||||
|  |  | ||||||
| class CycleNode(Node): | class CycleNode(Node): | ||||||
|     def __init__(self, cyclevars, variable_name=None, silent=False, escape=False): |     def __init__(self, cyclevars, variable_name=None, silent=False): | ||||||
|         self.cyclevars = cyclevars |         self.cyclevars = cyclevars | ||||||
|         self.variable_name = variable_name |         self.variable_name = variable_name | ||||||
|         self.silent = silent |         self.silent = silent | ||||||
|         self.escape = escape        # only while the "future" version exists |  | ||||||
|  |  | ||||||
|     def render(self, context): |     def render(self, context): | ||||||
|         if self not in context.render_context: |         if self not in context.render_context: | ||||||
| @@ -81,8 +79,6 @@ class CycleNode(Node): | |||||||
|             context[self.variable_name] = value |             context[self.variable_name] = value | ||||||
|         if self.silent: |         if self.silent: | ||||||
|             return '' |             return '' | ||||||
|         if not self.escape: |  | ||||||
|             value = mark_safe(value) |  | ||||||
|         return render_value_in_context(value, context) |         return render_value_in_context(value, context) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -107,16 +103,13 @@ class FilterNode(Node): | |||||||
|  |  | ||||||
|  |  | ||||||
| class FirstOfNode(Node): | class FirstOfNode(Node): | ||||||
|     def __init__(self, variables, escape=False): |     def __init__(self, variables): | ||||||
|         self.vars = variables |         self.vars = variables | ||||||
|         self.escape = escape        # only while the "future" version exists |  | ||||||
|  |  | ||||||
|     def render(self, context): |     def render(self, context): | ||||||
|         for var in self.vars: |         for var in self.vars: | ||||||
|             value = var.resolve(context, True) |             value = var.resolve(context, True) | ||||||
|             if value: |             if value: | ||||||
|                 if not self.escape: |  | ||||||
|                     value = mark_safe(value) |  | ||||||
|                 return render_value_in_context(value, context) |                 return render_value_in_context(value, context) | ||||||
|         return '' |         return '' | ||||||
|  |  | ||||||
| @@ -554,7 +547,7 @@ def comment(parser, token): | |||||||
|  |  | ||||||
|  |  | ||||||
| @register.tag | @register.tag | ||||||
| def cycle(parser, token, escape=False): | def cycle(parser, token): | ||||||
|     """ |     """ | ||||||
|     Cycles among the given strings each time this tag is encountered. |     Cycles among the given strings each time this tag is encountered. | ||||||
|  |  | ||||||
| @@ -587,13 +580,6 @@ def cycle(parser, token, escape=False): | |||||||
|         {% endfor %} |         {% endfor %} | ||||||
|  |  | ||||||
|     """ |     """ | ||||||
|     if not escape: |  | ||||||
|         warnings.warn( |  | ||||||
|             "'The `cycle` template tag is changing to escape its arguments; " |  | ||||||
|             "the non-autoescaping version is deprecated. Load it " |  | ||||||
|             "from the `future` tag library to start using the new behavior.", |  | ||||||
|             RemovedInDjango18Warning, stacklevel=2) |  | ||||||
|  |  | ||||||
|     # Note: This returns the exact same node on each {% cycle name %} call; |     # Note: This returns the exact same node on each {% cycle name %} call; | ||||||
|     # that is, the node object returned from {% cycle a b c as name %} and the |     # that is, the node object returned from {% cycle a b c as name %} and the | ||||||
|     # one returned from {% cycle name %} are the exact same object. This |     # one returned from {% cycle name %} are the exact same object. This | ||||||
| @@ -640,13 +626,13 @@ def cycle(parser, token, escape=False): | |||||||
|     if as_form: |     if as_form: | ||||||
|         name = args[-1] |         name = args[-1] | ||||||
|         values = [parser.compile_filter(arg) for arg in args[1:-2]] |         values = [parser.compile_filter(arg) for arg in args[1:-2]] | ||||||
|         node = CycleNode(values, name, silent=silent, escape=escape) |         node = CycleNode(values, name, silent=silent) | ||||||
|         if not hasattr(parser, '_namedCycleNodes'): |         if not hasattr(parser, '_namedCycleNodes'): | ||||||
|             parser._namedCycleNodes = {} |             parser._namedCycleNodes = {} | ||||||
|         parser._namedCycleNodes[name] = node |         parser._namedCycleNodes[name] = node | ||||||
|     else: |     else: | ||||||
|         values = [parser.compile_filter(arg) for arg in args[1:]] |         values = [parser.compile_filter(arg) for arg in args[1:]] | ||||||
|         node = CycleNode(values, escape=escape) |         node = CycleNode(values) | ||||||
|     return node |     return node | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -701,7 +687,7 @@ def do_filter(parser, token): | |||||||
|  |  | ||||||
|  |  | ||||||
| @register.tag | @register.tag | ||||||
| def firstof(parser, token, escape=False): | def firstof(parser, token): | ||||||
|     """ |     """ | ||||||
|     Outputs the first variable passed that is not False, without escaping. |     Outputs the first variable passed that is not False, without escaping. | ||||||
|  |  | ||||||
| @@ -735,17 +721,10 @@ def firstof(parser, token, escape=False): | |||||||
|         {% endfilter %} |         {% endfilter %} | ||||||
|  |  | ||||||
|     """ |     """ | ||||||
|     if not escape: |  | ||||||
|         warnings.warn( |  | ||||||
|             "'The `firstof` template tag is changing to escape its arguments; " |  | ||||||
|             "the non-autoescaping version is deprecated. Load it " |  | ||||||
|             "from the `future` tag library to start using the new behavior.", |  | ||||||
|             RemovedInDjango18Warning, stacklevel=2) |  | ||||||
|  |  | ||||||
|     bits = token.split_contents()[1:] |     bits = token.split_contents()[1:] | ||||||
|     if len(bits) < 1: |     if len(bits) < 1: | ||||||
|         raise TemplateSyntaxError("'firstof' statement requires at least one argument") |         raise TemplateSyntaxError("'firstof' statement requires at least one argument") | ||||||
|     return FirstOfNode([parser.compile_filter(bit) for bit in bits], escape=escape) |     return FirstOfNode([parser.compile_filter(bit) for bit in bits]) | ||||||
|  |  | ||||||
|  |  | ||||||
| @register.tag('for') | @register.tag('for') | ||||||
|   | |||||||
| @@ -29,6 +29,8 @@ def url(parser, token): | |||||||
| def cycle(parser, token): | def cycle(parser, token): | ||||||
|     """ |     """ | ||||||
|     This is the future version of `cycle` with auto-escaping. |     This is the future version of `cycle` with auto-escaping. | ||||||
|  |     The deprecation is now complete and this version is no different | ||||||
|  |     from the non-future version so this can be deprecated (#22306) | ||||||
|  |  | ||||||
|     By default all strings are escaped. |     By default all strings are escaped. | ||||||
|  |  | ||||||
| @@ -42,13 +44,15 @@ def cycle(parser, token): | |||||||
|  |  | ||||||
|         {% cycle var1 var2|safe var3|safe  as somecycle %} |         {% cycle var1 var2|safe var3|safe  as somecycle %} | ||||||
|     """ |     """ | ||||||
|     return defaulttags.cycle(parser, token, escape=True) |     return defaulttags.cycle(parser, token) | ||||||
|  |  | ||||||
|  |  | ||||||
| @register.tag | @register.tag | ||||||
| def firstof(parser, token): | def firstof(parser, token): | ||||||
|     """ |     """ | ||||||
|     This is the future version of `firstof` with auto-escaping. |     This is the future version of `firstof` with auto-escaping. | ||||||
|  |     The deprecation is now complete and this version is no different | ||||||
|  |     from the non-future version so this can be deprecated (#22306) | ||||||
|  |  | ||||||
|     This is equivalent to:: |     This is equivalent to:: | ||||||
|  |  | ||||||
| @@ -71,4 +75,4 @@ def firstof(parser, token): | |||||||
|         {% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %} |         {% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %} | ||||||
|  |  | ||||||
|     """ |     """ | ||||||
|     return defaulttags.firstof(parser, token, escape=True) |     return defaulttags.firstof(parser, token) | ||||||
|   | |||||||
| @@ -102,13 +102,11 @@ this:: | |||||||
|         </tr> |         </tr> | ||||||
|     {% endfor %} |     {% endfor %} | ||||||
|  |  | ||||||
| Note that the variables included in the cycle will not be escaped. Any HTML or | Variables included in the cycle will be escaped.  You can disable auto-escaping | ||||||
| Javascript code contained in the printed variable will be rendered as-is, which | with:: | ||||||
| could potentially lead to security issues. So either make sure that you trust |  | ||||||
| their values or use explicit escaping like this:: |  | ||||||
|  |  | ||||||
|     {% for o in some_list %} |     {% for o in some_list %} | ||||||
|         <tr class="{% filter force_escape %}{% cycle rowvalue1 rowvalue2 %}{% endfilter %}"> |         <tr class="{% autoescape off %}{% cycle rowvalue1 rowvalue2 %}{% endautoescape | ||||||
|             ... |             ... | ||||||
|         </tr> |         </tr> | ||||||
|     {% endfor %} |     {% endfor %} | ||||||
| @@ -196,21 +194,6 @@ In this syntax, each value gets interpreted as a literal string, and there's no | |||||||
| way to specify variable values. Or literal commas. Or spaces. Did we mention | way to specify variable values. Or literal commas. Or spaces. Did we mention | ||||||
| you shouldn't use this syntax in any new projects? | you shouldn't use this syntax in any new projects? | ||||||
|  |  | ||||||
| .. versionchanged:: 1.6 |  | ||||||
|  |  | ||||||
| To improve safety, future versions of ``cycle`` will automatically escape |  | ||||||
| their output. You're encouraged to activate this behavior by loading |  | ||||||
| ``cycle`` from the ``future`` template library:: |  | ||||||
|  |  | ||||||
|     {% load cycle from future %} |  | ||||||
|  |  | ||||||
| When using the ``future`` version, you can disable auto-escaping with:: |  | ||||||
|  |  | ||||||
|     {% for o in some_list %} |  | ||||||
|         <tr class="{% autoescape off %}{% cycle rowvalue1 rowvalue2 %}{% endautoescape %}"> |  | ||||||
|             ... |  | ||||||
|         </tr> |  | ||||||
|     {% endfor %} |  | ||||||
|  |  | ||||||
| .. templatetag:: debug | .. templatetag:: debug | ||||||
|  |  | ||||||
| @@ -268,10 +251,8 @@ Sample usage:: | |||||||
| firstof | firstof | ||||||
| ^^^^^^^ | ^^^^^^^ | ||||||
|  |  | ||||||
| Outputs the first argument variable that is not False. This tag does *not* | Outputs the first argument variable that is not ``False``. Outputs nothing if | ||||||
| auto-escape variable values. | all the passed variables are ``False``. | ||||||
|  |  | ||||||
| Outputs nothing if all the passed variables are False. |  | ||||||
|  |  | ||||||
| Sample usage:: | Sample usage:: | ||||||
|  |  | ||||||
| @@ -292,32 +273,15 @@ passed variables are False:: | |||||||
|  |  | ||||||
|     {% firstof var1 var2 var3 "fallback value" %} |     {% firstof var1 var2 var3 "fallback value" %} | ||||||
|  |  | ||||||
| Note that currently the variables included in the firstof tag will not be | This tag auto-escapes variable values. You can disable auto-escaping with:: | ||||||
| escaped. Any HTML or Javascript code contained in the printed variable will be |  | ||||||
| rendered as-is, which could potentially lead to security issues. If you need |  | ||||||
| to escape the variables in the firstof tag, you must do so explicitly:: |  | ||||||
|  |  | ||||||
|     {% filter force_escape %} |     {% autoescape off %} | ||||||
|         {% firstof var1 var2 var3 "fallback value" %} |         {% firstof var1 var2 var3 "<strong>fallback value</strong>" %} | ||||||
|     {% endfilter %} |     {% endautoescape %} | ||||||
|  |  | ||||||
| .. versionchanged:: 1.6 | Or if only some variables should be escaped, you can use:: | ||||||
|  |  | ||||||
|     To improve safety, future versions of ``firstof`` will automatically escape |     {% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %} | ||||||
|     their output. You're encouraged to activate this behavior by loading |  | ||||||
|     ``firstof`` from the ``future`` template library:: |  | ||||||
|  |  | ||||||
|         {% load firstof from future %} |  | ||||||
|  |  | ||||||
|     When using the ``future`` version, you can disable auto-escaping with:: |  | ||||||
|  |  | ||||||
|         {% autoescape off %} |  | ||||||
|             {% firstof var1 var2 var3 "<strong>fallback value</strong>" %} |  | ||||||
|         {% endautoescape %} |  | ||||||
|  |  | ||||||
|     Or if only some variables should be escaped, you can use:: |  | ||||||
|  |  | ||||||
|         {% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %} |  | ||||||
|  |  | ||||||
| .. templatetag:: for | .. templatetag:: for | ||||||
|  |  | ||||||
|   | |||||||
| @@ -883,13 +883,13 @@ class TemplateTests(TestCase): | |||||||
|             'cycle17': ("{% cycle 'a' 'b' 'c' as abc silent %}{% cycle abc %}{% cycle abc %}{% cycle abc %}{% cycle abc %}", {}, ""), |             'cycle17': ("{% cycle 'a' 'b' 'c' as abc silent %}{% cycle abc %}{% cycle abc %}{% cycle abc %}{% cycle abc %}", {}, ""), | ||||||
|             'cycle18': ("{% cycle 'a' 'b' 'c' as foo invalid_flag %}", {}, template.TemplateSyntaxError), |             'cycle18': ("{% cycle 'a' 'b' 'c' as foo invalid_flag %}", {}, template.TemplateSyntaxError), | ||||||
|             'cycle19': ("{% cycle 'a' 'b' as silent %}{% cycle silent %}", {}, "ab"), |             'cycle19': ("{% cycle 'a' 'b' as silent %}{% cycle silent %}", {}, "ab"), | ||||||
|             'cycle20': ("{% cycle one two as foo %} & {% cycle foo %}", {'one': 'A & B', 'two': 'C & D'}, "A & B & C & D"), |             'cycle20': ("{% cycle one two as foo %} & {% cycle foo %}", {'one': 'A & B', 'two': 'C & D'}, "A & B & C & D"), | ||||||
|             'cycle21': ("{% filter force_escape %}{% cycle one two as foo %} & {% cycle foo %}{% endfilter %}", {'one': 'A & B', 'two': 'C & D'}, "A & B & C & D"), |             'cycle21': ("{% filter force_escape %}{% cycle one two as foo %} & {% cycle foo %}{% endfilter %}", {'one': 'A & B', 'two': 'C & D'}, "A &amp; B & C &amp; D"), | ||||||
|             'cycle22': ("{% for x in values %}{% cycle 'a' 'b' 'c' as abc silent %}{{ x }}{% endfor %}", {'values': [1, 2, 3, 4]}, "1234"), |             'cycle22': ("{% for x in values %}{% cycle 'a' 'b' 'c' as abc silent %}{{ x }}{% endfor %}", {'values': [1, 2, 3, 4]}, "1234"), | ||||||
|             'cycle23': ("{% for x in values %}{% cycle 'a' 'b' 'c' as abc silent %}{{ abc }}{{ x }}{% endfor %}", {'values': [1, 2, 3, 4]}, "a1b2c3a4"), |             'cycle23': ("{% for x in values %}{% cycle 'a' 'b' 'c' as abc silent %}{{ abc }}{{ x }}{% endfor %}", {'values': [1, 2, 3, 4]}, "a1b2c3a4"), | ||||||
|             'included-cycle': ('{{ abc }}', {'abc': 'xxx'}, 'xxx'), |             'included-cycle': ('{{ abc }}', {'abc': 'xxx'}, 'xxx'), | ||||||
|             'cycle24': ("{% for x in values %}{% cycle 'a' 'b' 'c' as abc silent %}{% include 'included-cycle' %}{% endfor %}", {'values': [1, 2, 3, 4]}, "abca"), |             'cycle24': ("{% for x in values %}{% cycle 'a' 'b' 'c' as abc silent %}{% include 'included-cycle' %}{% endfor %}", {'values': [1, 2, 3, 4]}, "abca"), | ||||||
|             'cycle25': ('{% cycle a as abc %}', {'a': '<'}, '<'), |             'cycle25': ('{% cycle a as abc %}', {'a': '<'}, '<'), | ||||||
|  |  | ||||||
|             'cycle26': ('{% load cycle from future %}{% cycle a b as ab %}{% cycle ab %}', {'a': '<', 'b': '>'}, '<>'), |             'cycle26': ('{% load cycle from future %}{% cycle a b as ab %}{% cycle ab %}', {'a': '<', 'b': '>'}, '<>'), | ||||||
|             'cycle27': ('{% load cycle from future %}{% autoescape off %}{% cycle a b as ab %}{% cycle ab %}{% endautoescape %}', {'a': '<', 'b': '>'}, '<>'), |             'cycle27': ('{% load cycle from future %}{% autoescape off %}{% cycle a b as ab %}{% cycle ab %}{% endautoescape %}', {'a': '<', 'b': '>'}, '<>'), | ||||||
| @@ -929,7 +929,7 @@ class TemplateTests(TestCase): | |||||||
|             'firstof07': ('{% firstof a b "c" %}', {'a': 0}, 'c'), |             'firstof07': ('{% firstof a b "c" %}', {'a': 0}, 'c'), | ||||||
|             'firstof08': ('{% firstof a b "c and d" %}', {'a': 0, 'b': 0}, 'c and d'), |             'firstof08': ('{% firstof a b "c and d" %}', {'a': 0, 'b': 0}, 'c and d'), | ||||||
|             'firstof09': ('{% firstof %}', {}, template.TemplateSyntaxError), |             'firstof09': ('{% firstof %}', {}, template.TemplateSyntaxError), | ||||||
|             'firstof10': ('{% firstof a %}', {'a': '<'}, '<'), |             'firstof10': ('{% firstof a %}', {'a': '<'}, '<'), | ||||||
|  |  | ||||||
|             'firstof11': ('{% load firstof from future %}{% firstof a b %}', {'a': '<', 'b': '>'}, '<'), |             'firstof11': ('{% load firstof from future %}{% firstof a b %}', {'a': '<', 'b': '>'}, '<'), | ||||||
|             'firstof12': ('{% load firstof from future %}{% firstof a b %}', {'a': '', 'b': '>'}, '>'), |             'firstof12': ('{% load firstof from future %}{% firstof a b %}', {'a': '', 'b': '>'}, '>'), | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user