Browse Source

Fixed #32395 -- Allowed capturing stdout of migration signals.

Simon Charette 4 years ago
parent
commit
f23b05696e

+ 2 - 2
django/core/management/commands/migrate.py

@@ -201,7 +201,7 @@ class Command(BaseCommand):
         pre_migrate_state = executor._create_project_state(with_applied_migrations=True)
         pre_migrate_apps = pre_migrate_state.apps
         emit_pre_migrate_signal(
-            self.verbosity, self.interactive, connection.alias, apps=pre_migrate_apps, plan=plan,
+            self.verbosity, self.interactive, connection.alias, stdout=self.stdout, apps=pre_migrate_apps, plan=plan,
         )
 
         # Run the syncdb phase.
@@ -266,7 +266,7 @@ class Command(BaseCommand):
         # Send the post_migrate signal, so individual apps can do whatever they need
         # to do at this point.
         emit_post_migrate_signal(
-            self.verbosity, self.interactive, connection.alias, apps=post_migrate_apps, plan=plan,
+            self.verbosity, self.interactive, connection.alias, stdout=self.stdout, apps=post_migrate_apps, plan=plan,
         )
 
     def migration_progress_callback(self, action, migration=None, fake=False):

+ 6 - 2
django/core/management/sql.py

@@ -1,3 +1,5 @@
+import sys
+
 from django.apps import apps
 from django.db import models
 
@@ -21,7 +23,8 @@ def emit_pre_migrate_signal(verbosity, interactive, db, **kwargs):
         if app_config.models_module is None:
             continue
         if verbosity >= 2:
-            print("Running pre-migrate handlers for application %s" % app_config.label)
+            stdout = kwargs.get('stdout', sys.stdout)
+            stdout.write('Running pre-migrate handlers for application %s' % app_config.label)
         models.signals.pre_migrate.send(
             sender=app_config,
             app_config=app_config,
@@ -38,7 +41,8 @@ def emit_post_migrate_signal(verbosity, interactive, db, **kwargs):
         if app_config.models_module is None:
             continue
         if verbosity >= 2:
-            print("Running post-migrate handlers for application %s" % app_config.label)
+            stdout = kwargs.get('stdout', sys.stdout)
+            stdout.write('Running post-migrate handlers for application %s' % app_config.label)
         models.signals.post_migrate.send(
             sender=app_config,
             app_config=app_config,

+ 10 - 0
docs/ref/signals.txt

@@ -424,6 +424,11 @@ Arguments sent with this signal:
     For example, the :mod:`django.contrib.auth` app only prompts to create a
     superuser when ``interactive`` is ``True``.
 
+``stdout``
+    .. versionadded:: 4.0
+
+    A stream-like object where verbose output should be redirected.
+
 ``using``
     The alias of database on which a command will operate.
 
@@ -478,6 +483,11 @@ Arguments sent with this signal:
     For example, the :mod:`django.contrib.auth` app only prompts to create a
     superuser when ``interactive`` is ``True``.
 
+``stdout``
+    .. versionadded:: 4.0
+
+    A stream-like object where verbose output should be redirected.
+
 ``using``
     The database alias used for synchronization. Defaults to the ``default``
     database.

+ 5 - 1
docs/releases/4.0.txt

@@ -191,7 +191,11 @@ Serialization
 Signals
 ~~~~~~~
 
-* ...
+* The new ``stdout`` argument for :func:`~django.db.models.signals.pre_migrate`
+  and :func:`~django.db.models.signals.post_migrate` signals allows redirecting
+  output to a stream-like object. It should be preferred over
+  :py:data:`sys.stdout` and :py:func:`print` when emitting verbose output in
+  order to allow proper capture when testing.
 
 Templates
 ~~~~~~~~~

+ 5 - 2
tests/migrate_signals/tests.py

@@ -1,3 +1,5 @@
+from io import StringIO
+
 from django.apps import apps
 from django.core import management
 from django.db import migrations
@@ -5,7 +7,7 @@ from django.db.models import signals
 from django.test import TransactionTestCase, override_settings
 
 APP_CONFIG = apps.get_app_config('migrate_signals')
-SIGNAL_ARGS = ['app_config', 'verbosity', 'interactive', 'using', 'plan', 'apps']
+SIGNAL_ARGS = ['app_config', 'verbosity', 'interactive', 'using', 'stdout', 'plan', 'apps']
 MIGRATE_DATABASE = 'default'
 MIGRATE_VERBOSITY = 0
 MIGRATE_INTERACTIVE = False
@@ -69,7 +71,7 @@ class MigrateSignalTests(TransactionTestCase):
         post_migrate_receiver = Receiver(signals.post_migrate)
         management.call_command(
             'migrate', database=MIGRATE_DATABASE, verbosity=MIGRATE_VERBOSITY,
-            interactive=MIGRATE_INTERACTIVE,
+            interactive=MIGRATE_INTERACTIVE, stdout=StringIO('test_args'),
         )
 
         for receiver in [pre_migrate_receiver, post_migrate_receiver]:
@@ -81,6 +83,7 @@ class MigrateSignalTests(TransactionTestCase):
                 self.assertEqual(args['verbosity'], MIGRATE_VERBOSITY)
                 self.assertEqual(args['interactive'], MIGRATE_INTERACTIVE)
                 self.assertEqual(args['using'], 'default')
+                self.assertIn('test_args', args['stdout'].getvalue())
                 self.assertEqual(args['plan'], [])
                 self.assertIsInstance(args['apps'], migrations.state.StateApps)
 

+ 6 - 2
tests/migrations/test_commands.py

@@ -39,10 +39,12 @@ class MigrateTests(MigrationTestBase):
         self.assertTableNotExists("migrations_book")
         # Run the migrations to 0001 only
         stdout = io.StringIO()
-        call_command('migrate', 'migrations', '0001', verbosity=1, stdout=stdout, no_color=True)
+        call_command('migrate', 'migrations', '0001', verbosity=2, stdout=stdout, no_color=True)
         stdout = stdout.getvalue()
         self.assertIn('Target specific migration: 0001_initial, from migrations', stdout)
         self.assertIn('Applying migrations.0001_initial... OK', stdout)
+        self.assertIn('Running pre-migrate handlers for application migrations', stdout)
+        self.assertIn('Running post-migrate handlers for application migrations', stdout)
         # The correct tables exist
         self.assertTableExists("migrations_author")
         self.assertTableExists("migrations_tribble")
@@ -55,10 +57,12 @@ class MigrateTests(MigrationTestBase):
         self.assertTableExists("migrations_book")
         # Unmigrate everything
         stdout = io.StringIO()
-        call_command('migrate', 'migrations', 'zero', verbosity=1, stdout=stdout, no_color=True)
+        call_command('migrate', 'migrations', 'zero', verbosity=2, stdout=stdout, no_color=True)
         stdout = stdout.getvalue()
         self.assertIn('Unapply all migrations: migrations', stdout)
         self.assertIn('Unapplying migrations.0002_second... OK', stdout)
+        self.assertIn('Running pre-migrate handlers for application migrations', stdout)
+        self.assertIn('Running post-migrate handlers for application migrations', stdout)
         # Tables are gone
         self.assertTableNotExists("migrations_author")
         self.assertTableNotExists("migrations_tribble")