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 19:59:39 +0100
commit737d24923ac69bb8b89af1bb2f3f4c4c744349e8 (patch)
tree781f111026f3fb1fbaddd6c31c1c134d5640965e
parenta6f144fd4fee0090de3a99b1f50a4142722e7946 (diff)
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.
-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.py21
-rw-r--r--django/db/migrations/loader.py12
-rw-r--r--docs/releases/1.7.4.txt3
6 files changed, 63 insertions, 3 deletions
diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py
index 5440e597c1..cf00908cab 100644
--- a/django/contrib/auth/management/__init__.py
+++ b/django/contrib/auth/management/__init__.py
@@ -11,6 +11,7 @@ from django.contrib.auth import 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.utils.encoding import DEFAULT_LOCALE_ENCODING
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):
+ # 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 0c039bd0ec..10d7cb07f1 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
@@ -570,3 +570,21 @@ class PermissionTestCase(TestCase):
six.assertRaisesRegex(self, exceptions.ValidationError,
"The verbose_name of permission is longer than 244 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 abcc5b836f..3617dbbcf6 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.utils.encoding import smart_text
from django.utils import six
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
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..5360b2a59a 100644
--- a/django/contrib/contenttypes/tests/tests.py
+++ b/django/contrib/contenttypes/tests/tests.py
@@ -3,8 +3,9 @@ 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.utils import six
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.
ct_fetched = ContentType.objects.get_for_id(ct.pk)
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())
diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py
index 9cc428e162..51fb2783eb 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, NodeNotFoundError
from django.utils import six
@@ -339,3 +340,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`).