commit d71e54a8bba804fe10a8919fe21d0f2fae7cdc52
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date:   Sat Jan 4 20:52:24 2014 +0100

    Fixed #21733 -- Infinite recursion in @python_2_unicode_compatible.
    
    Applying the decorator twice to the same class now raises an exception
    instead of triggering an infinite recursion.
    
    Thanks ntucker for the report.

diff --git a/django/utils/encoding.py b/django/utils/encoding.py
index ee67df3..8a32d7f 100644
--- a/django/utils/encoding.py
+++ b/django/utils/encoding.py
@@ -34,8 +34,14 @@ def python_2_unicode_compatible(klass):
             raise ValueError("@python_2_unicode_compatible cannot be applied "
                              "to %s because it doesn't define __str__()." %
                              klass.__name__)
+        elif hasattr(klass.__str__, '_python_2_unicode_compatible'):
+            raise ValueError("@python_2_unicode_compatible was already "
+                             "applied to %s." % klass.__name__)
         klass.__unicode__ = klass.__str__
-        klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+        def new_str(self):
+            return klass.__unicode__(self).encode('utf-8')
+        new_str._python_2_unicode_compatible = None
+        klass.__str__ = new_str
     return klass
 
 
diff --git a/tests/utils_tests/test_encoding.py b/tests/utils_tests/test_encoding.py
index a91f596..1516637 100644
--- a/tests/utils_tests/test_encoding.py
+++ b/tests/utils_tests/test_encoding.py
@@ -1,4 +1,5 @@
 # -*- encoding: utf-8 -*-
+
 from __future__ import unicode_literals
 
 import unittest
@@ -47,7 +48,16 @@ class TestEncodingUtils(unittest.TestCase):
 
     @unittest.skipIf(six.PY3, "tests a class not defining __str__ under Python 2")
     def test_decorated_class_without_str(self):
-        with self.assertRaises(ValueError):
+        with six.assertRaisesRegex(self, ValueError, "doesn't define __str__"):
             @python_2_unicode_compatible
             class NoStr(object):
                 pass
+
+    @unittest.skipIf(six.PY3, "tests a class not redefining __str__ under Python 2")
+    def test_twice_decorated_class(self):
+        with six.assertRaisesRegex(self, ValueError, "already applied"):
+            @python_2_unicode_compatible
+            @python_2_unicode_compatible
+            class Str(object):
+                def __str__(self):
+                    return b''
