diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
index 2459748f1b..7dab1cb364 100644
--- a/django/db/backends/postgresql/base.py
+++ b/django/db/backends/postgresql/base.py
@@ -13,7 +13,7 @@ from django.db import DEFAULT_DB_ALIAS
from django.db.backends.base.base import BaseDatabaseWrapper
from django.db.utils import DatabaseError as WrappedDatabaseError
from django.utils.functional import cached_property
-from django.utils.safestring import SafeBytes, SafeText
+from django.utils.safestring import SafeText
try:
import psycopg2 as Database
@@ -44,7 +44,6 @@ from .schema import DatabaseSchemaEditor # NOQA isort:skip
from .utils import utc_tzinfo_factory # NOQA isort:skip
from .version import get_version # NOQA isort:skip
-psycopg2.extensions.register_adapter(SafeBytes, psycopg2.extensions.QuotedString)
psycopg2.extensions.register_adapter(SafeText, psycopg2.extensions.QuotedString)
psycopg2.extras.register_uuid()
diff --git a/django/utils/encoding.py b/django/utils/encoding.py
index e889d5f3a9..ed0a97e5eb 100644
--- a/django/utils/encoding.py
+++ b/django/utils/encoding.py
@@ -68,10 +68,7 @@ def force_text(s, encoding='utf-8', strings_only=False, errors='strict'):
else:
s = str(s)
else:
- # Note: We use .decode() here, instead of str(s, encoding,
- # errors), so that if s is a SafeBytes, it ends up being a
- # SafeText at the end.
- s = s.decode(encoding, errors)
+ str(s, encoding, errors)
except UnicodeDecodeError as e:
if not isinstance(s, Exception):
raise DjangoUnicodeDecodeError(s, *e.args)
@@ -209,7 +206,7 @@ def escape_uri_path(path):
# and "?" according to section 3.3 of RFC 2396.
# The reason for not subtracting and escaping "/" is that we are escaping
# the entire path, not a path segment.
- return quote(force_bytes(path), safe=b"/:@&+$,-_.!~*'()")
+ return quote(path, safe="/:@&+$,-_.!~*'()")
def repercent_broken_unicode(path):
@@ -231,20 +228,18 @@ def filepath_to_uri(path):
"""Convert a file system path to a URI portion that is suitable for
inclusion in a URL.
- Assume the input is either UTF-8 bytes or a string.
-
This method will encode certain chars that would normally be recognized as
special chars for URIs. Note that this method does not encode the '
character, as it is a valid character within URIs. See
encodeURIComponent() JavaScript function for more details.
- Returns an ASCII string containing the encoded result.
+ Return a string containing the result.
"""
if path is None:
return path
# I know about `os.sep` and `os.altsep` but I want to leave
# some flexibility for hardcoding separators.
- return quote(force_bytes(path).replace(b"\\", b"/"), safe=b"/~!*()'")
+ return quote(path.replace("\\", "/"), safe="/~!*()'")
def get_system_encoding():
diff --git a/django/utils/safestring.py b/django/utils/safestring.py
index 2aed4a3cb9..5f5e7098f2 100644
--- a/django/utils/safestring.py
+++ b/django/utils/safestring.py
@@ -5,7 +5,7 @@ that the producer of the string has already turned characters that should not
be interpreted by the HTML engine (e.g. '<') into the appropriate entities.
"""
-from django.utils.functional import Promise, curry, wraps
+from django.utils.functional import Promise, wraps
class SafeData:
@@ -22,6 +22,9 @@ class SafeBytes(bytes, SafeData):
"""
A bytes subclass that has been specifically marked as "safe" (requires no
further escaping) for HTML output purposes.
+
+ Kept in Django 2.0 for usage by apps supporting Python 2. Shouldn't be used
+ in Django anymore.
"""
def __add__(self, rhs):
"""
@@ -35,20 +38,6 @@ class SafeBytes(bytes, SafeData):
return SafeBytes(t)
return t
- def _proxy_method(self, *args, **kwargs):
- """
- Wrap a call to a normal bytes method up so that the result is safe.
- The method that is being wrapped is passed in the 'method' argument.
- """
- method = kwargs.pop('method')
- data = method(self, *args, **kwargs)
- if isinstance(data, bytes):
- return SafeBytes(data)
- else:
- return SafeText(data)
-
- decode = curry(_proxy_method, method=bytes.decode)
-
class SafeText(str, SafeData):
"""
@@ -65,20 +54,6 @@ class SafeText(str, SafeData):
return SafeText(t)
return t
- def _proxy_method(self, *args, **kwargs):
- """
- Wrap a call to a normal str method up so that the result is safe.
- The method that is being wrapped is passed in the 'method' argument.
- """
- method = kwargs.pop('method')
- data = method(self, *args, **kwargs)
- if isinstance(data, bytes):
- return SafeBytes(data)
- else:
- return SafeText(data)
-
- encode = curry(_proxy_method, method=str.encode)
-
SafeString = SafeText
@@ -101,10 +76,8 @@ def mark_safe(s):
"""
if hasattr(s, '__html__'):
return s
- if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes):
- return SafeBytes(s)
if isinstance(s, (str, Promise)):
return SafeText(s)
if callable(s):
return _safety_decorator(mark_safe, s)
- return SafeString(str(s))
+ return SafeText(str(s))
diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt
index 9ce743549d..d568292229 100644
--- a/docs/howto/custom-template-tags.txt
+++ b/docs/howto/custom-template-tags.txt
@@ -199,11 +199,13 @@ passed around inside the template code:
They're commonly used for output that contains raw HTML that is intended
to be interpreted as-is on the client side.
- Internally, these strings are of type ``SafeBytes`` or ``SafeText``.
- They share a common base class of ``SafeData``, so you can test
- for them using code like::
+ Internally, these strings are of type
+ :class:`~django.utils.safestring.SafeText`. You can test for them
+ using code like::
- if isinstance(value, SafeData):
+ from django.utils.safestring import SafeText
+
+ if isinstance(value, SafeText):
# Do something with the "safe" string.
...
diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt
index cd5bbee707..1e37d28ca8 100644
--- a/docs/ref/utils.txt
+++ b/docs/ref/utils.txt
@@ -763,20 +763,12 @@ string" means that the producer of the string has already turned characters
that should not be interpreted by the HTML engine (e.g. '<') into the
appropriate entities.
-.. class:: SafeBytes
-
- A ``bytes`` subclass that has been specifically marked as "safe"
- (requires no further escaping) for HTML output purposes.
-
.. class:: SafeString
A ``str`` subclass that has been specifically marked as "safe"
(requires no further escaping) for HTML output purposes. Alias of
:class:`SafeText`.
- Alias of :class:`SafeBytes` on Python 2 (in older versions of Django that
- support it).
-
.. class:: SafeText
A ``str`` subclass that has been specifically marked as "safe" for HTML
@@ -799,7 +791,7 @@ appropriate entities.
>>> mystr = 'Hello World '
>>> mystr = mark_safe(mystr)
>>> type(mystr)
-
+
>>> mystr = mystr.strip() # removing whitespace
>>> type(mystr)
diff --git a/docs/releases/2.0.txt b/docs/releases/2.0.txt
index 7cb9b5da73..dec2b85228 100644
--- a/docs/releases/2.0.txt
+++ b/docs/releases/2.0.txt
@@ -205,6 +205,16 @@ Validators
Backwards incompatible changes in 2.0
=====================================
+Removed support for bytestrings in some places
+----------------------------------------------
+
+To support native Python 2 strings, older Django versions had to accept both
+bytestrings and unicode strings. Now that Python 2 support is dropped,
+bytestrings should only be encountered around input/output boundaries (handling
+of binary fields or HTTP streams, for example). You might have to update your
+code to limit bytestring usage to a minimum, as Django no longer accepts
+bytestrings in certain code paths.
+
Database backend API
--------------------
diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py
index acff0dfc28..d2b878b435 100644
--- a/tests/i18n/tests.py
+++ b/tests/i18n/tests.py
@@ -20,7 +20,7 @@ from django.utils.formats import (
localize_input, reset_format_cache, sanitize_separators, time_format,
)
from django.utils.numberformat import format as nformat
-from django.utils.safestring import SafeBytes, SafeText
+from django.utils.safestring import SafeText
from django.utils.translation import (
LANGUAGE_SESSION_KEY, activate, check_for_language, deactivate,
get_language, get_language_from_request, get_language_info, gettext_lazy,
@@ -1280,8 +1280,6 @@ class TestModels(TestCase):
c = Company(cents_paid=12, products_delivered=1)
c.name = SafeText('Iñtërnâtiônàlizætiøn1')
c.save()
- c.name = SafeBytes('Iñtërnâtiônàlizætiøn1'.encode('utf-8'))
- c.save()
class TestLanguageInfo(SimpleTestCase):
diff --git a/tests/utils_tests/test_encoding.py b/tests/utils_tests/test_encoding.py
index ea3b97ddf5..bd937bf960 100644
--- a/tests/utils_tests/test_encoding.py
+++ b/tests/utils_tests/test_encoding.py
@@ -62,10 +62,6 @@ class TestRFC3987IEncodingUtils(unittest.TestCase):
def test_filepath_to_uri(self):
self.assertEqual(filepath_to_uri('upload\\чубака.mp4'), 'upload/%D1%87%D1%83%D0%B1%D0%B0%D0%BA%D0%B0.mp4')
- self.assertEqual(
- filepath_to_uri('upload\\чубака.mp4'.encode('utf-8')),
- 'upload/%D1%87%D1%83%D0%B1%D0%B0%D0%BA%D0%B0.mp4'
- )
def test_iri_to_uri(self):
cases = [
diff --git a/tests/utils_tests/test_safestring.py b/tests/utils_tests/test_safestring.py
index d1ef28944e..d7afb38950 100644
--- a/tests/utils_tests/test_safestring.py
+++ b/tests/utils_tests/test_safestring.py
@@ -1,12 +1,9 @@
from django.template import Context, Template
from django.test import SimpleTestCase
from django.utils import html, text
-from django.utils.encoding import force_bytes
-from django.utils.functional import lazy, lazystr
+from django.utils.functional import lazystr
from django.utils.safestring import SafeData, mark_safe
-lazybytes = lazy(force_bytes, bytes)
-
class customescape(str):
def __html__(self):
@@ -37,10 +34,8 @@ class SafeStringTest(SimpleTestCase):
def test_mark_safe_lazy(self):
s = lazystr('a&b')
- b = lazybytes(b'a&b')
self.assertIsInstance(mark_safe(s), SafeData)
- self.assertIsInstance(mark_safe(b), SafeData)
self.assertRenderEqual('{{ s }}', 'a&b', s=mark_safe(s))
def test_mark_safe_object_implementing_dunder_str(self):