diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 0fb74ca00..3c4be8aeb 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -418,7 +418,7 @@ class BrowsableAPIRenderer(BaseRenderer): if render_style == 'binary': return '[%d bytes of binary content]' % len(content) - return content + return content.decode('utf-8') if isinstance(content, bytes) else content def show_form_for_method(self, view, method, request, obj): """ diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 9207f049b..a88e1591c 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -77,7 +77,7 @@
{% block request_forms %} - + {% if 'GET' in allowed_methods %}
@@ -176,9 +176,9 @@
HTTP {{ response.status_code }} {{ response.status_text }}{% for key, val in response_headers|items %}
-{{ key }}: {{ val|break_long_headers|urlize_quoted_links }}{% endfor %}
+{{ key }}: {{ val|break_long_headers|urlize }}{% endfor %}
 
-{{ content|urlize_quoted_links }}
+{{ content|urlize }}
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index 79dd953ff..7bfa8f599 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -4,9 +4,9 @@ from collections import OrderedDict from django import template from django.template import loader from django.urls import NoReverseMatch, reverse -from django.utils.encoding import force_str, iri_to_uri +from django.utils.encoding import iri_to_uri from django.utils.html import escape, format_html, smart_urlquote -from django.utils.safestring import SafeData, mark_safe +from django.utils.safestring import mark_safe from rest_framework.compat import apply_markdown, pygments_highlight from rest_framework.renderers import HTMLFormRenderer @@ -311,85 +311,6 @@ def smart_urlquote_wrapper(matched_url): return None -@register.filter(needs_autoescape=True) -def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=True): - """ - Converts any URLs in text into clickable links. - - Works on http://, https://, www. links, and also on links ending in one of - the original seven gTLDs (.com, .edu, .gov, .int, .mil, .net, and .org). - Links can have trailing punctuation (periods, commas, close-parens) and - leading punctuation (opening parens) and it'll still do the right thing. - - If trim_url_limit is not None, the URLs in link text longer than this limit - will truncated to trim_url_limit-3 characters and appended with an ellipsis. - - If nofollow is True, the URLs in link text will get a rel="nofollow" - attribute. - - If autoescape is True, the link text and URLs will get autoescaped. - """ - def trim_url(x, limit=trim_url_limit): - return limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x - - safe_input = isinstance(text, SafeData) - - # Unfortunately, Django built-in cannot be used here, because escaping - # is to be performed on words, which have been forcibly coerced to text - def conditional_escape(text): - return escape(text) if autoescape and not safe_input else text - - words = word_split_re.split(force_str(text)) - for i, word in enumerate(words): - if '.' in word or '@' in word or ':' in word: - # Deal with punctuation. - lead, middle, trail = '', word, '' - for punctuation in TRAILING_PUNCTUATION: - if middle.endswith(punctuation): - middle = middle[:-len(punctuation)] - trail = punctuation + trail - for opening, closing in WRAPPING_PUNCTUATION: - if middle.startswith(opening): - middle = middle[len(opening):] - lead = lead + opening - # Keep parentheses at the end only if they're balanced. - if ( - middle.endswith(closing) and - middle.count(closing) == middle.count(opening) + 1 - ): - middle = middle[:-len(closing)] - trail = closing + trail - - # Make URL we want to point to. - url = None - nofollow_attr = ' rel="nofollow"' if nofollow else '' - if simple_url_re.match(middle): - url = smart_urlquote_wrapper(middle) - elif simple_url_2_re.match(middle): - url = smart_urlquote_wrapper('http://%s' % middle) - elif ':' not in middle and simple_email_re.match(middle): - local, domain = middle.rsplit('@', 1) - try: - domain = domain.encode('idna').decode('ascii') - except UnicodeError: - continue - url = 'mailto:%s@%s' % (local, domain) - nofollow_attr = '' - - # Make link. - if url: - trimmed = trim_url(middle) - lead, trail = conditional_escape(lead), conditional_escape(trail) - url, trimmed = conditional_escape(url), conditional_escape(trimmed) - middle = '%s' % (url, nofollow_attr, trimmed) - words[i] = '%s%s%s' % (lead, middle, trail) - else: - words[i] = conditional_escape(word) - else: - words[i] = conditional_escape(word) - return mark_safe(''.join(words)) - - @register.filter def break_long_headers(header): """ diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py index 28d6b4011..cdf10ab1e 100644 --- a/tests/test_templatetags.py +++ b/tests/test_templatetags.py @@ -2,13 +2,14 @@ import unittest from django.template import Context, Template from django.test import TestCase +from django.utils.html import urlize from rest_framework.compat import coreapi, coreschema from rest_framework.relations import Hyperlink from rest_framework.templatetags import rest_framework from rest_framework.templatetags.rest_framework import ( add_nested_class, add_query_param, as_string, break_long_headers, - format_value, get_pagination_html, schema_links, urlize_quoted_links + format_value, get_pagination_html, schema_links ) from rest_framework.test import APIRequestFactory @@ -246,7 +247,7 @@ class Issue1386Tests(TestCase): def test_issue_1386(self): """ - Test function urlize_quoted_links with different args + Test function urlize with different args """ correct_urls = [ "asdf.com", @@ -255,7 +256,7 @@ class Issue1386Tests(TestCase): "as.d8f.ghj8.gov", ] for i in correct_urls: - res = urlize_quoted_links(i) + res = urlize(i) self.assertNotEqual(res, i) self.assertIn(i, res) @@ -264,11 +265,11 @@ class Issue1386Tests(TestCase): "asdf.netnet", ] for i in incorrect_urls: - res = urlize_quoted_links(i) + res = urlize(i) self.assertEqual(i, res) # example from issue #1386, this shouldn't raise an exception - urlize_quoted_links("asdf:[/p]zxcv.com") + urlize("asdf:[/p]zxcv.com") def test_smart_urlquote_wrapper_handles_value_error(self): def mock_smart_urlquote(url): @@ -289,7 +290,10 @@ class URLizerTests(TestCase): For all items in dict test assert that the value is urlized key """ for original, urlized in data.items(): - assert urlize_quoted_links(original, nofollow=False) == urlized + print('====') + print(repr(urlize(original, nofollow=False))) + print(repr(urlized)) + assert urlize(original, nofollow=False) == urlized def test_json_with_url(self): """ @@ -297,26 +301,26 @@ class URLizerTests(TestCase): """ data = {} data['"url": "http://api/users/1/", '] = \ - '"url": "http://api/users/1/", ' + '"url": "http://api/users/1/", ' data['"foo_set": [\n "http://api/foos/1/"\n], '] = \ - '"foo_set": [\n "http://api/foos/1/"\n], ' + '"foo_set": [\n "http://api/foos/1/"\n], ' self._urlize_dict_check(data) def test_template_render_with_autoescape(self): """ Test that HTML is correctly escaped in Browsable API views. """ - template = Template("{% load rest_framework %}{{ content|urlize_quoted_links }}") + template = Template("{% load rest_framework %}{{ content|urlize }}") rendered = template.render(Context({'content': ' http://example.com'})) assert rendered == '<script>alert()</script>' \ ' http://example.com' def test_template_render_with_noautoescape(self): """ - Test if the autoescape value is getting passed to urlize_quoted_links filter. + Test if the autoescape value is getting passed to urlize filter. """ template = Template("{% load rest_framework %}" - "{% autoescape off %}{{ content|urlize_quoted_links }}" + "{% autoescape off %}{{ content|urlize }}" "{% endautoescape %}") rendered = template.render(Context({'content': ' "http://example.com" '})) assert rendered == ' "http://example.com" '