summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarkus Holtermann <info@markusholtermann.eu>2015-01-12 02:52:22 +0100
committerMarkus Holtermann <info@markusholtermann.eu>2015-01-14 20:37:56 +0100
commit478546fcef38d95866a92bc44d10e15b26c7254c (patch)
treeaf93d799cce1eaefe27a8dd1c01792b547bcf297
parent02c059ff7f282e541629a67e25a70a292cc01d87 (diff)
[1.7.x] 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. Backport of 737d24923ac69bb8b89af1bb2f3f4c4c744349e8 from master.
-rw-r--r--django/contrib/auth/management/__init__.py5
-rw-r--r--django/contrib/auth/tests/test_management.py20
-rw-r--r--django/contrib/contenttypes/management.py5
-rw-r--r--django/contrib/contenttypes/tests/tests.py23
-rw-r--r--django/db/migrations/loader.py12
-rw-r--r--docs/releases/1.7.4.txt3
6 files changed, 65 insertions, 3 deletions
diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py
index 18cdcce598..683a8d5903 100644
--- a/django/contrib/auth/management/__init__.py
+++ b/django/contrib/auth/management/__init__.py
@@ -11,6 +11,7 @@ from django.contrib.auth import models as auth_app, get_permission_codename
from django.core import exceptions
from django.core.management.base import CommandError
from django.db import DEFAULT_DB_ALIAS, router
+from django.db.migrations.loader import is_latest_migration_applied
from django.db.models import signals
from django.utils.encoding import DEFAULT_LOCALE_ENCODING
from django.utils import six
@@ -59,6 +60,10 @@ def _check_permission_clashing(custom, builtin, ctype):
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:
return
diff --git a/django/contrib/auth/tests/test_management.py b/django/contrib/auth/tests/test_management.py
index 7fd41b9e8b..48c98a1e13 100644
--- a/django/contrib/auth/tests/test_management.py
+++ b/django/contrib/auth/tests/test_management.py
@@ -17,7 +17,7 @@ from django.core import checks
from django.core import exceptions
from django.core.management import call_command
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.encoding import force_str
@@ -506,3 +506,21 @@ class PermissionTestCase(TestCase):
six.assertRaisesRegex(self, exceptions.ValidationError,
"The verbose_name of permission is longer than 39 characters",
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())
diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py
index f705330137..91984a88df 100644
--- a/django/contrib/contenttypes/management.py
+++ b/django/contrib/contenttypes/management.py
@@ -1,5 +1,6 @@
from django.apps import apps
from django.db import DEFAULT_DB_ALIAS, router
+from django.db.migrations.loader import is_latest_migration_applied
from django.db.models import signals
from django.utils.encoding import smart_text
from django.utils import six
@@ -11,6 +12,10 @@ def update_contenttypes(app_config, verbosity=2, interactive=True, using=DEFAULT
Creates content types for models in the given app, removing any model
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:
return
diff --git a/django/contrib/contenttypes/tests/tests.py b/django/contrib/contenttypes/tests/tests.py
index 924ffdca46..d8d967fb80 100644
--- a/django/contrib/contenttypes/tests/tests.py
+++ b/django/contrib/contenttypes/tests/tests.py
@@ -3,8 +3,10 @@ from __future__ import unicode_literals
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.core.management import call_command
from django.http import HttpRequest, Http404
-from django.test import TestCase, override_settings
+from django.test import TestCase, override_settings, skipUnlessDBFeature
+from django.test.utils import override_system_checks
from django.utils import six
from .models import ConcreteModel, ProxyModel, FooWithoutUrl, FooWithUrl, FooWithBrokenAbsoluteUrl
@@ -241,3 +243,22 @@ class ContentTypesTests(TestCase):
# Instead, just return the ContentType object and let the app detect stale states.
ct_fetched = ContentType.objects.get_for_id(ct.pk)
self.assertIsNone(ct_fetched.model_class())
+
+
+class MigrateTests(TestCase):
+
+ @skipUnlessDBFeature('can_rollback_ddl')
+ @override_system_checks([])
+ 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())
diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py
index 923202a777..b700616746 100644
--- a/django/db/migrations/loader.py
+++ b/django/db/migrations/loader.py
@@ -5,6 +5,7 @@ import os
import sys
from django.apps import apps
+from django.db import connection
from django.db.migrations.recorder import MigrationRecorder
from django.db.migrations.graph import MigrationGraph
from django.utils import six
@@ -284,3 +285,14 @@ class AmbiguityError(Exception):
Raised when more than one migration matches a name prefix
"""
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
+ )
diff --git a/docs/releases/1.7.4.txt b/docs/releases/1.7.4.txt
index 379b148488..486baa6e07 100644
--- a/docs/releases/1.7.4.txt
+++ b/docs/releases/1.7.4.txt
@@ -9,4 +9,5 @@ Django 1.7.4 fixes several bugs in 1.7.3.
Bugfixes
========
-* ...
+* Fixed a migration crash when unapplying ``contrib.contenttypes``’s or
+ ``contrib.auth``’s first migration (:ticket:`24075`).