Fixed #24075 -- Prevented running post_migrate signals when unapplying initial migrations of contenttypes and auth
Thanks Florian Apolloner for the report and Claude Paroz and Tim Graham for the review and help on the patch.
This commit is contained in:
parent
a6f144fd4f
commit
737d24923a
|
@ -11,6 +11,7 @@ from django.contrib.auth import get_permission_codename
|
||||||
from django.core import exceptions
|
from django.core import exceptions
|
||||||
from django.core.management.base import CommandError
|
from django.core.management.base import CommandError
|
||||||
from django.db import DEFAULT_DB_ALIAS, router
|
from django.db import DEFAULT_DB_ALIAS, router
|
||||||
|
from django.db.migrations.loader import is_latest_migration_applied
|
||||||
from django.utils.encoding import DEFAULT_LOCALE_ENCODING
|
from django.utils.encoding import DEFAULT_LOCALE_ENCODING
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
|
@ -58,6 +59,10 @@ def _check_permission_clashing(custom, builtin, ctype):
|
||||||
|
|
||||||
|
|
||||||
def create_permissions(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs):
|
def create_permissions(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs):
|
||||||
|
# TODO: Remove when migration plan / state is passed (#24100).
|
||||||
|
if not is_latest_migration_applied('auth'):
|
||||||
|
return
|
||||||
|
|
||||||
if not app_config.models_module:
|
if not app_config.models_module:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ from django.core import checks
|
||||||
from django.core import exceptions
|
from django.core import exceptions
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.core.management.base import CommandError
|
from django.core.management.base import CommandError
|
||||||
from django.test import TestCase, override_settings, override_system_checks
|
from django.test import TestCase, override_settings, override_system_checks, skipUnlessDBFeature
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
|
@ -570,3 +570,21 @@ class PermissionTestCase(TestCase):
|
||||||
six.assertRaisesRegex(self, exceptions.ValidationError,
|
six.assertRaisesRegex(self, exceptions.ValidationError,
|
||||||
"The verbose_name of permission is longer than 244 characters",
|
"The verbose_name of permission is longer than 244 characters",
|
||||||
create_permissions, auth_app_config, verbosity=0)
|
create_permissions, auth_app_config, verbosity=0)
|
||||||
|
|
||||||
|
|
||||||
|
class MigrateTests(TestCase):
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('can_rollback_ddl')
|
||||||
|
def test_unmigrating_first_migration_post_migrate_signal(self):
|
||||||
|
"""
|
||||||
|
#24075 - When unmigrating an app before its first migration,
|
||||||
|
post_migrate signal handler must be aware of the missing tables.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with override_settings(
|
||||||
|
INSTALLED_APPS=["django.contrib.auth", "django.contrib.contenttypes"],
|
||||||
|
MIGRATION_MODULES={'auth': 'django.contrib.auth.migrations'},
|
||||||
|
):
|
||||||
|
call_command("migrate", "auth", "zero", stdout=six.StringIO())
|
||||||
|
finally:
|
||||||
|
call_command("migrate", stdout=six.StringIO())
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db import DEFAULT_DB_ALIAS, router
|
from django.db import DEFAULT_DB_ALIAS, router
|
||||||
|
from django.db.migrations.loader import is_latest_migration_applied
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.six.moves import input
|
from django.utils.six.moves import input
|
||||||
|
@ -10,6 +11,10 @@ def update_contenttypes(app_config, verbosity=2, interactive=True, using=DEFAULT
|
||||||
Creates content types for models in the given app, removing any model
|
Creates content types for models in the given app, removing any model
|
||||||
entries that no longer have a matching model class.
|
entries that no longer have a matching model class.
|
||||||
"""
|
"""
|
||||||
|
# TODO: Remove when migration plan / state is passed (#24100).
|
||||||
|
if not is_latest_migration_applied('contenttypes'):
|
||||||
|
return
|
||||||
|
|
||||||
if not app_config.models_module:
|
if not app_config.models_module:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,9 @@ from __future__ import unicode_literals
|
||||||
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.core.management import call_command
|
||||||
from django.http import HttpRequest, Http404
|
from django.http import HttpRequest, Http404
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings, skipUnlessDBFeature
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
from .models import ConcreteModel, ProxyModel, FooWithoutUrl, FooWithUrl, FooWithBrokenAbsoluteUrl
|
from .models import ConcreteModel, ProxyModel, FooWithoutUrl, FooWithUrl, FooWithBrokenAbsoluteUrl
|
||||||
|
@ -241,3 +242,21 @@ class ContentTypesTests(TestCase):
|
||||||
# Instead, just return the ContentType object and let the app detect stale states.
|
# Instead, just return the ContentType object and let the app detect stale states.
|
||||||
ct_fetched = ContentType.objects.get_for_id(ct.pk)
|
ct_fetched = ContentType.objects.get_for_id(ct.pk)
|
||||||
self.assertIsNone(ct_fetched.model_class())
|
self.assertIsNone(ct_fetched.model_class())
|
||||||
|
|
||||||
|
|
||||||
|
class MigrateTests(TestCase):
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('can_rollback_ddl')
|
||||||
|
def test_unmigrating_first_migration_post_migrate_signal(self):
|
||||||
|
"""
|
||||||
|
#24075 - When unmigrating an app before its first migration,
|
||||||
|
post_migrate signal handler must be aware of the missing tables.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with override_settings(
|
||||||
|
INSTALLED_APPS=["django.contrib.contenttypes"],
|
||||||
|
MIGRATION_MODULES={'contenttypes': 'django.contrib.contenttypes.migrations'},
|
||||||
|
):
|
||||||
|
call_command("migrate", "contenttypes", "zero", stdout=six.StringIO())
|
||||||
|
finally:
|
||||||
|
call_command("migrate", stdout=six.StringIO())
|
||||||
|
|
|
@ -5,6 +5,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
from django.db import connection
|
||||||
from django.db.migrations.recorder import MigrationRecorder
|
from django.db.migrations.recorder import MigrationRecorder
|
||||||
from django.db.migrations.graph import MigrationGraph, NodeNotFoundError
|
from django.db.migrations.graph import MigrationGraph, NodeNotFoundError
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
@ -339,3 +340,14 @@ class AmbiguityError(Exception):
|
||||||
Raised when more than one migration matches a name prefix
|
Raised when more than one migration matches a name prefix
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def is_latest_migration_applied(app_label):
|
||||||
|
# TODO: Remove when migration plan / state is passed (#24100).
|
||||||
|
loader = MigrationLoader(connection)
|
||||||
|
loader.load_disk()
|
||||||
|
leaf_nodes = loader.graph.leaf_nodes(app=app_label)
|
||||||
|
return (
|
||||||
|
leaf_nodes and leaf_nodes[0] in loader.applied_migrations or
|
||||||
|
app_label in loader.unmigrated_apps
|
||||||
|
)
|
||||||
|
|
|
@ -9,4 +9,5 @@ Django 1.7.4 fixes several bugs in 1.7.3.
|
||||||
Bugfixes
|
Bugfixes
|
||||||
========
|
========
|
||||||
|
|
||||||
* ...
|
* Fixed a migration crash when unapplying ``contrib.contenttypes``’s or
|
||||||
|
``contrib.auth``’s first migration (:ticket:`24075`).
|
||||||
|
|
Loading…
Reference in New Issue