diff --git a/django/db/models/base.py b/django/db/models/base.py index 33229f21f3..f6001b4e93 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -427,11 +427,6 @@ class Model(six.with_metaclass(ModelBase)): def __str__(self): if six.PY2 and hasattr(self, '__unicode__'): - if type(self).__unicode__ == Model.__str__: - klass_name = type(self).__name__ - raise RuntimeError("%s.__unicode__ is aliased to __str__. Did" - " you apply @python_2_unicode_compatible" - " without defining __str__?" % klass_name) return force_text(self).encode('utf-8') return '%s object' % self.__class__.__name__ diff --git a/django/utils/encoding.py b/django/utils/encoding.py index ad06336c4d..599952bdeb 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -52,6 +52,10 @@ def python_2_unicode_compatible(klass): returning text and apply this decorator to the class. """ if six.PY2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) klass.__unicode__ = klass.__str__ klass.__str__ = lambda self: self.__unicode__().encode('utf-8') return klass diff --git a/tests/commands_sql/models.py b/tests/commands_sql/models.py index d8f372b403..78ad722307 100644 --- a/tests/commands_sql/models.py +++ b/tests/commands_sql/models.py @@ -1,13 +1,10 @@ from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class Comment(models.Model): pass -@python_2_unicode_compatible class Book(models.Model): title = models.CharField(max_length=100, db_index=True) comments = models.ManyToManyField(Comment) diff --git a/tests/str/models.py b/tests/str/models.py index 1c158ea008..488012e861 100644 --- a/tests/str/models.py +++ b/tests/str/models.py @@ -27,14 +27,6 @@ class Article(models.Model): # in ASCII. return self.headline -@python_2_unicode_compatible -class BrokenArticle(models.Model): - headline = models.CharField(max_length=100) - pub_date = models.DateTimeField() - - def __unicode__(self): # instead of __str__ - return self.headline - @python_2_unicode_compatible class InternationalArticle(models.Model): headline = models.CharField(max_length=100) diff --git a/tests/str/tests.py b/tests/str/tests.py index bd85c48d05..2c11ac8c78 100644 --- a/tests/str/tests.py +++ b/tests/str/tests.py @@ -7,7 +7,7 @@ from django.test import TestCase from django.utils import six from django.utils.unittest import skipIf -from .models import Article, BrokenArticle, InternationalArticle +from .models import Article, InternationalArticle class SimpleTests(TestCase): @@ -21,16 +21,6 @@ class SimpleTests(TestCase): self.assertEqual(str(a), str('Area man programs in Python')) self.assertEqual(repr(a), str('')) - @skipIf(six.PY3, "tests Model's default __str__ method under Python 2") - def test_broken(self): - # Regression test for #19362. - a = BrokenArticle.objects.create( - headline='Girl wins €12.500 in lottery', - pub_date=datetime.datetime(2005, 7, 28) - ) - six.assertRaisesRegex(self, RuntimeError, "Did you apply " - "@python_2_unicode_compatible without defining __str__\?", str, a) - def test_international(self): a = InternationalArticle.objects.create( headline='Girl wins €12.500 in lottery', diff --git a/tests/utils_tests/test_encoding.py b/tests/utils_tests/test_encoding.py index 7aaba25a7a..df4ea1497a 100644 --- a/tests/utils_tests/test_encoding.py +++ b/tests/utils_tests/test_encoding.py @@ -2,7 +2,8 @@ from __future__ import unicode_literals from django.utils import unittest -from django.utils.encoding import force_bytes, filepath_to_uri +from django.utils.encoding import force_bytes, filepath_to_uri, python_2_unicode_compatible +from django.utils import six class TestEncodingUtils(unittest.TestCase): @@ -21,3 +22,10 @@ class TestEncodingUtils(unittest.TestCase): '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') + + @unittest.skipIf(six.PY3, "tests a class not defining __str__ under Python 2") + def test_decorated_class_without_str(self): + with self.assertRaises(ValueError): + @python_2_unicode_compatible + class NoStr(object): + pass