mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +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:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						parent
						
							93fc23b2d5
						
					
				
				
					commit
					d693074d43
				
			
							
								
								
									
										2
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								AUTHORS
									
									
									
									
									
								
							| @@ -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> | ||||
|   | ||||
| @@ -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. | ||||
|     """ | ||||
|     if not resultclasses: | ||||
|         raise TypeError("You must pass at least one argument to keep_lazy().") | ||||
|  | ||||
|     def decorator(func): | ||||
|         lazy_func = lazy(func, *resultclasses) | ||||
|  | ||||
|         @wraps(func) | ||||
|         def wrapper(*args, **kwargs): | ||||
|         for arg in list(args) + list(kwargs.values()): | ||||
|             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() | ||||
|  | ||||
|   | ||||
| @@ -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>(?: |\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('&', '&').replace('<', '<') | ||||
|         .replace('>', '>').replace('"', '"').replace("'", ''')) | ||||
| 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): | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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`` | ||||
| ===================== | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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&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, '<some html & special characters > here') | ||||
|  | ||||
|  | ||||
| class FunctionTests(SimpleTestCase): | ||||
|  | ||||
|   | ||||
| @@ -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' | ||||
|         ) | ||||
|   | ||||
| @@ -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>' | ||||
|         ) | ||||
|   | ||||
| @@ -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', | ||||
|         ) | ||||
|   | ||||
| @@ -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', | ||||
|         ) | ||||
|   | ||||
| @@ -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>', | ||||
|         ) | ||||
|   | ||||
| @@ -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', | ||||
|         ) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -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)] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user