0
0
mirror of https://github.com/django/django.git synced 2024-12-01 15:42:04 +01:00

[py3] Ported django.utils.safestring.

Backwards compatibility aliases were created under Python 2.
This commit is contained in:
Aymeric Augustin 2012-08-18 16:04:06 +02:00
parent e41c308014
commit 547b181046
8 changed files with 82 additions and 54 deletions

View File

@ -38,7 +38,7 @@ from django.db.backends.mysql.creation import DatabaseCreation
from django.db.backends.mysql.introspection import DatabaseIntrospection from django.db.backends.mysql.introspection import DatabaseIntrospection
from django.db.backends.mysql.validation import DatabaseValidation from django.db.backends.mysql.validation import DatabaseValidation
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.safestring import SafeString, SafeUnicode from django.utils.safestring import SafeBytes, SafeText
from django.utils import six from django.utils import six
from django.utils import timezone from django.utils import timezone
@ -75,7 +75,7 @@ def adapt_datetime_with_timezone_support(value, conv):
# MySQLdb-1.2.1 returns TIME columns as timedelta -- they are more like # MySQLdb-1.2.1 returns TIME columns as timedelta -- they are more like
# timedelta in terms of actual behavior as they are signed and include days -- # timedelta in terms of actual behavior as they are signed and include days --
# and Django expects time, so we still need to override that. We also need to # and Django expects time, so we still need to override that. We also need to
# add special handling for SafeUnicode and SafeString as MySQLdb's type # add special handling for SafeText and SafeBytes as MySQLdb's type
# checking is too tight to catch those (see Django ticket #6052). # checking is too tight to catch those (see Django ticket #6052).
# Finally, MySQLdb always returns naive datetime objects. However, when # Finally, MySQLdb always returns naive datetime objects. However, when
# timezone support is active, Django expects timezone-aware datetime objects. # timezone support is active, Django expects timezone-aware datetime objects.
@ -402,8 +402,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
kwargs['client_flag'] = CLIENT.FOUND_ROWS kwargs['client_flag'] = CLIENT.FOUND_ROWS
kwargs.update(settings_dict['OPTIONS']) kwargs.update(settings_dict['OPTIONS'])
self.connection = Database.connect(**kwargs) self.connection = Database.connect(**kwargs)
self.connection.encoders[SafeUnicode] = self.connection.encoders[six.text_type] self.connection.encoders[SafeText] = self.connection.encoders[six.text_type]
self.connection.encoders[SafeString] = self.connection.encoders[bytes] self.connection.encoders[SafeBytes] = self.connection.encoders[bytes]
connection_created.send(sender=self.__class__, connection=self) connection_created.send(sender=self.__class__, connection=self)
cursor = self.connection.cursor() cursor = self.connection.cursor()
if new_connection: if new_connection:

View File

@ -14,7 +14,7 @@ from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
from django.db.backends.postgresql_psycopg2.version import get_version from django.db.backends.postgresql_psycopg2.version import get_version
from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
from django.utils.log import getLogger from django.utils.log import getLogger
from django.utils.safestring import SafeUnicode, SafeString from django.utils.safestring import SafeText, SafeBytes
from django.utils import six from django.utils import six
from django.utils.timezone import utc from django.utils.timezone import utc
@ -29,8 +29,8 @@ DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError IntegrityError = Database.IntegrityError
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
psycopg2.extensions.register_adapter(SafeString, psycopg2.extensions.QuotedString) psycopg2.extensions.register_adapter(SafeBytes, psycopg2.extensions.QuotedString)
psycopg2.extensions.register_adapter(SafeUnicode, psycopg2.extensions.QuotedString) psycopg2.extensions.register_adapter(SafeText, psycopg2.extensions.QuotedString)
logger = getLogger('django.db.backends') logger = getLogger('django.db.backends')

View File

@ -20,7 +20,7 @@ from django.db.backends.sqlite3.creation import DatabaseCreation
from django.db.backends.sqlite3.introspection import DatabaseIntrospection from django.db.backends.sqlite3.introspection import DatabaseIntrospection
from django.utils.dateparse import parse_date, parse_datetime, parse_time from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.safestring import SafeString from django.utils.safestring import SafeBytes
from django.utils import six from django.utils import six
from django.utils import timezone from django.utils import timezone
@ -80,7 +80,7 @@ if Database.version_info >= (2, 4, 1):
# slow-down, this adapter is only registered for sqlite3 versions # slow-down, this adapter is only registered for sqlite3 versions
# needing it (Python 2.6 and up). # needing it (Python 2.6 and up).
Database.register_adapter(str, lambda s: s.decode('utf-8')) Database.register_adapter(str, lambda s: s.decode('utf-8'))
Database.register_adapter(SafeString, lambda s: s.decode('utf-8')) Database.register_adapter(SafeBytes, lambda s: s.decode('utf-8'))
class DatabaseFeatures(BaseDatabaseFeatures): class DatabaseFeatures(BaseDatabaseFeatures):
# SQLite cannot handle us only partially reading from a cursor's result set # SQLite cannot handle us only partially reading from a cursor's result set

View File

@ -119,8 +119,8 @@ def force_text(s, encoding='utf-8', strings_only=False, errors='strict'):
errors) for arg in s]) errors) for arg in s])
else: else:
# Note: We use .decode() here, instead of six.text_type(s, encoding, # Note: We use .decode() here, instead of six.text_type(s, encoding,
# errors), so that if s is a SafeString, it ends up being a # errors), so that if s is a SafeBytes, it ends up being a
# SafeUnicode at the end. # SafeText at the end.
s = s.decode(encoding, errors) s = s.decode(encoding, errors)
except UnicodeDecodeError as e: except UnicodeDecodeError as e:
if not isinstance(s, Exception): if not isinstance(s, Exception):

View File

@ -10,36 +10,43 @@ from django.utils import six
class EscapeData(object): class EscapeData(object):
pass pass
class EscapeString(bytes, EscapeData): class EscapeBytes(bytes, EscapeData):
""" """
A string that should be HTML-escaped when output. A byte string that should be HTML-escaped when output.
""" """
pass pass
class EscapeUnicode(six.text_type, EscapeData): class EscapeText(six.text_type, EscapeData):
""" """
A unicode object that should be HTML-escaped when output. A unicode string object that should be HTML-escaped when output.
""" """
pass pass
if six.PY3:
EscapeString = EscapeText
else:
EscapeString = EscapeBytes
# backwards compatibility for Python 2
EscapeUnicode = EscapeText
class SafeData(object): class SafeData(object):
pass pass
class SafeString(bytes, SafeData): class SafeBytes(bytes, SafeData):
""" """
A string subclass that has been specifically marked as "safe" (requires no A bytes subclass that has been specifically marked as "safe" (requires no
further escaping) for HTML output purposes. further escaping) for HTML output purposes.
""" """
def __add__(self, rhs): def __add__(self, rhs):
""" """
Concatenating a safe string with another safe string or safe unicode Concatenating a safe byte string with another safe byte string or safe
object is safe. Otherwise, the result is no longer safe. unicode string is safe. Otherwise, the result is no longer safe.
""" """
t = super(SafeString, self).__add__(rhs) t = super(SafeBytes, self).__add__(rhs)
if isinstance(rhs, SafeUnicode): if isinstance(rhs, SafeText):
return SafeUnicode(t) return SafeText(t)
elif isinstance(rhs, SafeString): elif isinstance(rhs, SafeBytes):
return SafeString(t) return SafeBytes(t)
return t return t
def _proxy_method(self, *args, **kwargs): def _proxy_method(self, *args, **kwargs):
@ -51,25 +58,25 @@ class SafeString(bytes, SafeData):
method = kwargs.pop('method') method = kwargs.pop('method')
data = method(self, *args, **kwargs) data = method(self, *args, **kwargs)
if isinstance(data, bytes): if isinstance(data, bytes):
return SafeString(data) return SafeBytes(data)
else: else:
return SafeUnicode(data) return SafeText(data)
decode = curry(_proxy_method, method=bytes.decode) decode = curry(_proxy_method, method=bytes.decode)
class SafeUnicode(six.text_type, SafeData): class SafeText(six.text_type, SafeData):
""" """
A unicode subclass that has been specifically marked as "safe" for HTML A unicode (Python 2) / str (Python 3) subclass that has been specifically
output purposes. marked as "safe" for HTML output purposes.
""" """
def __add__(self, rhs): def __add__(self, rhs):
""" """
Concatenating a safe unicode object with another safe string or safe Concatenating a safe unicode string with another safe byte string or
unicode object is safe. Otherwise, the result is no longer safe. safe unicode string is safe. Otherwise, the result is no longer safe.
""" """
t = super(SafeUnicode, self).__add__(rhs) t = super(SafeText, self).__add__(rhs)
if isinstance(rhs, SafeData): if isinstance(rhs, SafeData):
return SafeUnicode(t) return SafeText(t)
return t return t
def _proxy_method(self, *args, **kwargs): def _proxy_method(self, *args, **kwargs):
@ -81,12 +88,19 @@ class SafeUnicode(six.text_type, SafeData):
method = kwargs.pop('method') method = kwargs.pop('method')
data = method(self, *args, **kwargs) data = method(self, *args, **kwargs)
if isinstance(data, bytes): if isinstance(data, bytes):
return SafeString(data) return SafeBytes(data)
else: else:
return SafeUnicode(data) return SafeText(data)
encode = curry(_proxy_method, method=six.text_type.encode) encode = curry(_proxy_method, method=six.text_type.encode)
if six.PY3:
SafeString = SafeText
else:
SafeString = SafeBytes
# backwards compatibility for Python 2
SafeUnicode = SafeText
def mark_safe(s): def mark_safe(s):
""" """
Explicitly mark a string as safe for (HTML) output purposes. The returned Explicitly mark a string as safe for (HTML) output purposes. The returned
@ -97,10 +111,10 @@ def mark_safe(s):
if isinstance(s, SafeData): if isinstance(s, SafeData):
return s return s
if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes): if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes):
return SafeString(s) return SafeBytes(s)
if isinstance(s, (six.text_type, Promise)): if isinstance(s, (six.text_type, Promise)):
return SafeUnicode(s) return SafeText(s)
return SafeString(bytes(s)) return SafeString(str(s))
def mark_for_escaping(s): def mark_for_escaping(s):
""" """
@ -113,8 +127,8 @@ def mark_for_escaping(s):
if isinstance(s, (SafeData, EscapeData)): if isinstance(s, (SafeData, EscapeData)):
return s return s
if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes): if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes):
return EscapeString(s) return EscapeBytes(s)
if isinstance(s, (six.text_type, Promise)): if isinstance(s, (six.text_type, Promise)):
return EscapeUnicode(s) return EscapeText(s)
return EscapeString(bytes(s)) return EscapeBytes(bytes(s))

View File

@ -189,7 +189,7 @@ passed around inside the template code:
They're commonly used for output that contains raw HTML that is intended They're commonly used for output that contains raw HTML that is intended
to be interpreted as-is on the client side. to be interpreted as-is on the client side.
Internally, these strings are of type ``SafeString`` or ``SafeUnicode``. Internally, these strings are of type ``SafeBytes`` or ``SafeText``.
They share a common base class of ``SafeData``, so you can test They share a common base class of ``SafeData``, so you can test
for them using code like: for them using code like:
@ -204,8 +204,8 @@ passed around inside the template code:
not. These strings are only escaped once, however, even if auto-escaping not. These strings are only escaped once, however, even if auto-escaping
applies. applies.
Internally, these strings are of type ``EscapeString`` or Internally, these strings are of type ``EscapeBytes`` or
``EscapeUnicode``. Generally you don't have to worry about these; they ``EscapeText``. Generally you don't have to worry about these; they
exist for the implementation of the :tfilter:`escape` filter. exist for the implementation of the :tfilter:`escape` filter.
Template filter code falls into one of two situations: Template filter code falls into one of two situations:

View File

@ -560,15 +560,29 @@ 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 that should not be interpreted by the HTML engine (e.g. '<') into the
appropriate entities. appropriate entities.
.. class:: SafeBytes
.. versionadded:: 1.5
A :class:`bytes` subclass that has been specifically marked as "safe"
(requires no further escaping) for HTML output purposes.
.. class:: SafeString .. class:: SafeString
A string subclass that has been specifically marked as "safe" (requires no A :class:`str` subclass that has been specifically marked as "safe"
further escaping) for HTML output purposes. (requires no further escaping) for HTML output purposes. This is
:class:`SafeBytes` on Python 2 and :class:`SafeText` on Python 3.
.. class:: SafeText
.. versionadded:: 1.5
A :class:`str` (in Python 3) or :class:`unicode` (in Python 2) subclass
that has been specifically marked as "safe" for HTML output purposes.
.. class:: SafeUnicode .. class:: SafeUnicode
A unicode subclass that has been specifically marked as "safe" for HTML Historical name of :class:`SafeText`. Only available under Python 2.
output purposes.
.. function:: mark_safe(s) .. function:: mark_safe(s)

View File

@ -18,7 +18,7 @@ from django.utils.formats import (get_format, date_format, time_format,
number_format) number_format)
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.utils.numberformat import format as nformat from django.utils.numberformat import format as nformat
from django.utils.safestring import mark_safe, SafeString, SafeUnicode from django.utils.safestring import mark_safe, SafeBytes, SafeString, SafeText
from django.utils import six from django.utils import six
from django.utils.six import PY3 from django.utils.six import PY3
from django.utils.translation import (ugettext, ugettext_lazy, activate, from django.utils.translation import (ugettext, ugettext_lazy, activate,
@ -235,9 +235,9 @@ class TranslationTests(TestCase):
s = mark_safe(str('Password')) s = mark_safe(str('Password'))
self.assertEqual(SafeString, type(s)) self.assertEqual(SafeString, type(s))
with translation.override('de', deactivate=True): with translation.override('de', deactivate=True):
self.assertEqual(SafeUnicode, type(ugettext(s))) self.assertEqual(SafeText, type(ugettext(s)))
self.assertEqual('aPassword', SafeString('a') + s) self.assertEqual('aPassword', SafeText('a') + s)
self.assertEqual('Passworda', s + SafeString('a')) self.assertEqual('Passworda', s + SafeText('a'))
self.assertEqual('Passworda', s + mark_safe('a')) self.assertEqual('Passworda', s + mark_safe('a'))
self.assertEqual('aPassword', mark_safe('a') + s) self.assertEqual('aPassword', mark_safe('a') + s)
self.assertEqual('as', mark_safe('a') + mark_safe('s')) self.assertEqual('as', mark_safe('a') + mark_safe('s'))
@ -897,9 +897,9 @@ class TestModels(TestCase):
def test_safestr(self): def test_safestr(self):
c = Company(cents_paid=12, products_delivered=1) c = Company(cents_paid=12, products_delivered=1)
c.name = SafeUnicode('Iñtërnâtiônàlizætiøn1') c.name = SafeText('Iñtërnâtiônàlizætiøn1')
c.save() c.save()
c.name = SafeString('Iñtërnâtiônàlizætiøn1'.encode('utf-8')) c.name = SafeBytes('Iñtërnâtiônàlizætiøn1'.encode('utf-8'))
c.save() c.save()