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

This commit is contained in:
Sergey Fedoseev 2015-02-08 00:31:12 +05:00 committed by Tim Graham
parent d538e37e1b
commit d392c1e150
2 changed files with 38 additions and 13 deletions

View File

@ -4,7 +4,7 @@ import warnings
from django.apps import apps from django.apps import apps
from django.db import models 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.deprecation import RemovedInDjango20Warning
from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ 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 # The ContentType entry was not found in the cache, therefore we
# proceed to load or create it. # proceed to load or create it.
try:
try: try:
# We start with get() and not get_or_create() in order to use # We start with get() and not get_or_create() in order to use
# the db_for_read (see #20401). # the db_for_read (see #20401).
ct = self.get(app_label=opts.app_label, model=opts.model_name) 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: except self.model.DoesNotExist:
# Not found in the database; we proceed to create it. This time we # Not found in the database; we proceed to create it. This time we
# use get_or_create to take care of any race conditions. # use get_or_create to take care of any race conditions.
@ -77,6 +70,14 @@ class ContentTypeManager(models.Manager):
app_label=opts.app_label, app_label=opts.app_label,
model=opts.model_name, 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) self._add_to_cache(self.db, ct)
return ct return ct

View File

@ -5,8 +5,9 @@ import warnings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.views import shortcut from django.contrib.contenttypes.views import shortcut
from django.contrib.sites.shortcuts import get_current_site 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.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 django.utils import six
from .models import ( from .models import (
@ -264,3 +265,26 @@ class ContentTypesTests(TestCase):
"ContentType.name field doesn't exist any longer. Please remove it from your code." "ContentType.name field doesn't exist any longer. Please remove it from your code."
) )
self.assertTrue(ContentType.objects.filter(model='OldModel').exists()) 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)