summaryrefslogtreecommitdiff
path: root/django/utils
diff options
context:
space:
mode:
authorIacopo Spalletti <i.spalletti@nephila.it>2015-11-07 14:30:20 +0100
committerTim Graham <timograham@gmail.com>2015-12-12 14:46:48 -0500
commitd693074d431c50e4801dd6bf52525ce1436358f0 (patch)
treead452646aad45bf9241478f3fc17803d05320cfc /django/utils
parent93fc23b2d542105f5d129dff2dd2c8895e9abd5d (diff)
Fixed #20223 -- Added keep_lazy() as a replacement for allow_lazy().
Thanks to bmispelon and uruz for the initial patch.
Diffstat (limited to 'django/utils')
-rw-r--r--django/utils/functional.py50
-rw-r--r--django/utils/html.py17
-rw-r--r--django/utils/http.py10
-rw-r--r--django/utils/text.py26
4 files changed, 67 insertions, 36 deletions
diff --git a/django/utils/functional.py b/django/utils/functional.py
index 7b566e429f..2830cc47ab 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -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()
diff --git a/django/utils/html.py b/django/utils/html.py
index 1d86441a28..89d6a00eb2 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -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):
diff --git a/django/utils/http.py b/django/utils/http.py
index 8bbafaedec..3a3f665443 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -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):
diff --git a/django/utils/text.py b/django/utils/text.py
index 456c853712..af80812a0d 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -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):