Loading django/contrib/contenttypes/models.py +15 −6 Original line number Diff line number Diff line Loading @@ -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): Loading tests/contenttypes_tests/tests.py +38 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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
django/contrib/contenttypes/models.py +15 −6 Original line number Diff line number Diff line Loading @@ -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): Loading
tests/contenttypes_tests/tests.py +38 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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)