Преглед изворни кода

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

Tim Graham пре 9 година
родитељ
комит
2f0e0eee45

+ 15 - 1
django/template/base.py

@@ -54,6 +54,7 @@ from __future__ import unicode_literals
 import inspect
 import logging
 import re
+import warnings
 
 from django.template.context import (  # NOQA: imported for backwards compatibility
     BaseContext, Context, ContextPopException, RequestContext,
@@ -722,6 +723,7 @@ class FilterExpression(object):
                         obj = string_if_invalid
         else:
             obj = self.var
+        escape_isnt_last_filter = True
         for func, args in self.filters:
             arg_vals = []
             for lookup, arg in args:
@@ -738,9 +740,21 @@ class FilterExpression(object):
             if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
                 obj = mark_safe(new_obj)
             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:
                 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
 
     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 re
+import warnings
 from decimal import ROUND_HALF_UP, Context, Decimal, InvalidOperation
 from functools import wraps
 from operator import itemgetter
@@ -10,6 +11,7 @@ from pprint import pformat
 
 from django.utils import formats, six
 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.html import (
     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.
     """
-    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)

+ 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
 be interpreted by the HTML engine (e.g. '<') into the appropriate entities.
 """
+import warnings
+
 from django.utils import six
+from django.utils.deprecation import RemovedInDjango20Warning
 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
     only applied once).
     """
+    warnings.warn('mark_for_escaping() is deprecated.', RemovedInDjango20Warning)
     if hasattr(s, '__html__') or isinstance(s, EscapeData):
         return s
     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.
           ...
 
-* **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:
 
 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
   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:
 
 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 }}
     {% 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
 
 ``escapejs``

+ 2 - 0
docs/ref/utils.txt

@@ -839,6 +839,8 @@ appropriate entities.
 
 .. function:: mark_for_escaping(s)
 
+    .. deprecated:: 1.10
+
     Explicitly mark a string as requiring HTML escaping upon output. Has no
     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
 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
 -------------
 

+ 1 - 1
docs/releases/1.7.2.txt

@@ -177,7 +177,7 @@ Bugfixes
   setup (:ticket:`24000`).
 
 * 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__``
   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 ..utils import setup
@@ -38,9 +41,19 @@ class ChainingTests(SimpleTestCase):
     # Using a filter that forces safeness does not lead to double-escaping
     @setup({'chaining05': '{{ a|escape|capfirst }}'})
     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 %}'})
     def test_chaining06(self):
         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.test import SimpleTestCase
+from django.test import SimpleTestCase, ignore_warnings
 from django.utils import six
+from django.utils.deprecation import RemovedInDjango20Warning
 from django.utils.functional import Promise, lazy
 from django.utils.safestring import mark_safe
 
@@ -24,12 +25,14 @@ class EscapeTests(SimpleTestCase):
         self.assertEqual(output, "x&amp;y x&y")
 
     # 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 %}'})
     def test_escape03(self):
         output = self.engine.render_to_string('escape03', {"a": "x&y"})
         self.assertEqual(output, "x&amp;y")
 
+    @ignore_warnings(category=RemovedInDjango20Warning)
     @setup({'escape04': '{{ a|escape|escape }}'})
     def test_escape04(self):
         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 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 ..utils import setup
@@ -35,7 +36,8 @@ class ForceEscapeTests(SimpleTestCase):
         self.assertEqual(output, "x&amp;amp;y")
 
     # 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 %}'})
     def test_force_escape05(self):
         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"})
         self.assertEqual(output, "x&amp;y")
 
+    @ignore_warnings(category=RemovedInDjango20Warning)
     @setup({'force-escape07': '{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}'})
     def test_force_escape07(self):
         output = self.engine.render_to_string('force-escape07', {"a": "x&y"})
         self.assertEqual(output, "x&amp;y")
 
+    @ignore_warnings(category=RemovedInDjango20Warning)
     @setup({'force-escape08': '{{ a|escape|force_escape }}'})
     def test_force_escape08(self):
         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 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.deprecation import RemovedInDjango20Warning
 from django.utils.encoding import force_bytes
 from django.utils.functional import lazy, lazystr
 from django.utils.safestring import (
@@ -62,11 +63,13 @@ class SafeStringTest(SimpleTestCase):
     def test_mark_safe_lazy_result_implements_dunder_html(self):
         self.assertEqual(mark_safe(lazystr('a&b')).__html__(), 'a&b')
 
+    @ignore_warnings(category=RemovedInDjango20Warning)
     def test_mark_for_escaping(self):
         s = mark_for_escaping('a&b')
         self.assertRenderEqual('{{ s }}', 'a&amp;b', s=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):
         e = customescape('<a&b>')
         s = mark_for_escaping(e)
@@ -75,6 +78,7 @@ class SafeStringTest(SimpleTestCase):
         self.assertRenderEqual('{{ s }}', '<<a&b>>', 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):
         s = lazystr('a&b')
         b = lazybytes(b'a&b')
@@ -83,6 +87,7 @@ class SafeStringTest(SimpleTestCase):
         self.assertIsInstance(mark_for_escaping(b), EscapeData)
         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):
         class Obj(object):
             def __str__(self):