Explorar el Código

Fixed #24046 -- Deprecated the "escape" half of utils.safestring.

Tim Graham hace 9 años
padre
commit
2f0e0eee45

+ 15 - 1
django/template/base.py

@@ -54,6 +54,7 @@ from __future__ import unicode_literals
 import inspect
 import inspect
 import logging
 import logging
 import re
 import re
+import warnings
 
 
 from django.template.context import (  # NOQA: imported for backwards compatibility
 from django.template.context import (  # NOQA: imported for backwards compatibility
     BaseContext, Context, ContextPopException, RequestContext,
     BaseContext, Context, ContextPopException, RequestContext,
@@ -722,6 +723,7 @@ class FilterExpression(object):
                         obj = string_if_invalid
                         obj = string_if_invalid
         else:
         else:
             obj = self.var
             obj = self.var
+        escape_isnt_last_filter = True
         for func, args in self.filters:
         for func, args in self.filters:
             arg_vals = []
             arg_vals = []
             for lookup, arg in args:
             for lookup, arg in args:
@@ -738,9 +740,21 @@ class FilterExpression(object):
             if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
             if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
                 obj = mark_safe(new_obj)
                 obj = mark_safe(new_obj)
             elif isinstance(obj, EscapeData):
             elif isinstance(obj, EscapeData):
-                obj = mark_for_escaping(new_obj)
+                with warnings.catch_warnings():
+                    # Ignore mark_for_escaping deprecation as this will be
+                    # removed in Django 2.0.
+                    warnings.simplefilter('ignore', category=RemovedInDjango20Warning)
+                    obj = mark_for_escaping(new_obj)
+                    escape_isnt_last_filter = False
             else:
             else:
                 obj = new_obj
                 obj = new_obj
+        if not escape_isnt_last_filter:
+            warnings.warn(
+                "escape isn't the last filter in %s and will be applied "
+                "immediately in Django 2.0 so the output may change."
+                % [func.__name__ for func, _ in self.filters],
+                RemovedInDjango20Warning, stacklevel=2
+            )
         return obj
         return obj
 
 
     def args_check(name, func, provided):
     def args_check(name, func, provided):

+ 7 - 1
django/template/defaultfilters.py

@@ -3,6 +3,7 @@ from __future__ import unicode_literals
 
 
 import random as random_module
 import random as random_module
 import re
 import re
+import warnings
 from decimal import ROUND_HALF_UP, Context, Decimal, InvalidOperation
 from decimal import ROUND_HALF_UP, Context, Decimal, InvalidOperation
 from functools import wraps
 from functools import wraps
 from operator import itemgetter
 from operator import itemgetter
@@ -10,6 +11,7 @@ from pprint import pformat
 
 
 from django.utils import formats, six
 from django.utils import formats, six
 from django.utils.dateformat import format, time_format
 from django.utils.dateformat import format, time_format
+from django.utils.deprecation import RemovedInDjango20Warning
 from django.utils.encoding import force_text, iri_to_uri
 from django.utils.encoding import force_text, iri_to_uri
 from django.utils.html import (
 from django.utils.html import (
     avoid_wrapping, conditional_escape, escape, escapejs, linebreaks,
     avoid_wrapping, conditional_escape, escape, escapejs, linebreaks,
@@ -439,7 +441,11 @@ def escape_filter(value):
     """
     """
     Marks the value as a string that should be auto-escaped.
     Marks the value as a string that should be auto-escaped.
     """
     """
-    return mark_for_escaping(value)
+    with warnings.catch_warnings():
+        # Ignore mark_for_escaping deprecation -- this will use
+        # conditional_escape() in Django 2.0.
+        warnings.simplefilter('ignore', category=RemovedInDjango20Warning)
+        return mark_for_escaping(value)
 
 
 
 
 @register.filter(is_safe=True)
 @register.filter(is_safe=True)

+ 4 - 0
django/utils/safestring.py

@@ -4,7 +4,10 @@ without further escaping in HTML. Marking something as a "safe string" means
 that the producer of the string has already turned characters that should not
 that the producer of the string has already turned characters that should not
 be interpreted by the HTML engine (e.g. '<') into the appropriate entities.
 be interpreted by the HTML engine (e.g. '<') into the appropriate entities.
 """
 """
+import warnings
+
 from django.utils import six
 from django.utils import six
+from django.utils.deprecation import RemovedInDjango20Warning
 from django.utils.functional import Promise, curry
 from django.utils.functional import Promise, curry
 
 
 
 
@@ -138,6 +141,7 @@ def mark_for_escaping(s):
     Can be called multiple times on a single string (the resulting escaping is
     Can be called multiple times on a single string (the resulting escaping is
     only applied once).
     only applied once).
     """
     """
+    warnings.warn('mark_for_escaping() is deprecated.', RemovedInDjango20Warning)
     if hasattr(s, '__html__') or isinstance(s, EscapeData):
     if hasattr(s, '__html__') or isinstance(s, EscapeData):
         return s
         return s
     if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes):
     if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes):

+ 0 - 9
docs/howto/custom-template-tags.txt

@@ -210,15 +210,6 @@ passed around inside the template code:
           # Do something with the "safe" string.
           # Do something with the "safe" string.
           ...
           ...
 
 
-* **Strings marked as "needing escaping"** are *always* escaped on
-  output, regardless of whether they are in an :ttag:`autoescape` block or
-  not. These strings are only escaped once, however, even if auto-escaping
-  applies.
-
-  Internally, these strings are of type ``EscapeBytes`` or
-  ``EscapeText``. Generally you don't have to worry about these; they
-  exist for the implementation of the :tfilter:`escape` filter.
-
 Template filter code falls into one of two situations:
 Template filter code falls into one of two situations:
 
 
 1. Your filter does not introduce any HTML-unsafe characters (``<``, ``>``,
 1. Your filter does not introduce any HTML-unsafe characters (``<``, ``>``,

+ 7 - 0
docs/internals/deprecation.txt

@@ -168,6 +168,13 @@ details on these changes.
 * ``FileField`` methods ``get_directory_name()`` and ``get_filename()`` will be
 * ``FileField`` methods ``get_directory_name()`` and ``get_filename()`` will be
   removed.
   removed.
 
 
+* The ``mark_for_escaping()`` function and the classes it uses: ``EscapeData``,
+  ``EscapeBytes``, ``EscapeText``, ``EscapeString``, and ``EscapeUnicode`` will
+  be removed.
+
+* The ``escape`` filter will change to use
+  ``django.utils.html.conditional_escape()``.
+
 .. _deprecation-removed-in-1.10:
 .. _deprecation-removed-in-1.10:
 
 
 1.10
 1.10

+ 6 - 0
docs/ref/templates/builtins.txt

@@ -1578,6 +1578,12 @@ For example, you can apply ``escape`` to fields when :ttag:`autoescape` is off::
         {{ title|escape }}
         {{ title|escape }}
     {% endautoescape %}
     {% endautoescape %}
 
 
+.. deprecated:: 1.10
+
+    The "lazy" behavior of the ``escape`` filter is deprecated. It will change
+    to immediately apply :func:`~django.utils.html.conditional_escape` in
+    Django 2.0.
+
 .. templatefilter:: escapejs
 .. templatefilter:: escapejs
 
 
 ``escapejs``
 ``escapejs``

+ 2 - 0
docs/ref/utils.txt

@@ -839,6 +839,8 @@ appropriate entities.
 
 
 .. function:: mark_for_escaping(s)
 .. function:: mark_for_escaping(s)
 
 
+    .. deprecated:: 1.10
+
     Explicitly mark a string as requiring HTML escaping upon output. Has no
     Explicitly mark a string as requiring HTML escaping upon output. Has no
     effect on ``SafeData`` subclasses.
     effect on ``SafeData`` subclasses.
 
 

+ 12 - 0
docs/releases/1.10.txt

@@ -1012,6 +1012,18 @@ This method must accept a :class:`~django.db.models.query.QuerySet` instance
 as its single argument and return a filtered version of the queryset for the
 as its single argument and return a filtered version of the queryset for the
 model instance the manager is bound to.
 model instance the manager is bound to.
 
 
+The "escape" half of ``django.utils.safestring``
+------------------------------------------------
+
+The ``mark_for_escaping()`` function and the classes it uses: ``EscapeData``,
+``EscapeBytes``, ``EscapeText``, ``EscapeString``, and ``EscapeUnicode`` are
+deprecated.
+
+As a result, the "lazy" behavior of the ``escape`` filter (where it would
+always be applied as the last filter no matter where in the filter chain it
+appeared) is deprecated. The filter will change to immediately apply
+:func:`~django.utils.html.conditional_escape` in Django 2.0.
+
 Miscellaneous
 Miscellaneous
 -------------
 -------------
 
 

+ 1 - 1
docs/releases/1.7.2.txt

@@ -177,7 +177,7 @@ Bugfixes
   setup (:ticket:`24000`).
   setup (:ticket:`24000`).
 
 
 * Restored support for objects that aren't :class:`str` or :class:`bytes` in
 * Restored support for objects that aren't :class:`str` or :class:`bytes` in
-  :func:`~django.utils.safestring.mark_for_escaping` on Python 3.
+  ``django.utils.safestring.mark_for_escaping()`` on Python 3.
 
 
 * Supported strings escaped by third-party libraries with the ``__html__``
 * Supported strings escaped by third-party libraries with the ``__html__``
   convention in the template engine (:ticket:`23831`).
   convention in the template engine (:ticket:`23831`).

+ 17 - 4
tests/template_tests/filter_tests/test_chaining.py

@@ -1,4 +1,7 @@
-from django.test import SimpleTestCase
+import warnings
+
+from django.test import SimpleTestCase, ignore_warnings
+from django.utils.deprecation import RemovedInDjango20Warning
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 
 
 from ..utils import setup
 from ..utils import setup
@@ -38,9 +41,19 @@ class ChainingTests(SimpleTestCase):
     # Using a filter that forces safeness does not lead to double-escaping
     # Using a filter that forces safeness does not lead to double-escaping
     @setup({'chaining05': '{{ a|escape|capfirst }}'})
     @setup({'chaining05': '{{ a|escape|capfirst }}'})
     def test_chaining05(self):
     def test_chaining05(self):
-        output = self.engine.render_to_string('chaining05', {'a': 'a < b'})
-        self.assertEqual(output, 'A &lt; b')
-
+        with warnings.catch_warnings(record=True) as warns:
+            warnings.simplefilter('always')
+            output = self.engine.render_to_string('chaining05', {'a': 'a < b'})
+            self.assertEqual(output, 'A &lt; b')
+
+        self.assertEqual(len(warns), 1)
+        self.assertEqual(
+            str(warns[0].message),
+            "escape isn't the last filter in ['escape_filter', 'capfirst'] and "
+            "will be applied immediately in Django 2.0 so the output may change."
+        )
+
+    @ignore_warnings(category=RemovedInDjango20Warning)
     @setup({'chaining06': '{% autoescape off %}{{ a|escape|capfirst }}{% endautoescape %}'})
     @setup({'chaining06': '{% autoescape off %}{{ a|escape|capfirst }}{% endautoescape %}'})
     def test_chaining06(self):
     def test_chaining06(self):
         output = self.engine.render_to_string('chaining06', {'a': 'a < b'})
         output = self.engine.render_to_string('chaining06', {'a': 'a < b'})

+ 5 - 2
tests/template_tests/filter_tests/test_escape.py

@@ -1,6 +1,7 @@
 from django.template.defaultfilters import escape
 from django.template.defaultfilters import escape
-from django.test import SimpleTestCase
+from django.test import SimpleTestCase, ignore_warnings
 from django.utils import six
 from django.utils import six
+from django.utils.deprecation import RemovedInDjango20Warning
 from django.utils.functional import Promise, lazy
 from django.utils.functional import Promise, lazy
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 
 
@@ -24,12 +25,14 @@ class EscapeTests(SimpleTestCase):
         self.assertEqual(output, "x&amp;y x&y")
         self.assertEqual(output, "x&amp;y x&y")
 
 
     # It is only applied once, regardless of the number of times it
     # It is only applied once, regardless of the number of times it
-    # appears in a chain.
+    # appears in a chain (to be changed in Django 2.0).
+    @ignore_warnings(category=RemovedInDjango20Warning)
     @setup({'escape03': '{% autoescape off %}{{ a|escape|escape }}{% endautoescape %}'})
     @setup({'escape03': '{% autoescape off %}{{ a|escape|escape }}{% endautoescape %}'})
     def test_escape03(self):
     def test_escape03(self):
         output = self.engine.render_to_string('escape03', {"a": "x&y"})
         output = self.engine.render_to_string('escape03', {"a": "x&y"})
         self.assertEqual(output, "x&amp;y")
         self.assertEqual(output, "x&amp;y")
 
 
+    @ignore_warnings(category=RemovedInDjango20Warning)
     @setup({'escape04': '{{ a|escape|escape }}'})
     @setup({'escape04': '{{ a|escape|escape }}'})
     def test_escape04(self):
     def test_escape04(self):
         output = self.engine.render_to_string('escape04', {"a": "x&y"})
         output = self.engine.render_to_string('escape04', {"a": "x&y"})

+ 6 - 2
tests/template_tests/filter_tests/test_force_escape.py

@@ -2,7 +2,8 @@
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
 from django.template.defaultfilters import force_escape
 from django.template.defaultfilters import force_escape
-from django.test import SimpleTestCase
+from django.test import SimpleTestCase, ignore_warnings
+from django.utils.deprecation import RemovedInDjango20Warning
 from django.utils.safestring import SafeData
 from django.utils.safestring import SafeData
 
 
 from ..utils import setup
 from ..utils import setup
@@ -35,7 +36,8 @@ class ForceEscapeTests(SimpleTestCase):
         self.assertEqual(output, "x&amp;amp;y")
         self.assertEqual(output, "x&amp;amp;y")
 
 
     # Because the result of force_escape is "safe", an additional
     # Because the result of force_escape is "safe", an additional
-    # escape filter has no effect.
+    # escape filter has no effect (to be changed in Django 2.0).
+    @ignore_warnings(category=RemovedInDjango20Warning)
     @setup({'force-escape05': '{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}'})
     @setup({'force-escape05': '{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}'})
     def test_force_escape05(self):
     def test_force_escape05(self):
         output = self.engine.render_to_string('force-escape05', {"a": "x&y"})
         output = self.engine.render_to_string('force-escape05', {"a": "x&y"})
@@ -46,11 +48,13 @@ class ForceEscapeTests(SimpleTestCase):
         output = self.engine.render_to_string('force-escape06', {"a": "x&y"})
         output = self.engine.render_to_string('force-escape06', {"a": "x&y"})
         self.assertEqual(output, "x&amp;y")
         self.assertEqual(output, "x&amp;y")
 
 
+    @ignore_warnings(category=RemovedInDjango20Warning)
     @setup({'force-escape07': '{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}'})
     @setup({'force-escape07': '{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}'})
     def test_force_escape07(self):
     def test_force_escape07(self):
         output = self.engine.render_to_string('force-escape07', {"a": "x&y"})
         output = self.engine.render_to_string('force-escape07', {"a": "x&y"})
         self.assertEqual(output, "x&amp;y")
         self.assertEqual(output, "x&amp;y")
 
 
+    @ignore_warnings(category=RemovedInDjango20Warning)
     @setup({'force-escape08': '{{ a|escape|force_escape }}'})
     @setup({'force-escape08': '{{ a|escape|force_escape }}'})
     def test_force_escape08(self):
     def test_force_escape08(self):
         output = self.engine.render_to_string('force-escape08', {"a": "x&y"})
         output = self.engine.render_to_string('force-escape08', {"a": "x&y"})

+ 6 - 1
tests/utils_tests/test_safestring.py

@@ -1,8 +1,9 @@
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
 from django.template import Context, Template
 from django.template import Context, Template
-from django.test import SimpleTestCase
+from django.test import SimpleTestCase, ignore_warnings
 from django.utils import html, six, text
 from django.utils import html, six, text
+from django.utils.deprecation import RemovedInDjango20Warning
 from django.utils.encoding import force_bytes
 from django.utils.encoding import force_bytes
 from django.utils.functional import lazy, lazystr
 from django.utils.functional import lazy, lazystr
 from django.utils.safestring import (
 from django.utils.safestring import (
@@ -62,11 +63,13 @@ class SafeStringTest(SimpleTestCase):
     def test_mark_safe_lazy_result_implements_dunder_html(self):
     def test_mark_safe_lazy_result_implements_dunder_html(self):
         self.assertEqual(mark_safe(lazystr('a&b')).__html__(), 'a&b')
         self.assertEqual(mark_safe(lazystr('a&b')).__html__(), 'a&b')
 
 
+    @ignore_warnings(category=RemovedInDjango20Warning)
     def test_mark_for_escaping(self):
     def test_mark_for_escaping(self):
         s = mark_for_escaping('a&b')
         s = mark_for_escaping('a&b')
         self.assertRenderEqual('{{ s }}', 'a&amp;b', s=s)
         self.assertRenderEqual('{{ s }}', 'a&amp;b', s=s)
         self.assertRenderEqual('{{ s }}', 'a&amp;b', s=mark_for_escaping(s))
         self.assertRenderEqual('{{ s }}', 'a&amp;b', s=mark_for_escaping(s))
 
 
+    @ignore_warnings(category=RemovedInDjango20Warning)
     def test_mark_for_escaping_object_implementing_dunder_html(self):
     def test_mark_for_escaping_object_implementing_dunder_html(self):
         e = customescape('<a&b>')
         e = customescape('<a&b>')
         s = mark_for_escaping(e)
         s = mark_for_escaping(e)
@@ -75,6 +78,7 @@ class SafeStringTest(SimpleTestCase):
         self.assertRenderEqual('{{ s }}', '<<a&b>>', s=s)
         self.assertRenderEqual('{{ s }}', '<<a&b>>', s=s)
         self.assertRenderEqual('{{ s|force_escape }}', '&lt;a&amp;b&gt;', s=s)
         self.assertRenderEqual('{{ s|force_escape }}', '&lt;a&amp;b&gt;', s=s)
 
 
+    @ignore_warnings(category=RemovedInDjango20Warning)
     def test_mark_for_escaping_lazy(self):
     def test_mark_for_escaping_lazy(self):
         s = lazystr('a&b')
         s = lazystr('a&b')
         b = lazybytes(b'a&b')
         b = lazybytes(b'a&b')
@@ -83,6 +87,7 @@ class SafeStringTest(SimpleTestCase):
         self.assertIsInstance(mark_for_escaping(b), EscapeData)
         self.assertIsInstance(mark_for_escaping(b), EscapeData)
         self.assertRenderEqual('{% autoescape off %}{{ s }}{% endautoescape %}', 'a&amp;b', s=mark_for_escaping(s))
         self.assertRenderEqual('{% autoescape off %}{{ s }}{% endautoescape %}', 'a&amp;b', s=mark_for_escaping(s))
 
 
+    @ignore_warnings(category=RemovedInDjango20Warning)
     def test_mark_for_escaping_object_implementing_dunder_str(self):
     def test_mark_for_escaping_object_implementing_dunder_str(self):
         class Obj(object):
         class Obj(object):
             def __str__(self):
             def __str__(self):