summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/contrib/contenttypes/models.py21
-rw-r--r--tests/contenttypes_tests/tests.py39
2 files changed, 53 insertions, 7 deletions
diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py
index 0dc048cfa2..634576f4d0 100644
--- a/django/contrib/contenttypes/models.py
+++ b/django/contrib/contenttypes/models.py
@@ -38,18 +38,27 @@ class ContentTypeManager(models.Manager):
"""
opts = self._get_opts(model, for_concrete_model)
try:
- ct = self._get_from_cache(opts)
+ return self._get_from_cache(opts)
except KeyError:
- # Load or create the ContentType entry. The smart_text() is
- # needed around opts.verbose_name_raw because name_raw might be a
- # django.utils.functional.__proxy__ object.
+ pass
+
+ # The ContentType entry was not found in the cache, therefore we
+ # proceed to load or create it.
+ try:
+ # We start with get() and not get_or_create() in order to use
+ # the db_for_read (see #20401).
+ ct = self.get(app_label=opts.app_label, model=opts.model_name)
+ except self.model.DoesNotExist:
+ # Not found in the database; we proceed to create it. This time we
+ # use get_or_create to take care of any race conditions.
+ # The smart_text() is needed around opts.verbose_name_raw because
+ # name_raw might be a django.utils.functional.__proxy__ object.
ct, created = self.get_or_create(
app_label=opts.app_label,
model=opts.model_name,
defaults={'name': smart_text(opts.verbose_name_raw)},
)
- self._add_to_cache(self.db, ct)
-
+ self._add_to_cache(self.db, ct)
return ct
def get_for_models(self, *models, **kwargs):
diff --git a/tests/contenttypes_tests/tests.py b/tests/contenttypes_tests/tests.py
index 0f260a0b9b..18c542bf4b 100644
--- a/tests/contenttypes_tests/tests.py
+++ b/tests/contenttypes_tests/tests.py
@@ -7,7 +7,7 @@ from django.contrib.contenttypes.fields import (
)
from django.contrib.contenttypes.models import ContentType
from django.core import checks
-from django.db import models
+from django.db import connections, models, router
from django.test import TestCase
from django.test.utils import override_settings
from django.utils.encoding import force_str
@@ -335,3 +335,40 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
)
]
self.assertEqual(errors, expected)
+
+
+class TestRouter(object):
+ def db_for_read(self, model, **hints):
+ return 'other'
+
+ def db_for_write(self, model, **hints):
+ return 'default'
+
+
+class ContentTypesMultidbTestCase(TestCase):
+
+ def setUp(self):
+ self.old_routers = router.routers
+ router.routers = [TestRouter()]
+
+ # Whenever a test starts executing, only the "default" database is
+ # connected. We explicitly connect to the "other" database here. If we
+ # don't do it, then it will be implicitly connected later when we query
+ # it, but in that case some database backends may automatically perform
+ # extra queries upon connecting (notably mysql executes
+ # "SET SQL_AUTO_IS_NULL = 0"), which will affect assertNumQueries().
+ connections['other'].ensure_connection()
+
+ def tearDown(self):
+ router.routers = self.old_routers
+
+ def test_multidb(self):
+ """
+ Test that, when using multiple databases, we use the db_for_read (see
+ #20401).
+ """
+ ContentType.objects.clear_cache()
+
+ with self.assertNumQueries(0, using='default'), \
+ self.assertNumQueries(1, using='other'):
+ ContentType.objects.get_for_model(Author)