From be729b6120a50fff28195c28896c83a9b1e0c355 Mon Sep 17 00:00:00 2001 From: Scott Vitale Date: Thu, 2 Jun 2016 15:11:43 -0600 Subject: [PATCH] Fixed #10107 -- Allowed using mark_safe() as a decorator. Thanks ArcTanSusan for the initial patch. --- django/utils/safestring.py | 13 +++++++++++- docs/ref/utils.txt | 6 ++++++ docs/releases/1.11.txt | 2 +- tests/decorators/tests.py | 4 ++++ tests/utils_tests/test_safestring.py | 30 ++++++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 2 deletions(-) diff --git a/django/utils/safestring.py b/django/utils/safestring.py index 24a29e0747..cceb9fc3e5 100644 --- a/django/utils/safestring.py +++ b/django/utils/safestring.py @@ -8,7 +8,7 @@ import warnings from django.utils import six from django.utils.deprecation import RemovedInDjango20Warning -from django.utils.functional import Promise, curry +from django.utils.functional import Promise, curry, wraps class EscapeData(object): @@ -117,11 +117,20 @@ else: SafeUnicode = SafeText +def _safety_decorator(safety_marker, func): + @wraps(func) + def wrapped(*args, **kwargs): + return safety_marker(func(*args, **kwargs)) + return wrapped + + def mark_safe(s): """ Explicitly mark a string as safe for (HTML) output purposes. The returned object can be used everywhere a string or unicode object is appropriate. + If used on a method as a decorator, mark the returned data as safe. + Can be called multiple times on a single string. """ if hasattr(s, '__html__'): @@ -130,6 +139,8 @@ def mark_safe(s): return SafeBytes(s) if isinstance(s, (six.text_type, Promise)): return SafeText(s) + if callable(s): + return _safety_decorator(mark_safe, s) return SafeString(str(s)) diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 2fc868e143..e7b3b2a997 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -832,6 +832,8 @@ appropriate entities. Can be called multiple times on a single string. + Can also be used as a decorator. + For building up fragments of HTML, you should normally be using :func:`django.utils.html.format_html` instead. @@ -846,6 +848,10 @@ appropriate entities. >>> type(mystr) + .. versionchanged:: 1.11 + + Added support for decorator usage. + .. function:: mark_for_escaping(s) .. deprecated:: 1.10 diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index f3cc8820de..1436ffe964 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -202,7 +202,7 @@ Signals Templates ~~~~~~~~~ -* ... +* :meth:`~django.utils.safestring.mark_safe` can now be used as a decorator. Tests ~~~~~ diff --git a/tests/decorators/tests.py b/tests/decorators/tests.py index 8fed78a67f..08bb12ce29 100644 --- a/tests/decorators/tests.py +++ b/tests/decorators/tests.py @@ -13,6 +13,7 @@ from django.utils.decorators import method_decorator 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.safestring import mark_safe from django.utils.translation import ugettext_lazy from django.views.decorators.cache import ( cache_control, cache_page, never_cache, @@ -74,6 +75,9 @@ full_decorator = compose( keep_lazy(HttpResponse), keep_lazy_text, lazy, + + # django.utils.safestring + mark_safe, ) fully_decorated = full_decorator(fully_decorated) diff --git a/tests/utils_tests/test_safestring.py b/tests/utils_tests/test_safestring.py index 6ea3972f78..6afcb3b1f7 100644 --- a/tests/utils_tests/test_safestring.py +++ b/tests/utils_tests/test_safestring.py @@ -109,3 +109,33 @@ class SafeStringTest(SimpleTestCase): s = text.slugify(lazystr('a')) s += mark_safe('&b') self.assertRenderEqual('{{ s }}', 'a&b', s=s) + + def test_mark_safe_as_decorator(self): + """ + mark_safe used as a decorator leaves the result of a function + unchanged. + """ + def clean_string_provider(): + return 'dummy' + + self.assertEqual(mark_safe(clean_string_provider)(), clean_string_provider()) + + def test_mark_safe_decorator_does_not_affect_dunder_html(self): + """ + mark_safe doesn't affect a callable that has an __html__() method. + """ + class SafeStringContainer: + def __html__(self): + return '' + + self.assertIs(mark_safe(SafeStringContainer), SafeStringContainer) + + def test_mark_safe_decorator_does_not_affect_promises(self): + """ + mark_safe doesn't affect lazy strings (Promise objects). + """ + def html_str(): + return '' + + lazy_str = lazy(html_str, str)() + self.assertEqual(mark_safe(lazy_str), html_str())