1
0
mirror of https://github.com/django/django.git synced 2024-12-22 09:05:43 +00:00

Fixed #20223 -- Added keep_lazy() as a replacement for allow_lazy().

Thanks to bmispelon and uruz for the initial patch.
This commit is contained in:
Iacopo Spalletti 2015-11-07 14:30:20 +01:00 committed by Tim Graham
parent 93fc23b2d5
commit d693074d43
21 changed files with 237 additions and 59 deletions

View File

@ -32,6 +32,7 @@ answer newbie questions, and generally made Django that much better:
Alex Hill <alex@hill.net.au>
Alex Ogier <alex.ogier@gmail.com>
Alex Robbins <alexander.j.robbins@gmail.com>
Alexey Boriskin <alex@boriskin.me>
Aljosa Mohorovic <aljosa.mohorovic@gmail.com>
Amit Chakradeo <http://amit.chakradeo.net/>
Amit Ramon <amit.ramon@gmail.com>
@ -287,6 +288,7 @@ answer newbie questions, and generally made Django that much better:
Honza Král <honza.kral@gmail.com>
Horst Gutmann <zerok@zerokspot.com>
Hyun Mi Ae
Iacopo Spalletti <i.spalletti@nephila.it>
Ian A Wilson <http://ianawilson.com>
Ian Clelland <clelland@gmail.com>
Ian G. Kelly <ian.g.kelly@gmail.com>

View File

@ -1,8 +1,10 @@
import copy
import operator
import warnings
from functools import total_ordering, wraps
from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning
# You can't trivially replace this with `functools.partial` because this binds
@ -176,24 +178,52 @@ def _lazy_proxy_unpickle(func, args, kwargs, *resultclasses):
return lazy(func, *resultclasses)(*args, **kwargs)
def lazystr(text):
"""
Shortcut for the common case of a lazy callable that returns str.
"""
from django.utils.encoding import force_text # Avoid circular import
return lazy(force_text, six.text_type)(text)
def allow_lazy(func, *resultclasses):
warnings.warn(
"django.utils.functional.allow_lazy() is deprecated in favor of "
"django.utils.functional.keep_lazy()",
RemovedInDjango20Warning, 2)
return keep_lazy(*resultclasses)(func)
def keep_lazy(*resultclasses):
"""
A decorator that allows a function to be called with one or more lazy
arguments. If none of the args are lazy, the function is evaluated
immediately, otherwise a __proxy__ is returned that will evaluate the
function when needed.
"""
lazy_func = lazy(func, *resultclasses)
if not resultclasses:
raise TypeError("You must pass at least one argument to keep_lazy().")
@wraps(func)
def wrapper(*args, **kwargs):
for arg in list(args) + list(kwargs.values()):
if isinstance(arg, Promise):
break
else:
return func(*args, **kwargs)
return lazy_func(*args, **kwargs)
return wrapper
def decorator(func):
lazy_func = lazy(func, *resultclasses)
@wraps(func)
def wrapper(*args, **kwargs):
for arg in list(args) + list(six.itervalues(kwargs)):
if isinstance(arg, Promise):
break
else:
return func(*args, **kwargs)
return lazy_func(*args, **kwargs)
return wrapper
return decorator
def keep_lazy_text(func):
"""
A decorator for functions that accept lazy arguments and return text.
"""
return keep_lazy(six.text_type)(func)
empty = object()

View File

@ -6,7 +6,7 @@ import re
from django.utils import six
from django.utils.encoding import force_str, force_text
from django.utils.functional import allow_lazy
from django.utils.functional import keep_lazy, keep_lazy_text
from django.utils.http import RFC3986_GENDELIMS, RFC3986_SUBDELIMS
from django.utils.safestring import SafeData, SafeText, mark_safe
from django.utils.six.moves.urllib.parse import (
@ -38,6 +38,7 @@ hard_coded_bullets_re = re.compile(
trailing_empty_content_re = re.compile(r'(?:<p>(?:&nbsp;|\s|<br \/>)*?</p>\s*)+\Z')
@keep_lazy(six.text_type, SafeText)
def escape(text):
"""
Returns the given text with ampersands, quotes and angle brackets encoded
@ -49,7 +50,6 @@ def escape(text):
"""
return mark_safe(force_text(text).replace('&', '&amp;').replace('<', '&lt;')
.replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;'))
escape = allow_lazy(escape, six.text_type, SafeText)
_js_escapes = {
ord('\\'): '\\u005C',
@ -69,10 +69,10 @@ _js_escapes = {
_js_escapes.update((ord('%c' % z), '\\u%04X' % z) for z in range(32))
@keep_lazy(six.text_type, SafeText)
def escapejs(value):
"""Hex encodes characters for use in JavaScript strings."""
return mark_safe(force_text(value).translate(_js_escapes))
escapejs = allow_lazy(escapejs, six.text_type, SafeText)
def conditional_escape(text):
@ -118,16 +118,16 @@ def format_html_join(sep, format_string, args_generator):
for args in args_generator))
@keep_lazy_text
def linebreaks(value, autoescape=False):
"""Converts newlines into <p> and <br />s."""
value = normalize_newlines(value)
value = normalize_newlines(force_text(value))
paras = re.split('\n{2,}', value)
if autoescape:
paras = ['<p>%s</p>' % escape(p).replace('\n', '<br />') for p in paras]
else:
paras = ['<p>%s</p>' % p.replace('\n', '<br />') for p in paras]
return '\n\n'.join(paras)
linebreaks = allow_lazy(linebreaks, six.text_type)
class MLStripper(HTMLParser):
@ -166,10 +166,12 @@ def _strip_once(value):
return s.get_data()
@keep_lazy_text
def strip_tags(value):
"""Returns the given HTML with all tags stripped."""
# Note: in typical case this loop executes _strip_once once. Loop condition
# is redundant, but helps to reduce number of executions of _strip_once.
value = force_text(value)
while '<' in value and '>' in value:
new_value = _strip_once(value)
if len(new_value) >= len(value):
@ -179,13 +181,12 @@ def strip_tags(value):
break
value = new_value
return value
strip_tags = allow_lazy(strip_tags)
@keep_lazy_text
def strip_spaces_between_tags(value):
"""Returns the given HTML with spaces between tags removed."""
return re.sub(r'>\s+<', '><', force_text(value))
strip_spaces_between_tags = allow_lazy(strip_spaces_between_tags, six.text_type)
def smart_urlquote(url):
@ -224,6 +225,7 @@ def smart_urlquote(url):
return urlunsplit((scheme, netloc, path, query, fragment))
@keep_lazy_text
def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
"""
Converts any URLs in text into clickable links.
@ -321,7 +323,6 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
elif autoescape:
words[i] = escape(word)
return ''.join(words)
urlize = allow_lazy(urlize, six.text_type)
def avoid_wrapping(value):

View File

@ -12,7 +12,7 @@ from email.utils import formatdate
from django.utils import six
from django.utils.datastructures import MultiValueDict
from django.utils.encoding import force_bytes, force_str, force_text
from django.utils.functional import allow_lazy
from django.utils.functional import keep_lazy_text
from django.utils.six.moves.urllib.parse import (
quote, quote_plus, unquote, unquote_plus, urlencode as original_urlencode,
urlparse,
@ -40,6 +40,7 @@ PROTOCOL_TO_PORT = {
}
@keep_lazy_text
def urlquote(url, safe='/'):
"""
A version of Python's urllib.quote() function that can operate on unicode
@ -48,9 +49,9 @@ def urlquote(url, safe='/'):
without double-quoting occurring.
"""
return force_text(quote(force_str(url), force_str(safe)))
urlquote = allow_lazy(urlquote, six.text_type)
@keep_lazy_text
def urlquote_plus(url, safe=''):
"""
A version of Python's urllib.quote_plus() function that can operate on
@ -59,25 +60,24 @@ def urlquote_plus(url, safe=''):
iri_to_uri() call without double-quoting occurring.
"""
return force_text(quote_plus(force_str(url), force_str(safe)))
urlquote_plus = allow_lazy(urlquote_plus, six.text_type)
@keep_lazy_text
def urlunquote(quoted_url):
"""
A wrapper for Python's urllib.unquote() function that can operate on
the result of django.utils.http.urlquote().
"""
return force_text(unquote(force_str(quoted_url)))
urlunquote = allow_lazy(urlunquote, six.text_type)
@keep_lazy_text
def urlunquote_plus(quoted_url):
"""
A wrapper for Python's urllib.unquote_plus() function that can operate on
the result of django.utils.http.urlquote_plus().
"""
return force_text(unquote_plus(force_str(quoted_url)))
urlunquote_plus = allow_lazy(urlunquote_plus, six.text_type)
def urlencode(query, doseq=0):

View File

@ -7,7 +7,7 @@ from io import BytesIO
from django.utils import six
from django.utils.encoding import force_text
from django.utils.functional import SimpleLazyObject, allow_lazy
from django.utils.functional import SimpleLazyObject, keep_lazy, keep_lazy_text
from django.utils.safestring import SafeText, mark_safe
from django.utils.six.moves import html_entities
from django.utils.translation import pgettext, ugettext as _, ugettext_lazy
@ -20,7 +20,7 @@ if six.PY2:
# Capitalizes the first letter of a string.
capfirst = lambda x: x and force_text(x)[0].upper() + force_text(x)[1:]
capfirst = allow_lazy(capfirst, six.text_type)
capfirst = keep_lazy_text(capfirst)
# Set up regular expressions
re_words = re.compile(r'<.*?>|((?:\w[-\w]*|&.*?;)+)', re.U | re.S)
@ -30,6 +30,7 @@ re_newlines = re.compile(r'\r\n|\r') # Used in normalize_newlines
re_camel_case = re.compile(r'(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
@keep_lazy_text
def wrap(text, width):
"""
A word-wrap function that preserves existing line breaks. Expects that
@ -60,7 +61,6 @@ def wrap(text, width):
if line:
yield line
return ''.join(_generator())
wrap = allow_lazy(wrap, six.text_type)
class Truncator(SimpleLazyObject):
@ -95,6 +95,7 @@ class Truncator(SimpleLazyObject):
string has been truncated, defaulting to a translatable string of an
ellipsis (...).
"""
self._setup()
length = int(num)
text = unicodedata.normalize('NFC', self._wrapped)
@ -108,7 +109,6 @@ class Truncator(SimpleLazyObject):
if html:
return self._truncate_html(length, truncate, text, truncate_len, False)
return self._text_chars(length, truncate, text, truncate_len)
chars = allow_lazy(chars)
def _text_chars(self, length, truncate, text, truncate_len):
"""
@ -138,11 +138,11 @@ class Truncator(SimpleLazyObject):
argument of what should be used to notify that the string has been
truncated, defaulting to ellipsis (...).
"""
self._setup()
length = int(num)
if html:
return self._truncate_html(length, truncate, self._wrapped, length, True)
return self._text_words(length, truncate)
words = allow_lazy(words)
def _text_words(self, length, truncate):
"""
@ -229,6 +229,7 @@ class Truncator(SimpleLazyObject):
return out
@keep_lazy_text
def get_valid_filename(s):
"""
Returns the given string converted to a string that can be used for a clean
@ -240,9 +241,9 @@ def get_valid_filename(s):
"""
s = force_text(s).strip().replace(' ', '_')
return re.sub(r'(?u)[^-\w.]', '', s)
get_valid_filename = allow_lazy(get_valid_filename, six.text_type)
@keep_lazy_text
def get_text_list(list_, last_word=ugettext_lazy('or')):
"""
>>> get_text_list(['a', 'b', 'c', 'd'])
@ -264,16 +265,16 @@ def get_text_list(list_, last_word=ugettext_lazy('or')):
# Translators: This string is used as a separator between list elements
_(', ').join(force_text(i) for i in list_[:-1]),
force_text(last_word), force_text(list_[-1]))
get_text_list = allow_lazy(get_text_list, six.text_type)
@keep_lazy_text
def normalize_newlines(text):
"""Normalizes CRLF and CR newlines to just LF."""
text = force_text(text)
return re_newlines.sub('\n', text)
normalize_newlines = allow_lazy(normalize_newlines, six.text_type)
@keep_lazy_text
def phone2numeric(phone):
"""Converts a phone number with letters into its numeric equivalent."""
char2number = {'a': '2', 'b': '2', 'c': '2', 'd': '3', 'e': '3', 'f': '3',
@ -281,7 +282,6 @@ def phone2numeric(phone):
'n': '6', 'o': '6', 'p': '7', 'q': '7', 'r': '7', 's': '7', 't': '8',
'u': '8', 'v': '8', 'w': '9', 'x': '9', 'y': '9', 'z': '9'}
return ''.join(char2number.get(c, c) for c in phone.lower())
phone2numeric = allow_lazy(phone2numeric)
# From http://www.xhaus.com/alan/python/httpcomp.html#gzip
@ -384,11 +384,12 @@ def _replace_entity(match):
_entity_re = re.compile(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));")
@keep_lazy_text
def unescape_entities(text):
return _entity_re.sub(_replace_entity, text)
unescape_entities = allow_lazy(unescape_entities, six.text_type)
return _entity_re.sub(_replace_entity, force_text(text))
@keep_lazy_text
def unescape_string_literal(s):
r"""
Convert quoted string literals to unquoted strings with escaped quotes and
@ -407,9 +408,9 @@ def unescape_string_literal(s):
raise ValueError("Not a string literal: %r" % s)
quote = s[0]
return s[1:-1].replace(r'\%s' % quote, quote).replace(r'\\', '\\')
unescape_string_literal = allow_lazy(unescape_string_literal)
@keep_lazy(six.text_type, SafeText)
def slugify(value, allow_unicode=False):
"""
Convert to ASCII if 'allow_unicode' is False. Convert spaces to hyphens.
@ -424,7 +425,6 @@ def slugify(value, allow_unicode=False):
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
value = re.sub('[^\w\s-]', '', value).strip().lower()
return mark_safe(re.sub('[-\s]+', '-', value))
slugify = allow_lazy(slugify, six.text_type, SafeText)
def camel_case_to_spaces(value):

View File

@ -124,6 +124,8 @@ details on these changes.
* The ``cascaded_union`` property of ``django.contrib.gis.geos.MultiPolygon``
will be removed.
* ``django.utils.functional.allow_lazy()`` will be removed.
.. _deprecation-removed-in-1.10:
1.10

View File

@ -522,6 +522,15 @@ Atom1Feed
.. function:: allow_lazy(func, *resultclasses)
.. deprecated:: 1.10
Works like :meth:`~django.utils.functional.keep_lazy` except that it can't
be used as a decorator.
.. function:: keep_lazy(func, *resultclasses)
.. versionadded:: 1.10
Django offers many utility functions (particularly in ``django.utils``)
that take a string as their first argument and do something to that string.
These functions are used by template filters as well as directly in other
@ -533,31 +542,58 @@ Atom1Feed
because you might be using this function outside of a view (and hence the
current thread's locale setting will not be correct).
For cases like this, use the ``django.utils.functional.allow_lazy()``
For cases like this, use the ``django.utils.functional.keep_lazy()``
decorator. It modifies the function so that *if* it's called with a lazy
translation as one of its arguments, the function evaluation is delayed
until it needs to be converted to a string.
For example::
from django.utils.functional import allow_lazy
from django.utils import six
from django.utils.functional import keep_lazy, keep_lazy_text
def fancy_utility_function(s, ...):
# Do some conversion on string 's'
...
# Replace unicode by str on Python 3
fancy_utility_function = allow_lazy(fancy_utility_function, unicode)
fancy_utility_function = keep_lazy(six.text_type)(fancy_utility_function)
The ``allow_lazy()`` decorator takes, in addition to the function to
decorate, a number of extra arguments (``*args``) specifying the type(s)
that the original function can return. Usually, it's enough to include
``unicode`` (or ``str`` on Python 3) here and ensure that your function
returns only Unicode strings.
# Or more succinctly:
@keep_lazy(six.text_type)
def fancy_utility_function(s, ...):
...
The ``keep_lazy()`` decorator takes a number of extra arguments (``*args``)
specifying the type(s) that the original function can return. A common
use case is to have functions that return text. For these, you can just
pass the ``six.text_type`` type to ``keep_lazy`` (or even simpler, use the
:func:`keep_lazy_text` decorator described in the next section).
Using this decorator means you can write your function and assume that the
input is a proper string, then add support for lazy translation objects at
the end.
.. function:: keep_lazy_text(func)
.. versionadded:: 1.10
A shortcut for ``keep_lazy(six.text_type)(func)``.
If you have a function that returns text and you want to be able to take
lazy arguments while delaying their evaluation, simply use this decorator::
from django.utils import six
from django.utils.functional import keep_lazy, keep_lazy_text
# Our previous example was:
@keep_lazy(six.text_type)
def fancy_utility_function(s, ...):
...
# Which can be rewritten as:
@keep_lazy_text
def fancy_utility_function(s, ...):
...
``django.utils.html``
=====================

View File

@ -402,6 +402,10 @@ Miscellaneous
* The ``makemigrations --exit`` option is deprecated in favor of the
:djadminopt:`--check` option.
* ``django.utils.functional.allow_lazy()`` is deprecated in favor of the new
:func:`~django.utils.functional.keep_lazy` function which can be used with a
more natural decorator syntax.
.. _removed-features-1.10:
Features removed in 1.10

View File

@ -223,7 +223,7 @@ QuerySet <when-querysets-are-evaluated>`. Avoiding the premature evaluation of
a ``QuerySet`` can save making an expensive and unnecessary trip to the
database.
Django also offers an :meth:`~django.utils.functional.allow_lazy` decorator.
Django also offers a :meth:`~django.utils.functional.keep_lazy` decorator.
This allows a function that has been called with a lazy argument to behave
lazily itself, only being evaluated when it needs to be. Thus the lazy argument
- which could be an expensive one - will not be called upon for evaluation

View File

@ -8,8 +8,12 @@ from django.contrib.auth.decorators import (
from django.http import HttpRequest, HttpResponse, HttpResponseNotAllowed
from django.middleware.clickjacking import XFrameOptionsMiddleware
from django.test import SimpleTestCase
from django.utils import six
from django.utils.decorators import method_decorator
from django.utils.functional import allow_lazy, lazy
from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.encoding import force_text
from django.utils.functional import allow_lazy, keep_lazy, keep_lazy_text, lazy
from django.utils.translation import ugettext_lazy
from django.views.decorators.cache import (
cache_control, cache_page, never_cache,
)
@ -67,7 +71,8 @@ full_decorator = compose(
staff_member_required,
# django.utils.functional
allow_lazy,
keep_lazy(HttpResponse),
keep_lazy_text,
lazy,
)
@ -149,6 +154,15 @@ class DecoratorsTest(TestCase):
request.method = 'DELETE'
self.assertIsInstance(my_safe_view(request), HttpResponseNotAllowed)
def test_deprecated_allow_lazy(self):
with self.assertRaises(RemovedInDjango20Warning):
def noop_text(text):
return force_text(text)
noop_text = allow_lazy(noop_text, six.text_type)
rendered = noop_text(ugettext_lazy("I am a text"))
self.assertEqual(type(rendered), six.text_type)
self.assertEqual(rendered, "I am a text")
# For testing method_decorator, a decorator that assumes a single argument.
# We will get type arguments if there is a mismatch in the number of arguments.

View File

@ -21,10 +21,8 @@ from django.http import (
from django.test import SimpleTestCase
from django.utils import six
from django.utils._os import upath
from django.utils.encoding import force_text, smart_str
from django.utils.functional import lazy
lazystr = lazy(force_text, six.text_type)
from django.utils.encoding import smart_str
from django.utils.functional import lazystr
class QueryDictTests(unittest.TestCase):

View File

@ -1,5 +1,7 @@
from django.template.defaultfilters import escape
from django.test import SimpleTestCase
from django.utils import six
from django.utils.functional import Promise, lazy
from django.utils.safestring import mark_safe
from ..utils import setup
@ -33,6 +35,12 @@ class EscapeTests(SimpleTestCase):
output = self.engine.render_to_string('escape04', {"a": "x&y"})
self.assertEqual(output, "x&amp;y")
def test_escape_lazy_string(self):
add_html = lazy(lambda string: string + 'special characters > here', six.text_type)
escaped = escape(add_html('<some html & '))
self.assertIsInstance(escaped, Promise)
self.assertEqual(escaped, '&lt;some html &amp; special characters &gt; here')
class FunctionTests(SimpleTestCase):

View File

@ -2,6 +2,8 @@ from __future__ import unicode_literals
from django.template.defaultfilters import escapejs_filter
from django.test import SimpleTestCase
from django.utils import six
from django.utils.functional import lazy
from ..utils import setup
@ -51,3 +53,11 @@ class FunctionTests(SimpleTestCase):
escapejs_filter('paragraph separator:\u2029and line separator:\u2028'),
'paragraph separator:\\u2029and line separator:\\u2028',
)
def test_lazy_string(self):
append_script = lazy(lambda string: r'<script>this</script>' + string, six.text_type)
self.assertEqual(
escapejs_filter(append_script('whitespace: \r\n\t\v\f\b')),
'\\u003Cscript\\u003Ethis\\u003C/script\\u003E'
'whitespace: \\u000D\\u000A\\u0009\\u000B\\u000C\\u0008'
)

View File

@ -1,5 +1,7 @@
from django.template.defaultfilters import linebreaks_filter
from django.test import SimpleTestCase
from django.utils import six
from django.utils.functional import lazy
from django.utils.safestring import mark_safe
from ..utils import setup
@ -51,3 +53,10 @@ class FunctionTests(SimpleTestCase):
linebreaks_filter('foo\n<a>bar</a>\nbuz', autoescape=False),
'<p>foo<br /><a>bar</a><br />buz</p>',
)
def test_lazy_string_input(self):
add_header = lazy(lambda string: 'Header\n\n' + string, six.text_type)
self.assertEqual(
linebreaks_filter(add_header('line 1\r\nline2')),
'<p>Header</p>\n\n<p>line 1<br />line2</p>'
)

View File

@ -3,6 +3,9 @@ from __future__ import unicode_literals
from django.template.defaultfilters import slugify
from django.test import SimpleTestCase
from django.utils import six
from django.utils.encoding import force_text
from django.utils.functional import lazy
from django.utils.safestring import mark_safe
from ..utils import setup
@ -41,3 +44,10 @@ class FunctionTests(SimpleTestCase):
def test_non_string_input(self):
self.assertEqual(slugify(123), '123')
def test_slugify_lazy_string(self):
lazy_str = lazy(lambda string: force_text(string), six.text_type)
self.assertEqual(
slugify(lazy_str(' Jack & Jill like numbers 1,2,3 and 4 and silly characters ?%.$!/')),
'jack-jill-like-numbers-123-and-4-and-silly-characters',
)

View File

@ -1,5 +1,6 @@
from django.template.defaultfilters import striptags
from django.test import SimpleTestCase
from django.utils.functional import lazystr
from django.utils.safestring import mark_safe
from ..utils import setup
@ -40,3 +41,9 @@ class FunctionTests(SimpleTestCase):
def test_non_string_input(self):
self.assertEqual(striptags(123), '123')
def test_strip_lazy_string(self):
self.assertEqual(
striptags(lazystr('some <b>html</b> with <script>alert("Hello")</script> disallowed <img /> tags')),
'some html with alert("Hello") disallowed tags',
)

View File

@ -3,6 +3,8 @@ from __future__ import unicode_literals
from django.template.defaultfilters import urlize
from django.test import SimpleTestCase
from django.utils import six
from django.utils.functional import lazy
from django.utils.safestring import mark_safe
from ..utils import setup
@ -348,3 +350,10 @@ class FunctionTests(SimpleTestCase):
urlize('foo<a href=" google.com ">bar</a>buz', autoescape=False),
'foo<a href=" <a href="http://google.com" rel="nofollow">google.com</a> ">bar</a>buz',
)
def test_lazystring(self):
prepend_www = lazy(lambda url: 'www.' + url, six.text_type)
self.assertEqual(
urlize(prepend_www('google.com')),
'<a href="http://www.google.com" rel="nofollow">www.google.com</a>',
)

View File

@ -1,5 +1,6 @@
from django.template.defaultfilters import wordwrap
from django.test import SimpleTestCase
from django.utils.functional import lazystr
from django.utils.safestring import mark_safe
from ..utils import setup
@ -41,3 +42,11 @@ class FunctionTests(SimpleTestCase):
def test_non_string_input(self):
self.assertEqual(wordwrap(123, 2), '123')
def test_wrap_lazy_string(self):
self.assertEqual(
wordwrap(lazystr(
'this is a long paragraph of text that really needs to be wrapped I\'m afraid'
), 14),
'this is a long\nparagraph of\ntext that\nreally needs\nto be wrapped\nI\'m afraid',
)

View File

@ -8,6 +8,7 @@ from django.test import SimpleTestCase
from django.utils import html, safestring, six
from django.utils._os import upath
from django.utils.encoding import force_text
from django.utils.functional import lazystr
class TestUtilsHtml(SimpleTestCase):
@ -35,6 +36,7 @@ class TestUtilsHtml(SimpleTestCase):
for value, output in items:
for pattern in patterns:
self.check_output(f, pattern % value, pattern % output)
self.check_output(f, lazystr(pattern % value), pattern % output)
# Check repeated values.
self.check_output(f, value * 2, output * 2)
# Verify it doesn't double replace &.
@ -61,6 +63,7 @@ class TestUtilsHtml(SimpleTestCase):
)
for value, output in items:
self.check_output(f, value, output)
self.check_output(f, lazystr(value), output)
def test_strip_tags(self):
f = html.strip_tags
@ -86,6 +89,7 @@ class TestUtilsHtml(SimpleTestCase):
)
for value, output in items:
self.check_output(f, value, output)
self.check_output(f, lazystr(value), output)
# Some convoluted syntax for which parsing may differ between python versions
output = html.strip_tags('<sc<!-- -->ript>test<<!-- -->/script>')
@ -113,6 +117,7 @@ class TestUtilsHtml(SimpleTestCase):
items = (' <adf>', '<adf> ', ' </adf> ', ' <f> x</f>')
for value in items:
self.check_output(f, value)
self.check_output(f, lazystr(value))
# Strings that have spaces to strip.
items = (
('<d> </d>', '<d></d>'),
@ -121,6 +126,7 @@ class TestUtilsHtml(SimpleTestCase):
)
for value, output in items:
self.check_output(f, value, output)
self.check_output(f, lazystr(value), output)
def test_escapejs(self):
f = html.escapejs
@ -139,6 +145,7 @@ class TestUtilsHtml(SimpleTestCase):
)
for value, output in items:
self.check_output(f, value, output)
self.check_output(f, lazystr(value), output)
def test_smart_urlquote(self):
quote = html.smart_urlquote

View File

@ -3,13 +3,12 @@ from __future__ import unicode_literals
from django.template import Context, Template
from django.test import SimpleTestCase
from django.utils import html, six, text
from django.utils.encoding import force_bytes, force_text
from django.utils.functional import lazy
from django.utils.encoding import force_bytes
from django.utils.functional import lazy, lazystr
from django.utils.safestring import (
EscapeData, SafeData, mark_for_escaping, mark_safe,
)
lazystr = lazy(force_text, six.text_type)
lazybytes = lazy(force_bytes, bytes)

View File

@ -5,12 +5,9 @@ import json
from django.test import SimpleTestCase
from django.utils import six, text
from django.utils.encoding import force_text
from django.utils.functional import lazy
from django.utils.functional import lazystr
from django.utils.translation import override
lazystr = lazy(force_text, six.text_type)
IS_WIDE_BUILD = (len('\U0001F4A9') == 1)
@ -93,6 +90,8 @@ class TestUtilsText(SimpleTestCase):
# Make a best effort to shorten to the desired length, but requesting
# a length shorter than the ellipsis shouldn't break
self.assertEqual('...', text.Truncator('asdf').chars(1))
# Ensure that lazy strings are handled correctly
self.assertEqual(text.Truncator(lazystr('The quick brown fox')).chars(12), 'The quick...')
def test_truncate_words(self):
truncator = text.Truncator('The quick brown fox jumped over the lazy '
@ -102,6 +101,9 @@ class TestUtilsText(SimpleTestCase):
self.assertEqual('The quick brown fox...', truncator.words(4))
self.assertEqual('The quick brown fox[snip]',
truncator.words(4, '[snip]'))
# Ensure that lazy strings are handled correctly
truncator = text.Truncator(lazystr('The quick brown fox jumped over the lazy dog.'))
self.assertEqual('The quick brown fox...', truncator.words(4))
def test_truncate_html_words(self):
truncator = text.Truncator('<p id="par"><strong><em>The quick brown fox'
@ -156,6 +158,7 @@ class TestUtilsText(SimpleTestCase):
self.assertEqual(text.wrap(long_word, 20), long_word)
self.assertEqual(text.wrap('a %s word' % long_word, 10),
'a\n%s\nword' % long_word)
self.assertEqual(text.wrap(lazystr(digits), 100), '1234 67 9')
def test_normalize_newlines(self):
self.assertEqual(text.normalize_newlines("abc\ndef\rghi\r\n"),
@ -163,6 +166,7 @@ class TestUtilsText(SimpleTestCase):
self.assertEqual(text.normalize_newlines("\n\r\r\n\r"), "\n\n\n\n")
self.assertEqual(text.normalize_newlines("abcdefghi"), "abcdefghi")
self.assertEqual(text.normalize_newlines(""), "")
self.assertEqual(text.normalize_newlines(lazystr("abc\ndef\rghi\r\n")), "abc\ndef\nghi\n")
def test_normalize_newlines_bytes(self):
"""normalize_newlines should be able to handle bytes too"""
@ -170,6 +174,12 @@ class TestUtilsText(SimpleTestCase):
self.assertEqual(normalized, "abc\ndef\nghi\n")
self.assertIsInstance(normalized, six.text_type)
def test_phone2numeric(self):
numeric = text.phone2numeric('0800 flowers')
self.assertEqual(numeric, '0800 3569377')
lazy_numeric = lazystr(text.phone2numeric('0800 flowers'))
self.assertEqual(lazy_numeric, '0800 3569377')
def test_slugify(self):
items = (
# given - expected - unicode?
@ -195,10 +205,23 @@ class TestUtilsText(SimpleTestCase):
]
for value, output in items:
self.assertEqual(text.unescape_entities(value), output)
self.assertEqual(text.unescape_entities(lazystr(value)), output)
def test_unescape_string_literal(self):
items = [
('"abc"', 'abc'),
("'abc'", 'abc'),
('"a \"bc\""', 'a "bc"'),
("'\'ab\' c'", "'ab' c"),
]
for value, output in items:
self.assertEqual(text.unescape_string_literal(value), output)
self.assertEqual(text.unescape_string_literal(lazystr(value)), output)
def test_get_valid_filename(self):
filename = "^&'@{}[],$=!-#()%+~_123.txt"
self.assertEqual(text.get_valid_filename(filename), "-_123.txt")
self.assertEqual(text.get_valid_filename(lazystr(filename)), "-_123.txt")
def test_compress_sequence(self):
data = [{'key': i} for i in range(10)]