Commit 737d2492 authored by Markus Holtermann's avatar Markus Holtermann
Browse files

Fixed #24075 -- Prevented running post_migrate signals when unapplying initial...

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.
parent a6f144fd
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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

+19 −1
Original line number Diff line number Diff line
@@ -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())
+5 −0
Original line number Diff line number Diff line
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

+20 −1
Original line number Diff line number Diff line
@@ -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())
+12 −0
Original line number Diff line number Diff line
@@ -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
    )
Loading