Fixed #20401 -- ContentTypeManager.get_for_model reads from db_for_read.

Thanks Simon Charette and Tim Graham for the reviews.
This commit is contained in:
Antonis Christofides 2013-11-24 18:12:46 +01:00 committed by Loic Bistuer
parent d240b29c08
commit 62f9508ade
2 changed files with 53 additions and 7 deletions

View File

@ -38,18 +38,27 @@ class ContentTypeManager(models.Manager):
""" """
opts = self._get_opts(model, for_concrete_model) opts = self._get_opts(model, for_concrete_model)
try: try:
ct = self._get_from_cache(opts) return self._get_from_cache(opts)
except KeyError: except KeyError:
# Load or create the ContentType entry. The smart_text() is pass
# needed around opts.verbose_name_raw because name_raw might be a
# django.utils.functional.__proxy__ object. # 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( ct, created = self.get_or_create(
app_label=opts.app_label, app_label=opts.app_label,
model=opts.model_name, model=opts.model_name,
defaults={'name': smart_text(opts.verbose_name_raw)}, defaults={'name': smart_text(opts.verbose_name_raw)},
) )
self._add_to_cache(self.db, ct) self._add_to_cache(self.db, ct)
return ct return ct
def get_for_models(self, *models, **kwargs): def get_for_models(self, *models, **kwargs):

View File

@ -7,7 +7,7 @@ from django.contrib.contenttypes.fields import (
) )
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core import checks 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 import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils.encoding import force_str from django.utils.encoding import force_str
@ -335,3 +335,40 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
) )
] ]
self.assertEqual(errors, expected) 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)