Refs #24046 -- Removed mark_for_escaping() per deprecation timeline.

This commit is contained in:
Tim Graham 2016-12-31 12:43:30 -05:00
parent 0dfc5479a8
commit 60ca37d2e5
11 changed files with 24 additions and 175 deletions

View File

@ -54,22 +54,18 @@ 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,
) )
from django.utils import six from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.encoding import ( from django.utils.encoding import (
force_str, force_text, python_2_unicode_compatible, force_str, force_text, python_2_unicode_compatible,
) )
from django.utils.formats import localize from django.utils.formats import localize
from django.utils.html import conditional_escape, escape from django.utils.html import conditional_escape, escape
from django.utils.inspect import getargspec from django.utils.inspect import getargspec
from django.utils.safestring import ( from django.utils.safestring import SafeData, mark_safe
EscapeData, SafeData, mark_for_escaping, mark_safe,
)
from django.utils.text import ( from django.utils.text import (
get_text_list, smart_split, unescape_string_literal, get_text_list, smart_split, unescape_string_literal,
) )
@ -713,7 +709,6 @@ 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:
@ -729,22 +724,8 @@ class FilterExpression(object):
new_obj = func(obj, *arg_vals) new_obj = func(obj, *arg_vals)
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):
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):
@ -1015,7 +996,7 @@ def render_value_in_context(value, context):
value = template_localtime(value, use_tz=context.use_tz) value = template_localtime(value, use_tz=context.use_tz)
value = localize(value, use_l10n=context.use_l10n) value = localize(value, use_l10n=context.use_l10n)
value = force_text(value) value = force_text(value)
if context.autoescape or isinstance(value, EscapeData): if context.autoescape:
return conditional_escape(value) return conditional_escape(value)
else: else:
return value return value

View File

@ -3,7 +3,6 @@ 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
@ -11,14 +10,13 @@ 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,
strip_tags, urlize as _urlize, strip_tags, urlize as _urlize,
) )
from django.utils.http import urlquote from django.utils.http import urlquote
from django.utils.safestring import SafeData, mark_for_escaping, mark_safe from django.utils.safestring import SafeData, mark_safe
from django.utils.text import ( from django.utils.text import (
Truncator, normalize_newlines, phone2numeric, slugify as _slugify, wrap, Truncator, normalize_newlines, phone2numeric, slugify as _slugify, wrap,
) )
@ -442,11 +440,7 @@ 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.
""" """
with warnings.catch_warnings(): return conditional_escape(value)
# 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)

View File

@ -4,39 +4,11 @@ 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, wraps from django.utils.functional import Promise, curry, wraps
class EscapeData(object):
pass
class EscapeBytes(bytes, EscapeData):
"""
A byte string that should be HTML-escaped when output.
"""
pass
class EscapeText(six.text_type, EscapeData):
"""
A unicode string object that should be HTML-escaped when output.
"""
pass
if six.PY3:
EscapeString = EscapeText
else:
EscapeString = EscapeBytes
# backwards compatibility for Python 2
EscapeUnicode = EscapeText
class SafeData(object): class SafeData(object):
def __html__(self): def __html__(self):
""" """
@ -144,21 +116,3 @@ def mark_safe(s):
if callable(s): if callable(s):
return _safety_decorator(mark_safe, s) return _safety_decorator(mark_safe, s)
return SafeString(str(s)) return SafeString(str(s))
def mark_for_escaping(s):
"""
Explicitly mark a string as requiring HTML escaping upon output. Has no
effect on SafeData subclasses.
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):
return EscapeBytes(s)
if isinstance(s, (six.text_type, Promise)):
return EscapeText(s)
return EscapeString(str(s))

View File

@ -1636,11 +1636,6 @@ Escapes a string's HTML. Specifically, it makes these replacements:
* ``"`` (double quote) is converted to ``&quot;`` * ``"`` (double quote) is converted to ``&quot;``
* ``&`` is converted to ``&amp;`` * ``&`` is converted to ``&amp;``
The escaping is only applied when the string is output, so it does not matter
where in a chained sequence of filters you put ``escape``: it will always be
applied as though it were the last filter. If you want escaping to be applied
immediately, use the :tfilter:`force_escape` filter.
Applying ``escape`` to a variable that would normally have auto-escaping Applying ``escape`` to a variable that would normally have auto-escaping
applied to the result will only result in one round of escaping being done. So applied to the result will only result in one round of escaping being done. So
it is safe to use this function even in auto-escaping environments. If you want it is safe to use this function even in auto-escaping environments. If you want
@ -1652,12 +1647,6 @@ 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``

View File

@ -841,16 +841,6 @@ appropriate entities.
Added support for decorator usage. Added support for decorator usage.
.. function:: mark_for_escaping(s)
.. deprecated:: 1.10
Explicitly mark a string as requiring HTML escaping upon output. Has no
effect on ``SafeData`` subclasses.
Can be called multiple times on a single string (the resulting escaping is
only applied once).
``django.utils.text`` ``django.utils.text``
===================== =====================

View File

@ -378,3 +378,9 @@ these features.
* ``FileField`` methods ``get_directory_name()`` and ``get_filename()`` are * ``FileField`` methods ``get_directory_name()`` and ``get_filename()`` are
removed. removed.
* The ``mark_for_escaping()`` function and the classes it uses: ``EscapeData``,
``EscapeBytes``, ``EscapeText``, ``EscapeString``, and ``EscapeUnicode`` are
removed.
* The ``escape`` filter now uses ``django.utils.html.conditional_escape()``.

View File

@ -112,22 +112,18 @@ For forwards compatibility, the new names work as of Django 1.4.2.
information. information.
:mod:`django.utils.safestring` is mostly used via the :mod:`django.utils.safestring` is mostly used via the
:func:`~django.utils.safestring.mark_safe` and :func:`~django.utils.safestring.mark_safe` function, which didn't change. In
:func:`~django.utils.safestring.mark_for_escaping` functions, which didn't case you're using the internals, here are the name changes:
change. In case you're using the internals, here are the name changes:
================== ================== ================== ==================
Old name New name Old name New name
================== ================== ================== ==================
``EscapeString`` ``EscapeBytes``
``EscapeUnicode`` ``EscapeText``
``SafeString`` ``SafeBytes`` ``SafeString`` ``SafeBytes``
``SafeUnicode`` ``SafeText`` ``SafeUnicode`` ``SafeText``
================== ================== ================== ==================
For backwards compatibility, the old names still work on Python 2. Under For backwards compatibility, the old names still work on Python 2. On Python 3,
Python 3, ``EscapeString`` and ``SafeString`` are aliases for ``EscapeText`` ``SafeString`` is an alias for ``SafeText``.
and ``SafeText`` respectively.
For forwards compatibility, the new names work as of Django 1.4.2. For forwards compatibility, the new names work as of Django 1.4.2.

View File

@ -1,8 +1,4 @@
import warnings from django.test import SimpleTestCase
from django.test import SimpleTestCase, ignore_warnings
from django.test.utils import reset_warning_registry
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
@ -42,20 +38,9 @@ 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):
reset_warning_registry()
with warnings.catch_warnings(record=True) as warns:
warnings.simplefilter('always')
output = self.engine.render_to_string('chaining05', {'a': 'a < b'}) output = self.engine.render_to_string('chaining05', {'a': 'a < b'})
self.assertEqual(output, 'A &lt; 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'})

View File

@ -1,7 +1,6 @@
from django.template.defaultfilters import escape from django.template.defaultfilters import escape
from django.test import SimpleTestCase, ignore_warnings from django.test import SimpleTestCase
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,15 +23,11 @@ class EscapeTests(SimpleTestCase):
output = self.engine.render_to_string('escape02', {"a": "x&y", "b": mark_safe("x&y")}) output = self.engine.render_to_string('escape02', {"a": "x&y", "b": mark_safe("x&y")})
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
# 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"})

View File

@ -2,8 +2,7 @@
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, ignore_warnings from django.test import SimpleTestCase
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
@ -36,8 +35,7 @@ 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 (to be changed in Django 2.0). # escape filter has no effect.
@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"})
@ -48,17 +46,15 @@ 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;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"})
self.assertEqual(output, "x&amp;y") self.assertEqual(output, "x&amp;amp;y")
class FunctionTests(SimpleTestCase): class FunctionTests(SimpleTestCase):

View File

@ -1,14 +1,11 @@
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, ignore_warnings from django.test import SimpleTestCase
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 SafeData, mark_safe
EscapeData, SafeData, mark_for_escaping, mark_safe,
)
lazybytes = lazy(force_bytes, bytes) lazybytes = lazy(force_bytes, bytes)
@ -63,40 +60,6 @@ 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):
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)
self.assertIs(s, e)
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')
self.assertIsInstance(mark_for_escaping(s), EscapeData)
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):
return '<obj>'
s = mark_for_escaping(Obj())
self.assertRenderEqual('{{ s }}', '&lt;obj&gt;', s=s)
def test_add_lazy_safe_text_and_safe_text(self): def test_add_lazy_safe_text_and_safe_text(self):
s = html.escape(lazystr('a')) s = html.escape(lazystr('a'))
s += mark_safe('&b') s += mark_safe('&b')