From 921e4ed2ee11edffd19d2ca40f10d47d2c148ea1 Mon Sep 17 00:00:00 2001 From: Paul Oswald Date: Mon, 28 Jul 2014 16:59:55 +0900 Subject: [PATCH 1/4] Evaluate content before passing to regex.sub Issue #1708 --- rest_framework/tests/test_description.py | 22 ++++++++++++++++++++++ rest_framework/utils/formatting.py | 4 +--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/rest_framework/tests/test_description.py b/rest_framework/tests/test_description.py index 4c03c1ded..52fa55fb8 100644 --- a/rest_framework/tests/test_description.py +++ b/rest_framework/tests/test_description.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from django.test import TestCase +from django.utils.encoding import python_2_unicode_compatible from rest_framework.compat import apply_markdown, smart_text from rest_framework.views import APIView from rest_framework.tests.description import ViewWithNonASCIICharactersInDocstring @@ -98,6 +99,27 @@ class TestViewNamesAndDescriptions(TestCase): pass self.assertEqual(MockView().get_view_description(), '') + def test_view_description_can_be_promise(self): + """ + Ensure a view may have a docstring that is actually a lazily evaluated + class that can be converted to a string. + + See: https://github.com/tomchristie/django-rest-framework/issues/1708 + """ + # use a mock object instead of gettext_lazy to ensure that we can't end + # up with a test case string in our l10n catalog + @python_2_unicode_compatible + class MockLazyStr(object): + def __init__(self, string): + self.s = string + def __str__(self): + return self.s + + class MockView(APIView): + __doc__ = MockLazyStr("a gettext string") + + self.assertEqual(MockView().get_view_description(), 'a gettext string') + def test_markdown(self): """ Ensure markdown to HTML works as expected. diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index 4b59ba840..12b79b6cd 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -6,8 +6,6 @@ from __future__ import unicode_literals from django.utils.html import escape from django.utils.safestring import mark_safe from rest_framework.compat import apply_markdown -from rest_framework.settings import api_settings -from textwrap import dedent import re @@ -36,7 +34,7 @@ def dedent(content): # unindent the content if needed if whitespace_counts: whitespace_pattern = '^' + (' ' * min(whitespace_counts)) - content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content) + content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', unicode(content)) return content.strip() From 66fa40c300b4d3e768b4a7993f020056c44fdda3 Mon Sep 17 00:00:00 2001 From: Paul Oswald Date: Tue, 29 Jul 2014 22:13:11 +0900 Subject: [PATCH 2/4] evaluate content at function start --- rest_framework/utils/formatting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index 12b79b6cd..2b3cbc957 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -28,13 +28,14 @@ def dedent(content): as it fails to dedent multiline docstrings that include unindented text on the initial line. """ + content = unicode(content) whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in content.splitlines()[1:] if line.lstrip()] # unindent the content if needed if whitespace_counts: whitespace_pattern = '^' + (' ' * min(whitespace_counts)) - content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', unicode(content)) + content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content) return content.strip() From 192201d5840f13c8b96f44fdce4645edeb653f0f Mon Sep 17 00:00:00 2001 From: Paul Oswald Date: Thu, 7 Aug 2014 15:48:29 +0900 Subject: [PATCH 3/4] remove dep on python_2_unicode_compatible python_2_unicode_compatible is not available in all Django versions --- rest_framework/tests/test_description.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rest_framework/tests/test_description.py b/rest_framework/tests/test_description.py index 52fa55fb8..8aa162613 100644 --- a/rest_framework/tests/test_description.py +++ b/rest_framework/tests/test_description.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals from django.test import TestCase -from django.utils.encoding import python_2_unicode_compatible from rest_framework.compat import apply_markdown, smart_text from rest_framework.views import APIView from rest_framework.tests.description import ViewWithNonASCIICharactersInDocstring @@ -108,17 +107,18 @@ class TestViewNamesAndDescriptions(TestCase): """ # use a mock object instead of gettext_lazy to ensure that we can't end # up with a test case string in our l10n catalog - @python_2_unicode_compatible class MockLazyStr(object): def __init__(self, string): self.s = string def __str__(self): return self.s + def __unicode__(self): + return self.s class MockView(APIView): - __doc__ = MockLazyStr("a gettext string") + __doc__ = MockLazyStr(u"a gettext string") - self.assertEqual(MockView().get_view_description(), 'a gettext string') + self.assertEqual(MockView().get_view_description(), u'a gettext string') def test_markdown(self): """ From 3e93c96ece8af010185e1fe1188dd2df569d4528 Mon Sep 17 00:00:00 2001 From: Paul Oswald Date: Tue, 19 Aug 2014 10:09:48 +0900 Subject: [PATCH 4/4] replace unicode call with force_text --- rest_framework/utils/formatting.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index 2b3cbc957..40bced5f1 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -5,6 +5,8 @@ from __future__ import unicode_literals from django.utils.html import escape from django.utils.safestring import mark_safe +from django.utils.encoding import force_text + from rest_framework.compat import apply_markdown import re @@ -28,7 +30,7 @@ def dedent(content): as it fails to dedent multiline docstrings that include unindented text on the initial line. """ - content = unicode(content) + content = force_text(content) whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in content.splitlines()[1:] if line.lstrip()]