diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html
index 2b50015886..213650ccd4 100644
--- a/django/contrib/admin/templates/admin/index.html
+++ b/django/contrib/admin/templates/admin/index.html
@@ -73,7 +73,7 @@
{% endif %}
{% if entry.content_type %}
- {% filter capfirst %}{{ entry.content_type }}{% endfilter %}
+ {% filter capfirst %}{{ entry.content_type.name }}{% endfilter %}
{% else %}
{% trans 'Unknown content' %}
{% endif %}
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index 1d40f3f07d..324faa6f3e 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -71,11 +71,7 @@ class Permission(models.Model):
'codename')
def __str__(self):
- return "%s | %s | %s" % (
- self.content_type.app_label,
- self.content_type,
- self.name,
- )
+ return '%s | %s' % (self.content_type, self.name)
def natural_key(self):
return (self.codename,) + self.content_type.natural_key()
diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py
index 0ff0933faf..597a8faa32 100644
--- a/django/contrib/contenttypes/models.py
+++ b/django/contrib/contenttypes/models.py
@@ -142,7 +142,7 @@ class ContentType(models.Model):
unique_together = (('app_label', 'model'),)
def __str__(self):
- return self.name
+ return self.app_labeled_name
@property
def name(self):
@@ -151,6 +151,13 @@ class ContentType(models.Model):
return self.model
return str(model._meta.verbose_name)
+ @property
+ def app_labeled_name(self):
+ model = self.model_class()
+ if not model:
+ return self.model
+ return '%s | %s' % (model._meta.app_label, model._meta.verbose_name)
+
def model_class(self):
"""Return the model class for this type of content."""
try:
diff --git a/docs/releases/3.0.txt b/docs/releases/3.0.txt
index 6c5eb0ccb5..344302c460 100644
--- a/docs/releases/3.0.txt
+++ b/docs/releases/3.0.txt
@@ -285,7 +285,8 @@ Django 3.0, we're removing these APIs at this time.
Miscellaneous
-------------
-* ...
+* ``ContentType.__str__()`` now includes the model's ``app_label`` to
+ disambiguate model's with the same name in different apps.
.. _deprecated-features-3.0:
diff --git a/tests/contenttypes_tests/test_models.py b/tests/contenttypes_tests/test_models.py
index 91fdf8340f..0554d13184 100644
--- a/tests/contenttypes_tests/test_models.py
+++ b/tests/contenttypes_tests/test_models.py
@@ -201,7 +201,15 @@ class ContentTypesTests(TestCase):
def test_str(self):
ct = ContentType.objects.get(app_label='contenttypes_tests', model='site')
- self.assertEqual(str(ct), 'site')
+ self.assertEqual(str(ct), 'contenttypes_tests | site')
+
+ def test_app_labeled_name(self):
+ ct = ContentType.objects.get(app_label='contenttypes_tests', model='site')
+ self.assertEqual(ct.app_labeled_name, 'contenttypes_tests | site')
+
+ def test_app_labeled_name_unknown_model(self):
+ ct = ContentType(app_label='contenttypes_tests', model='unknown')
+ self.assertEqual(ct.app_labeled_name, 'unknown')
class TestRouter:
diff --git a/tests/i18n/contenttypes/tests.py b/tests/i18n/contenttypes/tests.py
index e2b7157692..d23e2bdf56 100644
--- a/tests/i18n/contenttypes/tests.py
+++ b/tests/i18n/contenttypes/tests.py
@@ -20,6 +20,6 @@ class ContentTypeTests(TestCase):
def test_verbose_name(self):
company_type = ContentType.objects.get(app_label='i18n', model='company')
with translation.override('en'):
- self.assertEqual(str(company_type), 'Company')
+ self.assertEqual(str(company_type), 'i18n | Company')
with translation.override('fr'):
- self.assertEqual(str(company_type), 'Société')
+ self.assertEqual(str(company_type), 'i18n | Société')
diff --git a/tests/multiple_database/tests.py b/tests/multiple_database/tests.py
index a403b9fd88..e39967ce78 100644
--- a/tests/multiple_database/tests.py
+++ b/tests/multiple_database/tests.py
@@ -840,8 +840,8 @@ class QueryTestCase(TestCase):
# Set a foreign key with an object from a different database
msg = (
- 'Cannot assign "": the current database router '
- 'prevents this relation.'
+ 'Cannot assign "": the '
+ 'current database router prevents this relation.'
)
with self.assertRaisesMessage(ValueError, msg):
review1.content_object = dive