Commit d392c1e1 authored by Sergey Fedoseev's avatar Sergey Fedoseev Committed by Tim Graham
Browse files

Refs #24299 -- Made contenttypes migrations signal handler more robust.

parent d538e37e
Loading
Loading
Loading
Loading
+13 −12
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ import warnings

from django.apps import apps
from django.db import models
from django.db.utils import OperationalError, ProgrammingError
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
@@ -58,18 +58,11 @@ class ContentTypeManager(models.Manager):

        # The ContentType entry was not found in the cache, therefore we
        # proceed to load or create it.
        try:
            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 (OperationalError, ProgrammingError):
            # It's possible to migrate a single app before contenttypes,
            # as it's not a required initial dependency (it's contrib!)
            # Have a nice error for this.
            raise RuntimeError(
                "Error creating new content types. Please make sure contenttypes "
                "is migrated before trying to migrate apps individually."
            )
            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.
@@ -77,6 +70,14 @@ class ContentTypeManager(models.Manager):
                    app_label=opts.app_label,
                    model=opts.model_name,
                )
        except (OperationalError, ProgrammingError, IntegrityError):
            # It's possible to migrate a single app before contenttypes,
            # as it's not a required initial dependency (it's contrib!)
            # Have a nice error for this.
            raise RuntimeError(
                "Error creating new content types. Please make sure contenttypes "
                "is migrated before trying to migrate apps individually."
            )
        self._add_to_cache(self.db, ct)
        return ct

+25 −1
Original line number Diff line number Diff line
@@ -5,8 +5,9 @@ import warnings
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.views import shortcut
from django.contrib.sites.shortcuts import get_current_site
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
from django.http import Http404, HttpRequest
from django.test import TestCase, override_settings
from django.test import TestCase, mock, override_settings
from django.utils import six

from .models import (
@@ -264,3 +265,26 @@ class ContentTypesTests(TestCase):
            "ContentType.name field doesn't exist any longer. Please remove it from your code."
        )
        self.assertTrue(ContentType.objects.filter(model='OldModel').exists())

    @mock.patch('django.contrib.contenttypes.models.ContentTypeManager.get_or_create')
    @mock.patch('django.contrib.contenttypes.models.ContentTypeManager.get')
    def test_message_if_get_for_model_fails(self, mocked_get, mocked_get_or_create):
        """
        Check that `RuntimeError` with nice error message is raised if
        `get_for_model` fails because of database errors.
        """

        def _test_message(mocked_method):
            for ExceptionClass in (IntegrityError, OperationalError, ProgrammingError):
                mocked_method.side_effect = ExceptionClass
                with self.assertRaisesMessage(
                    RuntimeError,
                    "Error creating new content types. Please make sure contenttypes "
                    "is migrated before trying to migrate apps individually."
                ):
                    ContentType.objects.get_for_model(ContentType)

        _test_message(mocked_get)

        mocked_get.side_effect = ContentType.DoesNotExist
        _test_message(mocked_get_or_create)