From cd40306854bdeec7d961e3715fba0ba9f72f2f88 Mon Sep 17 00:00:00 2001 From: Sarah Guermond Date: Sun, 15 Jul 2018 20:32:47 -0700 Subject: [PATCH] Fixed #25884 -- Fixed migrate --run-syncdb when specifying an app label. --- django/core/management/commands/migrate.py | 25 ++++++++++++++----- .../unmigrated_app_simple/__init__.py | 0 .../unmigrated_app_simple/models.py | 9 +++++++ tests/migrations/test_commands.py | 23 +++++++++++++++++ 4 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 tests/migrations/migrations_test_apps/unmigrated_app_simple/__init__.py create mode 100644 tests/migrations/migrations_test_apps/unmigrated_app_simple/models.py diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py index f28816fcee..6efae2fc90 100644 --- a/django/core/management/commands/migrate.py +++ b/django/core/management/commands/migrate.py @@ -104,6 +104,7 @@ class Command(BaseCommand): ) # If they supplied command line arguments, work out what they mean. + run_syncdb = options['run_syncdb'] target_app_labels_only = True if options['app_label']: # Validate app_label. @@ -112,7 +113,10 @@ class Command(BaseCommand): apps.get_app_config(app_label) except LookupError as err: raise CommandError(str(err)) - if app_label not in executor.loader.migrated_apps: + if run_syncdb: + if app_label in executor.loader.migrated_apps: + raise CommandError("Can't use run_syncdb with app '%s' as it has migrations." % app_label) + elif app_label not in executor.loader.migrated_apps: raise CommandError("App '%s' does not have migrations." % app_label) if options['app_label'] and options['migration_name']: @@ -152,15 +156,21 @@ class Command(BaseCommand): self.stdout.write(' ' + message, style) return + # At this point, ignore run_syncdb if there aren't any apps to sync. run_syncdb = options['run_syncdb'] and executor.loader.unmigrated_apps # Print some useful info if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Operations to perform:")) if run_syncdb: - self.stdout.write( - self.style.MIGRATE_LABEL(" Synchronize unmigrated apps: ") + - (", ".join(sorted(executor.loader.unmigrated_apps))) - ) + if options['app_label']: + self.stdout.write( + self.style.MIGRATE_LABEL(" Synchronize unmigrated app: %s" % app_label) + ) + else: + self.stdout.write( + self.style.MIGRATE_LABEL(" Synchronize unmigrated apps: ") + + (", ".join(sorted(executor.loader.unmigrated_apps))) + ) if target_app_labels_only: self.stdout.write( self.style.MIGRATE_LABEL(" Apply all migrations: ") + @@ -187,7 +197,10 @@ class Command(BaseCommand): if run_syncdb: if self.verbosity >= 1: self.stdout.write(self.style.MIGRATE_HEADING("Synchronizing apps without migrations:")) - self.sync_apps(connection, executor.loader.unmigrated_apps) + if options['app_label']: + self.sync_apps(connection, [app_label]) + else: + self.sync_apps(connection, executor.loader.unmigrated_apps) # Migrate! if self.verbosity >= 1: diff --git a/tests/migrations/migrations_test_apps/unmigrated_app_simple/__init__.py b/tests/migrations/migrations_test_apps/unmigrated_app_simple/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/migrations/migrations_test_apps/unmigrated_app_simple/models.py b/tests/migrations/migrations_test_apps/unmigrated_app_simple/models.py new file mode 100644 index 0000000000..785d040800 --- /dev/null +++ b/tests/migrations/migrations_test_apps/unmigrated_app_simple/models.py @@ -0,0 +1,9 @@ +from django.db import models + + +class UnmigratedModel(models.Model): + """ + A model that is in a migration-less app (which this app is + if its migrations directory has not been repointed) + """ + pass diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 2548804677..c2891ed1a1 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -663,6 +663,29 @@ class MigrateTests(MigrationTestBase): table_name = truncate_name('unmigrated_app_syncdb_classroom', connection.ops.max_name_length()) self.assertIn('Creating table %s' % table_name, stdout) + @override_settings(MIGRATION_MODULES={'migrations': 'migrations.test_migrations'}) + def test_migrate_syncdb_app_with_migrations(self): + msg = "Can't use run_syncdb with app 'migrations' as it has migrations." + with self.assertRaisesMessage(CommandError, msg): + call_command('migrate', 'migrations', run_syncdb=True, verbosity=0) + + @override_settings(INSTALLED_APPS=[ + 'migrations.migrations_test_apps.unmigrated_app_syncdb', + 'migrations.migrations_test_apps.unmigrated_app_simple', + ]) + def test_migrate_syncdb_app_label(self): + """ + Running migrate --run-syncdb with an app_label only creates tables for + the specified app. + """ + stdout = io.StringIO() + with mock.patch.object(BaseDatabaseSchemaEditor, 'execute') as execute: + call_command('migrate', 'unmigrated_app_syncdb', run_syncdb=True, stdout=stdout) + create_table_count = len([call for call in execute.mock_calls if 'CREATE TABLE' in str(call)]) + self.assertEqual(create_table_count, 2) + self.assertGreater(len(execute.mock_calls), 2) + self.assertIn('Synchronize unmigrated app: unmigrated_app_syncdb', stdout.getvalue()) + @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_squashed"}) def test_migrate_record_replaced(self): """