diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 66e6ebdd76..2c9ef0975b 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -89,7 +89,16 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): # SafeUnicode at the end. s = s.decode(encoding, errors) except UnicodeDecodeError, e: - raise DjangoUnicodeDecodeError(s, *e.args) + if not isinstance(s, Exception): + raise DjangoUnicodeDecodeError(s, *e.args) + else: + # If we get to here, the caller has passed in an Exception + # subclass populated with non-ASCII bytestring data without a + # working unicode method. Try to handle this without raising a + # further exception by individually forcing the exception args + # to unicode. + s = ' '.join([force_unicode(arg, encoding, strings_only, + errors) for arg in s]) return s def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'): diff --git a/tests/regressiontests/debug/__init__.py b/tests/regressiontests/debug/__init__.py new file mode 100644 index 0000000000..7ca84ebc4b --- /dev/null +++ b/tests/regressiontests/debug/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf8 -*- + +class BrokenException(Exception): + pass + +except_args = ('Broken!', # plain exception with ASCII text + u'¡Broken!', # non-ASCII unicode data + '¡Broken!', # non-ASCII, utf-8 encoded bytestring + '\xa1Broken!', ) # non-ASCII, latin1 bytestring + diff --git a/tests/regressiontests/debug/models.py b/tests/regressiontests/debug/models.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/debug/tests.py b/tests/regressiontests/debug/tests.py new file mode 100644 index 0000000000..b99445d432 --- /dev/null +++ b/tests/regressiontests/debug/tests.py @@ -0,0 +1,21 @@ +from django.test import TestCase +from django.conf import settings +from django.core.urlresolvers import reverse + +from regressiontests.debug import BrokenException, except_args + +class ExceptionTest(TestCase): + urls = 'regressiontests.debug.urls' + + def setUp(self): + self.old_debug = settings.DEBUG + settings.DEBUG = True + + def tearDown(self): + settings.DEBUG = self.old_debug + + def test_view_exceptions(self): + for n in range(len(except_args)): + self.assertRaises(BrokenException, self.client.get, + reverse('view_exception', args=(n,))) + diff --git a/tests/regressiontests/debug/urls.py b/tests/regressiontests/debug/urls.py new file mode 100644 index 0000000000..762ab2d632 --- /dev/null +++ b/tests/regressiontests/debug/urls.py @@ -0,0 +1,5 @@ +from django.conf.urls.defaults import * + +urlpatterns = patterns('regressiontests.debug.views', + url(r'view_exception/(?P\d+)/$', 'view_exception', name='view_exception'), +) diff --git a/tests/regressiontests/debug/views.py b/tests/regressiontests/debug/views.py new file mode 100644 index 0000000000..e58e1ca637 --- /dev/null +++ b/tests/regressiontests/debug/views.py @@ -0,0 +1,5 @@ +from regressiontests.debug import BrokenException, except_args + +def view_exception(request, n): + raise BrokenException(except_args[int(n)]) +