From 921e4ed2ee11edffd19d2ca40f10d47d2c148ea1 Mon Sep 17 00:00:00 2001 From: Paul Oswald Date: Mon, 28 Jul 2014 16:59:55 +0900 Subject: [PATCH 1/7] 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/7] 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/7] 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/7] 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()] From 3e20b0110cc30c112906cc473e04e837737ff4f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Padilla?= Date: Fri, 5 Sep 2014 15:58:42 -0700 Subject: [PATCH 5/7] Fix linting issues --- tests/test_description.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_description.py b/tests/test_description.py index 25bee19b4..1208ce548 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -110,8 +110,10 @@ class TestViewNamesAndDescriptions(TestCase): class MockLazyStr(object): def __init__(self, string): self.s = string + def __str__(self): return self.s + def __unicode__(self): return self.s From c9d4497d816023e30a7adfc9f2b74eea966ff17c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Padilla?= Date: Fri, 5 Sep 2014 15:58:53 -0700 Subject: [PATCH 6/7] Use force_text from compat --- rest_framework/utils/formatting.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index bca697ae4..470af51b0 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -2,13 +2,12 @@ Utility functions to return a formatted name and description for a given view. """ from __future__ import unicode_literals +import re 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 +from rest_framework.compat import apply_markdown, force_text def remove_trailing_string(content, trailing): From 97ebd68f681961fb7e3f785e3cb84a69b3dc56aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Padilla?= Date: Fri, 5 Sep 2014 16:01:17 -0700 Subject: [PATCH 7/7] Remove unicode strings --- tests/test_description.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_description.py b/tests/test_description.py index 1208ce548..0675d209c 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -118,9 +118,9 @@ class TestViewNamesAndDescriptions(TestCase): return self.s class MockView(APIView): - __doc__ = MockLazyStr(u"a gettext string") + __doc__ = MockLazyStr("a gettext string") - self.assertEqual(MockView().get_view_description(), u'a gettext string') + self.assertEqual(MockView().get_view_description(), 'a gettext string') def test_markdown(self): """