Commit 50931dfa authored by Mounir Messelmeni's avatar Mounir Messelmeni Committed by Tim Graham
Browse files

Fixed #25304 -- Allowed management commands to check if migrations are applied.

parent 004ba0f9
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@ from django.utils.encoding import force_str

class Command(BaseCommand):
    help = "Change a user's password for django.contrib.auth."

    requires_migrations_checks = True
    requires_system_checks = False

    def _get_pass(self, prompt="Password: "):
+1 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ class NotRunningInTTYException(Exception):

class Command(BaseCommand):
    help = 'Used to create a superuser.'
    requires_migrations_checks = True

    def __init__(self, *args, **kwargs):
        super(Command, self).__init__(*args, **kwargs)
+42 −1
Original line number Diff line number Diff line
@@ -11,8 +11,10 @@ from argparse import ArgumentParser

import django
from django.core import checks
from django.core.exceptions import ImproperlyConfigured
from django.core.management.color import color_style, no_style
from django.db import connections
from django.db import DEFAULT_DB_ALIAS, connections
from django.db.migrations.exceptions import MigrationSchemaMissing
from django.utils.encoding import force_str


@@ -165,6 +167,10 @@ class BaseCommand(object):
        wrapped with ``BEGIN;`` and ``COMMIT;``. Default value is
        ``False``.

    ``requires_migrations_checks``
        A boolean; if ``True``, the command prints a warning if the set of
        migrations on disk don't match the migrations in the database.

    ``requires_system_checks``
        A boolean; if ``True``, entire Django project will be checked for errors
        prior to executing the command. Default value is ``True``.
@@ -199,6 +205,7 @@ class BaseCommand(object):
    can_import_settings = True
    output_transaction = False  # Whether to wrap the output in a "BEGIN; COMMIT;"
    leave_locale_alone = False
    requires_migrations_checks = False
    requires_system_checks = True

    def __init__(self, stdout=None, stderr=None, no_color=False):
@@ -336,6 +343,8 @@ class BaseCommand(object):
        try:
            if self.requires_system_checks and not options.get('skip_checks'):
                self.check()
            if self.requires_migrations_checks:
                self.check_migrations()
            output = self.handle(*args, **options)
            if output:
                if self.output_transaction:
@@ -419,6 +428,38 @@ class BaseCommand(object):
            else:
                self.stdout.write(msg)

    def check_migrations(self):
        """
        Print a warning if the set of migrations on disk don't match the
        migrations in the database.
        """
        from django.db.migrations.executor import MigrationExecutor
        try:
            executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
        except ImproperlyConfigured:
            # No databases are configured (or the dummy one)
            return
        except MigrationSchemaMissing:
            self.stdout.write(self.style.NOTICE(
                "\nNot checking migrations as it is not possible to access/create the django_migrations table."
            ))
            return

        plan = executor.migration_plan(executor.loader.graph.leaf_nodes())
        if plan:
            apps_waiting_migration = sorted(set(migration.app_label for migration, backwards in plan))
            self.stdout.write(
                self.style.NOTICE(
                    "\nYou have %(unpplied_migration_count)s unapplied migration(s). "
                    "Your project may not work properly until you apply the "
                    "migrations for app(s): %(apps_waiting_migration)s." % {
                        "unpplied_migration_count": len(plan),
                        "apps_waiting_migration": ", ".join(apps_waiting_migration),
                    }
                )
            )
            self.stdout.write(self.style.NOTICE("Run 'python manage.py migrate' to apply them.\n"))

    def handle(self, *args, **options):
        """
        The actual logic of the command. Subclasses must implement
+2 −35
Original line number Diff line number Diff line
@@ -8,12 +8,8 @@ import sys
from datetime import datetime

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.management.base import BaseCommand, CommandError
from django.core.servers.basehttp import get_internal_wsgi_application, run
from django.db import DEFAULT_DB_ALIAS, connections
from django.db.migrations.exceptions import MigrationSchemaMissing
from django.db.migrations.executor import MigrationExecutor
from django.utils import autoreload, six
from django.utils.encoding import force_text, get_system_encoding

@@ -114,6 +110,8 @@ class Command(BaseCommand):

        self.stdout.write("Performing system checks...\n\n")
        self.check(display_num_errors=True)
        # Need to check migrations here, so can't use the
        # requires_migrations_check attribute.
        self.check_migrations()
        now = datetime.now().strftime('%B %d, %Y - %X')
        if six.PY2:
@@ -154,36 +152,5 @@ class Command(BaseCommand):
                self.stdout.write(shutdown_message)
            sys.exit(0)

    def check_migrations(self):
        """
        Checks to see if the set of migrations on disk matches the
        migrations in the database. Prints a warning if they don't match.
        """
        try:
            executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
        except ImproperlyConfigured:
            # No databases are configured (or the dummy one)
            return
        except MigrationSchemaMissing:
            self.stdout.write(self.style.NOTICE(
                "\nNot checking migrations as it is not possible to access/create the django_migrations table."
            ))
            return

        plan = executor.migration_plan(executor.loader.graph.leaf_nodes())
        if plan:
            apps_waiting_migration = sorted(set(migration.app_label for migration, backwards in plan))
            self.stdout.write(
                self.style.NOTICE(
                    "\nYou have %(unpplied_migration_count)s unapplied migration(s). "
                    "Your project may not work properly until you apply the "
                    "migrations for app(s): %(apps_waiting_migration)s." % {
                        "unpplied_migration_count": len(plan),
                        "apps_waiting_migration": ", ".join(apps_waiting_migration),
                    }
                )
            )
            self.stdout.write(self.style.NOTICE("Run 'python manage.py migrate' to apply them.\n"))

# Kept for backward compatibility
BaseRunserverCommand = Command
+8 −0
Original line number Diff line number Diff line
@@ -231,6 +231,14 @@ All attributes can be set in your derived class and can be used in
    ``True``, the output will automatically be wrapped with ``BEGIN;`` and
    ``COMMIT;``. Default value is ``False``.

.. attribute:: BaseCommand.requires_migrations_checks

    .. versionadded:: 1.10

    A boolean; if ``True``, the command prints a warning if the set of
    migrations on disk don't match the migrations in the database. A warning
    doesn't prevent the command from executing. Default value is ``False``.

.. attribute:: BaseCommand.requires_system_checks

    A boolean; if ``True``, the entire Django project will be checked for
Loading