Refs #24299 -- Made contenttypes migrations signal handler more robust.
This commit is contained in:
parent
d538e37e1b
commit
d392c1e150
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue