Browse Source

Fixed #30399 -- Changed django.utils.html.escape()/urlize() to use html.escape()/unescape().

Jon Dufresne 6 years ago
parent
commit
8d76443aba

+ 5 - 21
django/utils/html.py

@@ -1,5 +1,6 @@
 """HTML utilities suitable for global use."""
 """HTML utilities suitable for global use."""
 
 
+import html
 import json
 import json
 import re
 import re
 from html.parser import HTMLParser
 from html.parser import HTMLParser
@@ -24,14 +25,6 @@ word_split_re = re.compile(r'''([\s<>"']+)''')
 simple_url_re = re.compile(r'^https?://\[?\w', re.IGNORECASE)
 simple_url_re = re.compile(r'^https?://\[?\w', re.IGNORECASE)
 simple_url_2_re = re.compile(r'^www\.|^(?!http)\w[^@]+\.(com|edu|gov|int|mil|net|org)($|/.*)$', re.IGNORECASE)
 simple_url_2_re = re.compile(r'^www\.|^(?!http)\w[^@]+\.(com|edu|gov|int|mil|net|org)($|/.*)$', re.IGNORECASE)
 
 
-_html_escapes = {
-    ord('&'): '&amp;',
-    ord('<'): '&lt;',
-    ord('>'): '&gt;',
-    ord('"'): '&quot;',
-    ord("'"): '&#39;',
-}
-
 
 
 @keep_lazy(str, SafeString)
 @keep_lazy(str, SafeString)
 def escape(text):
 def escape(text):
@@ -43,7 +36,7 @@ def escape(text):
     This may result in double-escaping. If this is a concern, use
     This may result in double-escaping. If this is a concern, use
     conditional_escape() instead.
     conditional_escape() instead.
     """
     """
-    return mark_safe(str(text).translate(_html_escapes))
+    return mark_safe(html.escape(str(text)))
 
 
 
 
 _js_escapes = {
 _js_escapes = {
@@ -259,15 +252,6 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
             return x
             return x
         return '%s…' % x[:max(0, limit - 1)]
         return '%s…' % x[:max(0, limit - 1)]
 
 
-    def unescape(text):
-        """
-        If input URL is HTML-escaped, unescape it so that it can be safely fed
-        to smart_urlquote. For example:
-        http://example.com?x=1&amp;y=&lt;2&gt; => http://example.com?x=1&y=<2>
-        """
-        return text.replace('&amp;', '&').replace('&lt;', '<').replace(
-            '&gt;', '>').replace('&quot;', '"').replace('&#39;', "'")
-
     def trim_punctuation(lead, middle, trail):
     def trim_punctuation(lead, middle, trail):
         """
         """
         Trim trailing and wrapping punctuation from `middle`. Return the items
         Trim trailing and wrapping punctuation from `middle`. Return the items
@@ -292,7 +276,7 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
             # Trim trailing punctuation (after trimming wrapping punctuation,
             # Trim trailing punctuation (after trimming wrapping punctuation,
             # as encoded entities contain ';'). Unescape entites to avoid
             # as encoded entities contain ';'). Unescape entites to avoid
             # breaking them by removing ';'.
             # breaking them by removing ';'.
-            middle_unescaped = unescape(middle)
+            middle_unescaped = html.unescape(middle)
             stripped = middle_unescaped.rstrip(TRAILING_PUNCTUATION_CHARS)
             stripped = middle_unescaped.rstrip(TRAILING_PUNCTUATION_CHARS)
             if middle_unescaped != stripped:
             if middle_unescaped != stripped:
                 trail = middle[len(stripped):] + trail
                 trail = middle[len(stripped):] + trail
@@ -329,9 +313,9 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
             url = None
             url = None
             nofollow_attr = ' rel="nofollow"' if nofollow else ''
             nofollow_attr = ' rel="nofollow"' if nofollow else ''
             if simple_url_re.match(middle):
             if simple_url_re.match(middle):
-                url = smart_urlquote(unescape(middle))
+                url = smart_urlquote(html.unescape(middle))
             elif simple_url_2_re.match(middle):
             elif simple_url_2_re.match(middle):
-                url = smart_urlquote('http://%s' % unescape(middle))
+                url = smart_urlquote('http://%s' % html.unescape(middle))
             elif ':' not in middle and is_email_simple(middle):
             elif ':' not in middle and is_email_simple(middle):
                 local, domain = middle.rsplit('@', 1)
                 local, domain = middle.rsplit('@', 1)
                 try:
                 try:

+ 1 - 1
docs/intro/tutorial05.txt

@@ -387,7 +387,7 @@ With that ready, we can ask the client to do some work for us::
     >>> response.status_code
     >>> response.status_code
     200
     200
     >>> response.content
     >>> response.content
-    b'\n    <ul>\n    \n        <li><a href="/polls/1/">What&#39;s up?</a></li>\n    \n    </ul>\n\n'
+    b'\n    <ul>\n    \n        <li><a href="/polls/1/">What&#x27;s up?</a></li>\n    \n    </ul>\n\n'
     >>> response.context['latest_question_list']
     >>> response.context['latest_question_list']
     <QuerySet [<Question: What's up?>]>
     <QuerySet [<Question: What's up?>]>
 
 

+ 1 - 1
docs/ref/templates/builtins.txt

@@ -1603,7 +1603,7 @@ Escapes a string's HTML. Specifically, it makes these replacements:
 
 
 * ``<`` is converted to ``&lt;``
 * ``<`` is converted to ``&lt;``
 * ``>`` is converted to ``&gt;``
 * ``>`` is converted to ``&gt;``
-* ``'`` (single quote) is converted to ``&#39;``
+* ``'`` (single quote) is converted to ``&#x27;``
 * ``"`` (double quote) is converted to ``&quot;``
 * ``"`` (double quote) is converted to ``&quot;``
 * ``&`` is converted to ``&amp;``
 * ``&`` is converted to ``&amp;``
 
 

+ 1 - 1
docs/ref/templates/language.txt

@@ -492,7 +492,7 @@ escaped:
 
 
 * ``<`` is converted to ``&lt;``
 * ``<`` is converted to ``&lt;``
 * ``>`` is converted to ``&gt;``
 * ``>`` is converted to ``&gt;``
-* ``'`` (single quote) is converted to ``&#39;``
+* ``'`` (single quote) is converted to ``&#x27;``
 * ``"`` (double quote) is converted to ``&quot;``
 * ``"`` (double quote) is converted to ``&quot;``
 * ``&`` is converted to ``&amp;``
 * ``&`` is converted to ``&amp;``
 
 

+ 5 - 0
docs/ref/utils.txt

@@ -584,6 +584,11 @@ escaping HTML.
     for use in HTML. The input is first coerced to a string and the output has
     for use in HTML. The input is first coerced to a string and the output has
     :func:`~django.utils.safestring.mark_safe` applied.
     :func:`~django.utils.safestring.mark_safe` applied.
 
 
+    .. versionchanged:: 3.0
+
+        In older versions, ``'`` is converted to its decimal code ``&#39;``
+        instead of the equivalent hex code ``&#x27;``.
+
 .. function:: conditional_escape(text)
 .. function:: conditional_escape(text)
 
 
     Similar to ``escape()``, except that it doesn't operate on pre-escaped
     Similar to ``escape()``, except that it doesn't operate on pre-escaped

+ 4 - 0
docs/releases/3.0.txt

@@ -348,6 +348,10 @@ Miscellaneous
   the session and :func:`django.contrib.auth.logout` no longer preserves the
   the session and :func:`django.contrib.auth.logout` no longer preserves the
   session's language after logout.
   session's language after logout.
 
 
+* :func:`django.utils.html.escape` now uses :func:`html.escape` to escape HTML.
+  This converts ``'`` to ``&#x27;`` instead of the previous equivalent decimal
+  code ``&#39;``.
+
 .. _deprecated-features-3.0:
 .. _deprecated-features-3.0:
 
 
 Features deprecated in 3.0
 Features deprecated in 3.0

+ 1 - 1
tests/admin_docs/test_views.py

@@ -199,7 +199,7 @@ class TestModelDetailView(TestDataMixin, AdminDocsTestCase):
         """
         """
         Methods with keyword arguments should have their arguments displayed.
         Methods with keyword arguments should have their arguments displayed.
         """
         """
-        self.assertContains(self.response, "<td>suffix=&#39;ltd&#39;</td>")
+        self.assertContains(self.response, '<td>suffix=&#x27;ltd&#x27;</td>')
 
 
     def test_methods_with_multiple_arguments_display_arguments(self):
     def test_methods_with_multiple_arguments_display_arguments(self):
         """
         """

+ 1 - 1
tests/auth_tests/test_forms.py

@@ -236,7 +236,7 @@ class UserCreationFormTest(TestDataMixin, TestCase):
         form = UserCreationForm()
         form = UserCreationForm()
         self.assertEqual(
         self.assertEqual(
             form.fields['password1'].help_text,
             form.fields['password1'].help_text,
-            '<ul><li>Your password can&#39;t be too similar to your other personal information.</li></ul>'
+            '<ul><li>Your password can&#x27;t be too similar to your other personal information.</li></ul>'
         )
         )
 
 
     @override_settings(AUTH_PASSWORD_VALIDATORS=[
     @override_settings(AUTH_PASSWORD_VALIDATORS=[

+ 5 - 5
tests/forms_tests/tests/test_forms.py

@@ -995,7 +995,7 @@ Java</label></li>
         self.assertHTMLEqual(
         self.assertHTMLEqual(
             f.as_table(),
             f.as_table(),
             """<tr><th>&lt;em&gt;Special&lt;/em&gt; Field:</th><td>
             """<tr><th>&lt;em&gt;Special&lt;/em&gt; Field:</th><td>
-<ul class="errorlist"><li>Something&#39;s wrong with &#39;Nothing to escape&#39;</li></ul>
+<ul class="errorlist"><li>Something&#x27;s wrong with &#x27;Nothing to escape&#x27;</li></ul>
 <input type="text" name="special_name" value="Nothing to escape" required></td></tr>
 <input type="text" name="special_name" value="Nothing to escape" required></td></tr>
 <tr><th><em>Special</em> Field:</th><td>
 <tr><th><em>Special</em> Field:</th><td>
 <ul class="errorlist"><li>'<b>Nothing to escape</b>' is a safe string</li></ul>
 <ul class="errorlist"><li>'<b>Nothing to escape</b>' is a safe string</li></ul>
@@ -1008,10 +1008,10 @@ Java</label></li>
         self.assertHTMLEqual(
         self.assertHTMLEqual(
             f.as_table(),
             f.as_table(),
             """<tr><th>&lt;em&gt;Special&lt;/em&gt; Field:</th><td>
             """<tr><th>&lt;em&gt;Special&lt;/em&gt; Field:</th><td>
-<ul class="errorlist"><li>Something&#39;s wrong with &#39;Should escape &lt; &amp; &gt; and
+<ul class="errorlist"><li>Something&#x27;s wrong with &#x27;Should escape &lt; &amp; &gt; and
-&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;&#39;</li></ul>
+&lt;script&gt;alert(&#x27;xss&#x27;)&lt;/script&gt;&#x27;</li></ul>
 <input type="text" name="special_name"
 <input type="text" name="special_name"
-value="Should escape &lt; &amp; &gt; and &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;" required></td></tr>
+value="Should escape &lt; &amp; &gt; and &lt;script&gt;alert(&#x27;xss&#x27;)&lt;/script&gt;" required></td></tr>
 <tr><th><em>Special</em> Field:</th><td>
 <tr><th><em>Special</em> Field:</th><td>
 <ul class="errorlist"><li>'<b><i>Do not escape</i></b>' is a safe string</li></ul>
 <ul class="errorlist"><li>'<b><i>Do not escape</i></b>' is a safe string</li></ul>
 <input type="text" name="special_safe_name" value="&lt;i&gt;Do not escape&lt;/i&gt;" required></td></tr>"""
 <input type="text" name="special_safe_name" value="&lt;i&gt;Do not escape&lt;/i&gt;" required></td></tr>"""
@@ -2632,7 +2632,7 @@ Password: <input type="password" name="password" required>
             t.render(Context({'form': UserRegistration(auto_id=False)})),
             t.render(Context({'form': UserRegistration(auto_id=False)})),
             """<form>
             """<form>
 <p>Username: <input type="text" name="username" maxlength="10" required><br>
 <p>Username: <input type="text" name="username" maxlength="10" required><br>
-Good luck picking a username that doesn&#39;t already exist.</p>
+Good luck picking a username that doesn&#x27;t already exist.</p>
 <p>Password1: <input type="password" name="password1" required></p>
 <p>Password1: <input type="password" name="password1" required></p>
 <p>Password2: <input type="password" name="password2" required></p>
 <p>Password2: <input type="password" name="password2" required></p>
 <input type="submit" required>
 <input type="submit" required>

+ 4 - 1
tests/forms_tests/widget_tests/base.py

@@ -22,7 +22,10 @@ class WidgetTest(SimpleTestCase):
         if self.jinja2_renderer:
         if self.jinja2_renderer:
             output = widget.render(name, value, attrs=attrs, renderer=self.jinja2_renderer, **kwargs)
             output = widget.render(name, value, attrs=attrs, renderer=self.jinja2_renderer, **kwargs)
             # Django escapes quotes with '&quot;' while Jinja2 uses '&#34;'.
             # Django escapes quotes with '&quot;' while Jinja2 uses '&#34;'.
-            assertEqual(output.replace('&#34;', '&quot;'), html)
+            output = output.replace('&#34;', '&quot;')
+            # Django escapes single quotes with '&#x27;' while Jinja2 uses '&#39;'.
+            output = output.replace('&#39;', '&#x27;')
+            assertEqual(output, html)
 
 
         output = widget.render(name, value, attrs=attrs, renderer=self.django_renderer, **kwargs)
         output = widget.render(name, value, attrs=attrs, renderer=self.django_renderer, **kwargs)
         assertEqual(output, html)
         assertEqual(output, html)

+ 1 - 1
tests/forms_tests/widget_tests/test_clearablefileinput.py

@@ -46,7 +46,7 @@ class ClearableFileInputTest(WidgetTest):
         self.check_html(ClearableFileInput(), 'my<div>file', StrangeFieldFile(), html=(
         self.check_html(ClearableFileInput(), 'my<div>file', StrangeFieldFile(), html=(
             """
             """
             Currently: <a href="something?chapter=1&amp;sect=2&amp;copy=3&amp;lang=en">
             Currently: <a href="something?chapter=1&amp;sect=2&amp;copy=3&amp;lang=en">
-            something&lt;div onclick=&quot;alert(&#39;oops&#39;)&quot;&gt;.jpg</a>
+            something&lt;div onclick=&quot;alert(&#x27;oops&#x27;)&quot;&gt;.jpg</a>
             <input type="checkbox" name="my&lt;div&gt;file-clear" id="my&lt;div&gt;file-clear_id">
             <input type="checkbox" name="my&lt;div&gt;file-clear" id="my&lt;div&gt;file-clear_id">
             <label for="my&lt;div&gt;file-clear_id">Clear</label><br>
             <label for="my&lt;div&gt;file-clear_id">Clear</label><br>
             Change: <input type="file" name="my&lt;div&gt;file">
             Change: <input type="file" name="my&lt;div&gt;file">

+ 7 - 7
tests/model_forms/tests.py

@@ -1197,7 +1197,7 @@ class ModelFormBasicTests(TestCase):
 <li>Article: <textarea rows="10" cols="40" name="article" required></textarea></li>
 <li>Article: <textarea rows="10" cols="40" name="article" required></textarea></li>
 <li>Categories: <select multiple name="categories">
 <li>Categories: <select multiple name="categories">
 <option value="%s" selected>Entertainment</option>
 <option value="%s" selected>Entertainment</option>
-<option value="%s" selected>It&#39;s a test</option>
+<option value="%s" selected>It&#x27;s a test</option>
 <option value="%s">Third test</option>
 <option value="%s">Third test</option>
 </select></li>
 </select></li>
 <li>Status: <select name="status">
 <li>Status: <select name="status">
@@ -1239,7 +1239,7 @@ class ModelFormBasicTests(TestCase):
 <li>Article: <textarea rows="10" cols="40" name="article" required>Hello.</textarea></li>
 <li>Article: <textarea rows="10" cols="40" name="article" required>Hello.</textarea></li>
 <li>Categories: <select multiple name="categories">
 <li>Categories: <select multiple name="categories">
 <option value="%s">Entertainment</option>
 <option value="%s">Entertainment</option>
-<option value="%s">It&#39;s a test</option>
+<option value="%s">It&#x27;s a test</option>
 <option value="%s">Third test</option>
 <option value="%s">Third test</option>
 </select></li>
 </select></li>
 <li>Status: <select name="status">
 <li>Status: <select name="status">
@@ -1290,7 +1290,7 @@ class ModelFormBasicTests(TestCase):
 <li><label for="id_categories">Categories:</label>
 <li><label for="id_categories">Categories:</label>
 <select multiple name="categories" id="id_categories">
 <select multiple name="categories" id="id_categories">
 <option value="%d" selected>Entertainment</option>
 <option value="%d" selected>Entertainment</option>
-<option value="%d" selected>It&39;s a test</option>
+<option value="%d" selected>It&#x27;s a test</option>
 <option value="%d">Third test</option>
 <option value="%d">Third test</option>
 </select></li>"""
 </select></li>"""
             % (self.c1.pk, self.c2.pk, self.c3.pk))
             % (self.c1.pk, self.c2.pk, self.c3.pk))
@@ -1361,7 +1361,7 @@ class ModelFormBasicTests(TestCase):
 <tr><th>Article:</th><td><textarea rows="10" cols="40" name="article" required></textarea></td></tr>
 <tr><th>Article:</th><td><textarea rows="10" cols="40" name="article" required></textarea></td></tr>
 <tr><th>Categories:</th><td><select multiple name="categories">
 <tr><th>Categories:</th><td><select multiple name="categories">
 <option value="%s">Entertainment</option>
 <option value="%s">Entertainment</option>
-<option value="%s">It&#39;s a test</option>
+<option value="%s">It&#x27;s a test</option>
 <option value="%s">Third test</option>
 <option value="%s">Third test</option>
 </select></td></tr>
 </select></td></tr>
 <tr><th>Status:</th><td><select name="status">
 <tr><th>Status:</th><td><select name="status">
@@ -1391,7 +1391,7 @@ class ModelFormBasicTests(TestCase):
 <li>Article: <textarea rows="10" cols="40" name="article" required>Hello.</textarea></li>
 <li>Article: <textarea rows="10" cols="40" name="article" required>Hello.</textarea></li>
 <li>Categories: <select multiple name="categories">
 <li>Categories: <select multiple name="categories">
 <option value="%s" selected>Entertainment</option>
 <option value="%s" selected>Entertainment</option>
-<option value="%s">It&#39;s a test</option>
+<option value="%s">It&#x27;s a test</option>
 <option value="%s">Third test</option>
 <option value="%s">Third test</option>
 </select></li>
 </select></li>
 <li>Status: <select name="status">
 <li>Status: <select name="status">
@@ -1535,7 +1535,7 @@ class ModelFormBasicTests(TestCase):
 <li>Article: <textarea rows="10" cols="40" name="article" required></textarea></li>
 <li>Article: <textarea rows="10" cols="40" name="article" required></textarea></li>
 <li>Categories: <select multiple name="categories">
 <li>Categories: <select multiple name="categories">
 <option value="%s">Entertainment</option>
 <option value="%s">Entertainment</option>
-<option value="%s">It&#39;s a test</option>
+<option value="%s">It&#x27;s a test</option>
 <option value="%s">Third test</option>
 <option value="%s">Third test</option>
 </select> </li>
 </select> </li>
 <li>Status: <select name="status">
 <li>Status: <select name="status">
@@ -1561,7 +1561,7 @@ class ModelFormBasicTests(TestCase):
 <li>Article: <textarea rows="10" cols="40" name="article" required></textarea></li>
 <li>Article: <textarea rows="10" cols="40" name="article" required></textarea></li>
 <li>Categories: <select multiple name="categories">
 <li>Categories: <select multiple name="categories">
 <option value="%s">Entertainment</option>
 <option value="%s">Entertainment</option>
-<option value="%s">It&#39;s a test</option>
+<option value="%s">It&#x27;s a test</option>
 <option value="%s">Third test</option>
 <option value="%s">Third test</option>
 <option value="%s">Fourth</option>
 <option value="%s">Fourth</option>
 </select></li>
 </select></li>

+ 1 - 1
tests/template_tests/filter_tests/test_addslashes.py

@@ -15,7 +15,7 @@ class AddslashesTests(SimpleTestCase):
     @setup({'addslashes02': '{{ a|addslashes }} {{ b|addslashes }}'})
     @setup({'addslashes02': '{{ a|addslashes }} {{ b|addslashes }}'})
     def test_addslashes02(self):
     def test_addslashes02(self):
         output = self.engine.render_to_string('addslashes02', {"a": "<a>'", "b": mark_safe("<a>'")})
         output = self.engine.render_to_string('addslashes02', {"a": "<a>'", "b": mark_safe("<a>'")})
-        self.assertEqual(output, r"&lt;a&gt;\&#39; <a>\'")
+        self.assertEqual(output, r"&lt;a&gt;\&#x27; <a>\'")
 
 
 
 
 class FunctionTests(SimpleTestCase):
 class FunctionTests(SimpleTestCase):

+ 1 - 1
tests/template_tests/filter_tests/test_make_list.py

@@ -19,7 +19,7 @@ class MakeListTests(SimpleTestCase):
     @setup({'make_list02': '{{ a|make_list }}'})
     @setup({'make_list02': '{{ a|make_list }}'})
     def test_make_list02(self):
     def test_make_list02(self):
         output = self.engine.render_to_string('make_list02', {"a": mark_safe("&")})
         output = self.engine.render_to_string('make_list02', {"a": mark_safe("&")})
-        self.assertEqual(output, "[&#39;&amp;&#39;]")
+        self.assertEqual(output, '[&#x27;&amp;&#x27;]')
 
 
     @setup({'make_list03': '{% autoescape off %}{{ a|make_list|stringformat:"s"|safe }}{% endautoescape %}'})
     @setup({'make_list03': '{% autoescape off %}{{ a|make_list|stringformat:"s"|safe }}{% endautoescape %}'})
     def test_make_list03(self):
     def test_make_list03(self):

+ 1 - 1
tests/template_tests/filter_tests/test_title.py

@@ -9,7 +9,7 @@ class TitleTests(SimpleTestCase):
     @setup({'title1': '{{ a|title }}'})
     @setup({'title1': '{{ a|title }}'})
     def test_title1(self):
     def test_title1(self):
         output = self.engine.render_to_string('title1', {'a': 'JOE\'S CRAB SHACK'})
         output = self.engine.render_to_string('title1', {'a': 'JOE\'S CRAB SHACK'})
-        self.assertEqual(output, 'Joe&#39;s Crab Shack')
+        self.assertEqual(output, 'Joe&#x27;s Crab Shack')
 
 
     @setup({'title2': '{{ a|title }}'})
     @setup({'title2': '{{ a|title }}'})
     def test_title2(self):
     def test_title2(self):

+ 3 - 3
tests/template_tests/filter_tests/test_urlize.py

@@ -52,7 +52,7 @@ class UrlizeTests(SimpleTestCase):
     @setup({'urlize06': '{{ a|urlize }}'})
     @setup({'urlize06': '{{ a|urlize }}'})
     def test_urlize06(self):
     def test_urlize06(self):
         output = self.engine.render_to_string('urlize06', {'a': "<script>alert('foo')</script>"})
         output = self.engine.render_to_string('urlize06', {'a': "<script>alert('foo')</script>"})
-        self.assertEqual(output, '&lt;script&gt;alert(&#39;foo&#39;)&lt;/script&gt;')
+        self.assertEqual(output, '&lt;script&gt;alert(&#x27;foo&#x27;)&lt;/script&gt;')
 
 
     # mailto: testing for urlize
     # mailto: testing for urlize
     @setup({'urlize07': '{{ a|urlize }}'})
     @setup({'urlize07': '{{ a|urlize }}'})
@@ -113,7 +113,7 @@ class FunctionTests(SimpleTestCase):
         )
         )
         self.assertEqual(
         self.assertEqual(
             urlize('www.server.com\'abc'),
             urlize('www.server.com\'abc'),
-            '<a href="http://www.server.com" rel="nofollow">www.server.com</a>&#39;abc',
+            '<a href="http://www.server.com" rel="nofollow">www.server.com</a>&#x27;abc',
         )
         )
         self.assertEqual(
         self.assertEqual(
             urlize('www.server.com<abc'),
             urlize('www.server.com<abc'),
@@ -284,7 +284,7 @@ class FunctionTests(SimpleTestCase):
             ('<>', ('&lt;', '&gt;')),
             ('<>', ('&lt;', '&gt;')),
             ('[]', ('[', ']')),
             ('[]', ('[', ']')),
             ('""', ('&quot;', '&quot;')),
             ('""', ('&quot;', '&quot;')),
-            ("''", ('&#39;', '&#39;')),
+            ("''", ('&#x27;', '&#x27;')),
         )
         )
         for wrapping_in, (start_out, end_out) in wrapping_chars:
         for wrapping_in, (start_out, end_out) in wrapping_chars:
             with self.subTest(wrapping_in=wrapping_in):
             with self.subTest(wrapping_in=wrapping_in):

+ 1 - 1
tests/template_tests/syntax_tests/test_url.py

@@ -78,7 +78,7 @@ class UrlTagTests(SimpleTestCase):
     @setup({'url12': '{% url "client_action" id=client.id action="!$&\'()*+,;=~:@," %}'})
     @setup({'url12': '{% url "client_action" id=client.id action="!$&\'()*+,;=~:@," %}'})
     def test_url12(self):
     def test_url12(self):
         output = self.engine.render_to_string('url12', {'client': {'id': 1}})
         output = self.engine.render_to_string('url12', {'client': {'id': 1}})
-        self.assertEqual(output, '/client/1/!$&amp;&#39;()*+,;=~:@,/')
+        self.assertEqual(output, '/client/1/!$&amp;&#x27;()*+,;=~:@,/')
 
 
     @setup({'url13': '{% url "client_action" id=client.id action=arg|join:"-" %}'})
     @setup({'url13': '{% url "client_action" id=client.id action=arg|join:"-" %}'})
     def test_url13(self):
     def test_url13(self):

+ 3 - 1
tests/utils_tests/test_html.py

@@ -27,7 +27,7 @@ class TestUtilsHtml(SimpleTestCase):
             ('<', '&lt;'),
             ('<', '&lt;'),
             ('>', '&gt;'),
             ('>', '&gt;'),
             ('"', '&quot;'),
             ('"', '&quot;'),
-            ("'", '&#39;'),
+            ("'", '&#x27;'),
         )
         )
         # Substitution patterns for testing the above items.
         # Substitution patterns for testing the above items.
         patterns = ("%s", "asdf%sfdsa", "%s1", "1%sb")
         patterns = ("%s", "asdf%sfdsa", "%s1", "1%sb")
@@ -70,6 +70,8 @@ class TestUtilsHtml(SimpleTestCase):
         items = (
         items = (
             ('<p>See: &#39;&eacute; is an apostrophe followed by e acute</p>',
             ('<p>See: &#39;&eacute; is an apostrophe followed by e acute</p>',
              'See: &#39;&eacute; is an apostrophe followed by e acute'),
              'See: &#39;&eacute; is an apostrophe followed by e acute'),
+            ('<p>See: &#x27;&eacute; is an apostrophe followed by e acute</p>',
+             'See: &#x27;&eacute; is an apostrophe followed by e acute'),
             ('<adf>a', 'a'),
             ('<adf>a', 'a'),
             ('</adf>a', 'a'),
             ('</adf>a', 'a'),
             ('<asdf><asdf>e', 'e'),
             ('<asdf><asdf>e', 'e'),

+ 4 - 4
tests/view_tests/tests/test_csrf.py

@@ -44,22 +44,22 @@ class CsrfViewTests(SimpleTestCase):
         self.assertContains(
         self.assertContains(
             response,
             response,
             'You are seeing this message because this HTTPS site requires a '
             'You are seeing this message because this HTTPS site requires a '
-            '&#39;Referer header&#39; to be sent by your Web browser, but '
+            '&#x27;Referer header&#x27; to be sent by your Web browser, but '
             'none was sent.',
             'none was sent.',
             status_code=403,
             status_code=403,
         )
         )
         self.assertContains(
         self.assertContains(
             response,
             response,
-            'If you have configured your browser to disable &#39;Referer&#39; '
+            'If you have configured your browser to disable &#x27;Referer&#x27; '
             'headers, please re-enable them, at least for this site, or for '
             'headers, please re-enable them, at least for this site, or for '
-            'HTTPS connections, or for &#39;same-origin&#39; requests.',
+            'HTTPS connections, or for &#x27;same-origin&#x27; requests.',
             status_code=403,
             status_code=403,
         )
         )
         self.assertContains(
         self.assertContains(
             response,
             response,
             'If you are using the &lt;meta name=&quot;referrer&quot; '
             'If you are using the &lt;meta name=&quot;referrer&quot; '
             'content=&quot;no-referrer&quot;&gt; tag or including the '
             'content=&quot;no-referrer&quot;&gt; tag or including the '
-            '&#39;Referrer-Policy: no-referrer&#39; header, please remove them.',
+            '&#x27;Referrer-Policy: no-referrer&#x27; header, please remove them.',
             status_code=403,
             status_code=403,
         )
         )
 
 

+ 7 - 7
tests/view_tests/tests/test_debug.py

@@ -304,7 +304,7 @@ class ExceptionReporterTests(SimpleTestCase):
         reporter = ExceptionReporter(request, exc_type, exc_value, tb)
         reporter = ExceptionReporter(request, exc_type, exc_value, tb)
         html = reporter.get_traceback_html()
         html = reporter.get_traceback_html()
         self.assertInHTML('<h1>ValueError at /test_view/</h1>', html)
         self.assertInHTML('<h1>ValueError at /test_view/</h1>', html)
-        self.assertIn('<pre class="exception_value">Can&#39;t find my keys</pre>', html)
+        self.assertIn('<pre class="exception_value">Can&#x27;t find my keys</pre>', html)
         self.assertIn('<th>Request Method:</th>', html)
         self.assertIn('<th>Request Method:</th>', html)
         self.assertIn('<th>Request URL:</th>', html)
         self.assertIn('<th>Request URL:</th>', html)
         self.assertIn('<h3 id="user-info">USER</h3>', html)
         self.assertIn('<h3 id="user-info">USER</h3>', html)
@@ -325,7 +325,7 @@ class ExceptionReporterTests(SimpleTestCase):
         reporter = ExceptionReporter(None, exc_type, exc_value, tb)
         reporter = ExceptionReporter(None, exc_type, exc_value, tb)
         html = reporter.get_traceback_html()
         html = reporter.get_traceback_html()
         self.assertInHTML('<h1>ValueError</h1>', html)
         self.assertInHTML('<h1>ValueError</h1>', html)
-        self.assertIn('<pre class="exception_value">Can&#39;t find my keys</pre>', html)
+        self.assertIn('<pre class="exception_value">Can&#x27;t find my keys</pre>', html)
         self.assertNotIn('<th>Request Method:</th>', html)
         self.assertNotIn('<th>Request Method:</th>', html)
         self.assertNotIn('<th>Request URL:</th>', html)
         self.assertNotIn('<th>Request URL:</th>', html)
         self.assertNotIn('<h3 id="user-info">USER</h3>', html)
         self.assertNotIn('<h3 id="user-info">USER</h3>', html)
@@ -463,7 +463,7 @@ class ExceptionReporterTests(SimpleTestCase):
         reporter = ExceptionReporter(request, None, "I'm a little teapot", None)
         reporter = ExceptionReporter(request, None, "I'm a little teapot", None)
         html = reporter.get_traceback_html()
         html = reporter.get_traceback_html()
         self.assertInHTML('<h1>Report at /test_view/</h1>', html)
         self.assertInHTML('<h1>Report at /test_view/</h1>', html)
-        self.assertIn('<pre class="exception_value">I&#39;m a little teapot</pre>', html)
+        self.assertIn('<pre class="exception_value">I&#x27;m a little teapot</pre>', html)
         self.assertIn('<th>Request Method:</th>', html)
         self.assertIn('<th>Request Method:</th>', html)
         self.assertIn('<th>Request URL:</th>', html)
         self.assertIn('<th>Request URL:</th>', html)
         self.assertNotIn('<th>Exception Type:</th>', html)
         self.assertNotIn('<th>Exception Type:</th>', html)
@@ -476,7 +476,7 @@ class ExceptionReporterTests(SimpleTestCase):
         reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
         reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
         html = reporter.get_traceback_html()
         html = reporter.get_traceback_html()
         self.assertInHTML('<h1>Report</h1>', html)
         self.assertInHTML('<h1>Report</h1>', html)
-        self.assertIn('<pre class="exception_value">I&#39;m a little teapot</pre>', html)
+        self.assertIn('<pre class="exception_value">I&#x27;m a little teapot</pre>', html)
         self.assertNotIn('<th>Request Method:</th>', html)
         self.assertNotIn('<th>Request Method:</th>', html)
         self.assertNotIn('<th>Request URL:</th>', html)
         self.assertNotIn('<th>Request URL:</th>', html)
         self.assertNotIn('<th>Exception Type:</th>', html)
         self.assertNotIn('<th>Exception Type:</th>', html)
@@ -508,7 +508,7 @@ class ExceptionReporterTests(SimpleTestCase):
         except Exception:
         except Exception:
             exc_type, exc_value, tb = sys.exc_info()
             exc_type, exc_value, tb = sys.exc_info()
         html = ExceptionReporter(None, exc_type, exc_value, tb).get_traceback_html()
         html = ExceptionReporter(None, exc_type, exc_value, tb).get_traceback_html()
-        self.assertIn('<td class="code"><pre>&#39;&lt;p&gt;Local variable&lt;/p&gt;&#39;</pre></td>', html)
+        self.assertIn('<td class="code"><pre>&#x27;&lt;p&gt;Local variable&lt;/p&gt;&#x27;</pre></td>', html)
 
 
     def test_unprintable_values_handling(self):
     def test_unprintable_values_handling(self):
         "Unprintable values should not make the output generation choke."
         "Unprintable values should not make the output generation choke."
@@ -607,7 +607,7 @@ class ExceptionReporterTests(SimpleTestCase):
         An exception report can be generated for requests with 'items' in
         An exception report can be generated for requests with 'items' in
         request GET, POST, FILES, or COOKIES QueryDicts.
         request GET, POST, FILES, or COOKIES QueryDicts.
         """
         """
-        value = '<td>items</td><td class="code"><pre>&#39;Oops&#39;</pre></td>'
+        value = '<td>items</td><td class="code"><pre>&#x27;Oops&#x27;</pre></td>'
         # GET
         # GET
         request = self.rf.get('/test_view/?items=Oops')
         request = self.rf.get('/test_view/?items=Oops')
         reporter = ExceptionReporter(request, None, None, None)
         reporter = ExceptionReporter(request, None, None, None)
@@ -634,7 +634,7 @@ class ExceptionReporterTests(SimpleTestCase):
         request = rf.get('/test_view/')
         request = rf.get('/test_view/')
         reporter = ExceptionReporter(request, None, None, None)
         reporter = ExceptionReporter(request, None, None, None)
         html = reporter.get_traceback_html()
         html = reporter.get_traceback_html()
-        self.assertInHTML('<td>items</td><td class="code"><pre>&#39;Oops&#39;</pre></td>', html)
+        self.assertInHTML('<td>items</td><td class="code"><pre>&#x27;Oops&#x27;</pre></td>', html)
 
 
     def test_exception_fetching_user(self):
     def test_exception_fetching_user(self):
         """
         """