diff --git a/django/utils/functional.py b/django/utils/functional.py index 177325dfb6..085ec40b63 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -93,13 +93,19 @@ def lazy(func, *resultclasses): if hasattr(cls, k): continue setattr(cls, k, meth) - cls._delegate_str = bytes in resultclasses - cls._delegate_unicode = six.text_type in resultclasses - assert not (cls._delegate_str and cls._delegate_unicode), "Cannot call lazy() with both str and unicode return types." - if cls._delegate_unicode: - cls.__unicode__ = cls.__unicode_cast - elif cls._delegate_str: - cls.__str__ = cls.__str_cast + cls._delegate_bytes = bytes in resultclasses + cls._delegate_text = six.text_type in resultclasses + assert not (cls._delegate_bytes and cls._delegate_text), "Cannot call lazy() with both bytes and text return types." + if cls._delegate_text: + if six.PY3: + cls.__str__ = cls.__text_cast + else: + cls.__unicode__ = cls.__text_cast + elif cls._delegate_bytes: + if six.PY3: + cls.__bytes__ = cls.__bytes_cast + else: + cls.__str__ = cls.__bytes_cast __prepare_class__ = classmethod(__prepare_class__) def __promise__(cls, klass, funcname, method): @@ -120,17 +126,17 @@ def lazy(func, *resultclasses): return __wrapper__ __promise__ = classmethod(__promise__) - def __unicode_cast(self): + def __text_cast(self): return func(*self.__args, **self.__kw) - def __str_cast(self): - return str(func(*self.__args, **self.__kw)) + def __bytes_cast(self): + return bytes(func(*self.__args, **self.__kw)) def __cast(self): - if self._delegate_str: - return self.__str_cast() - elif self._delegate_unicode: - return self.__unicode_cast() + if self._delegate_bytes: + return self.__bytes_cast() + elif self._delegate_text: + return self.__text_cast() else: return func(*self.__args, **self.__kw) @@ -144,10 +150,12 @@ def lazy(func, *resultclasses): other = other.__cast() return self.__cast() < other + __hash__ = object.__hash__ + def __mod__(self, rhs): - if self._delegate_str: - return str(self) % rhs - elif self._delegate_unicode: + if self._delegate_bytes and not six.PY3: + return bytes(self) % rhs + elif self._delegate_text: return six.text_type(self) % rhs else: raise AssertionError('__mod__ not supported for non-string types') @@ -234,6 +242,9 @@ class LazyObject(object): __dir__ = new_method_proxy(dir) +# Workaround for http://bugs.python.org/issue12370 +_super = super + class SimpleLazyObject(LazyObject): """ A lazy object initialised from any function. @@ -251,13 +262,17 @@ class SimpleLazyObject(LazyObject): value. """ self.__dict__['_setupfunc'] = func - super(SimpleLazyObject, self).__init__() + _super(SimpleLazyObject, self).__init__() def _setup(self): self._wrapped = self._setupfunc() - __str__ = new_method_proxy(bytes) - __unicode__ = new_method_proxy(six.text_type) + if six.PY3: + __bytes__ = new_method_proxy(bytes) + __str__ = new_method_proxy(str) + else: + __str__ = new_method_proxy(str) + __unicode__ = new_method_proxy(unicode) def __deepcopy__(self, memo): if self._wrapped is empty: @@ -284,7 +299,8 @@ class SimpleLazyObject(LazyObject): __class__ = property(new_method_proxy(operator.attrgetter("__class__"))) __eq__ = new_method_proxy(operator.eq) __hash__ = new_method_proxy(hash) - __nonzero__ = new_method_proxy(bool) + __bool__ = new_method_proxy(bool) # Python 3 + __nonzero__ = __bool__ # Python 2 class lazy_property(property): diff --git a/django/utils/safestring.py b/django/utils/safestring.py index 1599fc2a66..bfaefd07ee 100644 --- a/django/utils/safestring.py +++ b/django/utils/safestring.py @@ -96,7 +96,7 @@ def mark_safe(s): """ if isinstance(s, SafeData): return s - if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_str): + if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes): return SafeString(s) if isinstance(s, (six.text_type, Promise)): return SafeUnicode(s) @@ -112,7 +112,7 @@ def mark_for_escaping(s): """ if isinstance(s, (SafeData, EscapeData)): return s - if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_str): + if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes): return EscapeString(s) if isinstance(s, (six.text_type, Promise)): return EscapeUnicode(s) diff --git a/tests/regressiontests/utils/simplelazyobject.py b/tests/regressiontests/utils/simplelazyobject.py index 960a5e3201..3f81e8f608 100644 --- a/tests/regressiontests/utils/simplelazyobject.py +++ b/tests/regressiontests/utils/simplelazyobject.py @@ -19,17 +19,27 @@ class _ComplexObject(object): def __hash__(self): return hash(self.name) - def __str__(self): - return "I am _ComplexObject(%r)" % self.name + if six.PY3: + def __bytes__(self): + return ("I am _ComplexObject(%r)" % self.name).encode("utf-8") - def __unicode__(self): - return six.text_type(self.name) + def __str__(self): + return self.name + + else: + def __str__(self): + return b"I am _ComplexObject(%r)" % str(self.name) + + def __unicode__(self): + return self.name def __repr__(self): return "_ComplexObject(%r)" % self.name + complex_object = lambda: _ComplexObject("joe") + class TestUtilsSimpleLazyObject(TestCase): """ Tests for SimpleLazyObject @@ -54,11 +64,11 @@ class TestUtilsSimpleLazyObject(TestCase): # proxy __repr__ self.assertTrue("SimpleLazyObject" in repr(SimpleLazyObject(complex_object))) - def test_str(self): - self.assertEqual(str_prefix("I am _ComplexObject(%(_)s'joe')"), - str(SimpleLazyObject(complex_object))) + def test_bytes(self): + self.assertEqual(b"I am _ComplexObject('joe')", + bytes(SimpleLazyObject(complex_object))) - def test_unicode(self): + def test_text(self): self.assertEqual("joe", six.text_type(SimpleLazyObject(complex_object))) def test_class(self):