Commit f61c4f49 authored by Claude Paroz's avatar Claude Paroz
Browse files

Fixed #24742 -- Made runserver.check_migrations ignore read-only databases

Thanks Luis Del Giudice for the report, and Aymeric Augustin and Markus
Holtermann for the reviews.
parent 3c8fe5dd
Loading
Loading
Loading
Loading
+13 −5
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ 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
@@ -109,10 +110,7 @@ class Command(BaseCommand):

        self.stdout.write("Performing system checks...\n\n")
        self.check(display_num_errors=True)
        try:
        self.check_migrations()
        except ImproperlyConfigured:
            pass
        now = datetime.now().strftime('%B %d, %Y - %X')
        if six.PY2:
            now = now.decode(get_system_encoding())
@@ -157,7 +155,17 @@ class Command(BaseCommand):
        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:
            self.stdout.write(self.style.NOTICE(
+5 −0
Original line number Diff line number Diff line
from __future__ import unicode_literals

from django.db.utils import DatabaseError
from django.utils.encoding import python_2_unicode_compatible


@@ -53,3 +54,7 @@ class NodeNotFoundError(LookupError):

    def __repr__(self):
        return "NodeNotFoundError(%r)" % self.node


class MigrationSchemaMissing(DatabaseError):
    pass
+8 −2
Original line number Diff line number Diff line
@@ -2,9 +2,12 @@ from __future__ import unicode_literals

from django.apps.registry import Apps
from django.db import models
from django.db.utils import DatabaseError
from django.utils.encoding import python_2_unicode_compatible
from django.utils.timezone import now

from .exceptions import MigrationSchemaMissing


class MigrationRecorder(object):
    """
@@ -49,8 +52,11 @@ class MigrationRecorder(object):
        if self.Migration._meta.db_table in self.connection.introspection.table_names(self.connection.cursor()):
            return
        # Make the table
        try:
            with self.connection.schema_editor() as editor:
                editor.create_model(self.Migration)
        except DatabaseError as exc:
            raise MigrationSchemaMissing("Unable to create the django_migrations table (%s)" % exc)

    def applied_migrations(self):
        """
+25 −1
Original line number Diff line number Diff line
@@ -22,6 +22,9 @@ from django.conf import settings
from django.core.management import (
    BaseCommand, CommandError, call_command, color,
)
from django.db import ConnectionHandler
from django.db.migrations.exceptions import MigrationSchemaMissing
from django.db.migrations.recorder import MigrationRecorder
from django.test import LiveServerTestCase, TestCase, mock, override_settings
from django.test.runner import DiscoverRunner
from django.utils._os import npath, upath
@@ -1247,7 +1250,8 @@ class ManageRunserver(AdminScriptTestCase):
        def monkey_run(*args, **options):
            return

        self.cmd = Command()
        self.output = StringIO()
        self.cmd = Command(stdout=self.output)
        self.cmd.run = monkey_run

    def assertServerSettings(self, addr, port, ipv6=None, raw_ipv6=False):
@@ -1298,6 +1302,26 @@ class ManageRunserver(AdminScriptTestCase):
        self.cmd.handle(addrport="deadbeef:7654")
        self.assertServerSettings('deadbeef', '7654')

    def test_no_database(self):
        """
        Ensure runserver.check_migrations doesn't choke on empty DATABASES.
        """
        tested_connections = ConnectionHandler({})
        with mock.patch('django.core.management.commands.runserver.connections', new=tested_connections):
            self.cmd.check_migrations()

    def test_readonly_database(self):
        """
        Ensure runserver.check_migrations doesn't choke when a database is read-only
        (with possibly no django_migrations table).
        """
        with mock.patch.object(
                MigrationRecorder, 'ensure_schema',
                side_effect=MigrationSchemaMissing()):
            self.cmd.check_migrations()
        # Check a warning is emitted
        self.assertIn("Not checking migrations", self.output.getvalue())


class ManageRunserverEmptyAllowedHosts(AdminScriptTestCase):
    def setUp(self):