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:
parent
d240b29c08
commit
62f9508ade
|
@ -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)
|
||||
|
||||
return ct
|
||||
|
||||
def get_for_models(self, *models, **kwargs):
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue