Commit 62f9508a authored by Antonis Christofides's avatar Antonis Christofides Committed by Loic Bistuer
Browse files

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

Thanks Simon Charette and Tim Graham for the reviews.
parent d240b29c
Loading
Loading
Loading
Loading
+15 −6
Original line number Diff line number Diff line
@@ -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):
+38 −1
Original line number Diff line number Diff line
@@ -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)