summaryrefslogtreecommitdiff
path: root/tests/migrations/test_commands.py
diff options
context:
space:
mode:
authorMateo Radman <48420316+mateoradman@users.noreply.github.com>2021-08-25 18:53:19 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-08-27 13:27:41 +0200
commit61c5eae516257d4827b8a12da2b714f05ac88a9f (patch)
treea0f87ce074c1fef12f1c6e75574c361f282ce6cf /tests/migrations/test_commands.py
parentd00fb4d2d6f375ace2b4fe35a4acde8e476000d2 (diff)
Refs #32900 -- Added makemigrations tests for messages in interactive mode.
Diffstat (limited to 'tests/migrations/test_commands.py')
-rw-r--r--tests/migrations/test_commands.py111
1 files changed, 111 insertions, 0 deletions
diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py
index daeeaf8edb..133df37c06 100644
--- a/tests/migrations/test_commands.py
+++ b/tests/migrations/test_commands.py
@@ -16,6 +16,7 @@ from django.db.backends.utils import truncate_name
from django.db.migrations.exceptions import InconsistentMigrationHistory
from django.db.migrations.recorder import MigrationRecorder
from django.test import TestCase, override_settings, skipUnlessDBFeature
+from django.test.utils import captured_stdout
from .models import UnicodeModel, UnserializableModel
from .routers import TestRouter
@@ -1347,6 +1348,46 @@ class MakeMigrationsTests(MigrationTestBase):
with self.temporary_migration_module(module="migrations.test_migrations_no_default"):
call_command("makemigrations", "migrations", interactive=False)
+ def test_makemigrations_interactive_not_null_addition(self):
+ """
+ makemigrations messages when adding a NOT NULL field in interactive
+ mode.
+ """
+ class Author(models.Model):
+ silly_field = models.BooleanField(null=False)
+
+ class Meta:
+ app_label = 'migrations'
+
+ input_msg = (
+ "You are trying to add a non-nullable field 'silly_field' to "
+ "author without a default; we can't do that (the database needs "
+ "something to populate existing rows).\n"
+ "Please select a fix:\n"
+ " 1) Provide a one-off default now (will be set on all existing "
+ "rows with a null value for this column)\n"
+ " 2) Quit, and let me add a default in models.py"
+ )
+ with self.temporary_migration_module(module='migrations.test_migrations'):
+ # 2 - quit.
+ with mock.patch('builtins.input', return_value='2'):
+ with captured_stdout() as out, self.assertRaises(SystemExit):
+ call_command('makemigrations', 'migrations', interactive=True)
+ self.assertIn(input_msg, out.getvalue())
+ # 1 - provide a default.
+ with mock.patch('builtins.input', return_value='1'):
+ with captured_stdout() as out:
+ call_command('makemigrations', 'migrations', interactive=True)
+ output = out.getvalue()
+ self.assertIn(input_msg, output)
+ self.assertIn('Please enter the default value now, as valid Python', output)
+ self.assertIn(
+ 'The datetime and django.utils.timezone modules are '
+ 'available, so you can do e.g. timezone.now',
+ output,
+ )
+ self.assertIn("Type 'exit' to exit this prompt", output)
+
def test_makemigrations_non_interactive_not_null_alteration(self):
"""
Non-interactive makemigrations fails when a default is missing on a
@@ -1365,6 +1406,49 @@ class MakeMigrationsTests(MigrationTestBase):
call_command("makemigrations", "migrations", interactive=False, stdout=out)
self.assertIn("Alter field slug on author", out.getvalue())
+ def test_makemigrations_interactive_not_null_alteration(self):
+ """
+ makemigrations messages when changing a NULL field to NOT NULL in
+ interactive mode.
+ """
+ class Author(models.Model):
+ slug = models.SlugField(null=False)
+
+ class Meta:
+ app_label = 'migrations'
+
+ input_msg = (
+ "You are trying to change the nullable field 'slug' on author to "
+ "non-nullable without a default; we can't do that (the database "
+ "needs something to populate existing rows).\n"
+ "Please select a fix:\n"
+ " 1) Provide a one-off default now (will be set on all existing "
+ "rows with a null value for this column)\n"
+ " 2) Ignore for now, and let me handle existing rows with NULL "
+ "myself (e.g. because you added a RunPython or RunSQL operation "
+ "to handle NULL values in a previous data migration)\n"
+ " 3) Quit, and let me add a default in models.py"
+ )
+ with self.temporary_migration_module(module='migrations.test_migrations'):
+ # 3 - quit.
+ with mock.patch('builtins.input', return_value='3'):
+ with captured_stdout() as out, self.assertRaises(SystemExit):
+ call_command('makemigrations', 'migrations', interactive=True)
+ self.assertIn(input_msg, out.getvalue())
+ # 1 - provide a default.
+ with mock.patch('builtins.input', return_value='1'):
+ with captured_stdout() as out:
+ call_command('makemigrations', 'migrations', interactive=True)
+ output = out.getvalue()
+ self.assertIn(input_msg, output)
+ self.assertIn('Please enter the default value now, as valid Python', output)
+ self.assertIn(
+ 'The datetime and django.utils.timezone modules are '
+ 'available, so you can do e.g. timezone.now',
+ output,
+ )
+ self.assertIn("Type 'exit' to exit this prompt", output)
+
def test_makemigrations_non_interactive_no_model_rename(self):
"""
makemigrations adds and removes a possible model rename in
@@ -1729,6 +1813,14 @@ class MakeMigrationsTests(MigrationTestBase):
class Meta:
app_label = 'migrations'
+ input_msg = (
+ "You are trying to add the field 'creation_date' with "
+ "'auto_now_add=True' to entry without a default; the database "
+ "needs something to populate existing rows.\n\n"
+ " 1) Provide a one-off default now (will be set on all existing "
+ "rows)\n"
+ " 2) Quit, and let me add a default in models.py"
+ )
# Monkeypatch interactive questioner to auto accept
with mock.patch('django.db.migrations.questioner.sys.stdout', new_callable=io.StringIO) as prompt_stdout:
out = io.StringIO()
@@ -1736,9 +1828,28 @@ class MakeMigrationsTests(MigrationTestBase):
call_command('makemigrations', 'migrations', interactive=True, stdout=out)
output = out.getvalue()
prompt_output = prompt_stdout.getvalue()
+ self.assertIn(input_msg, prompt_output)
+ self.assertIn(
+ 'Please enter the default value now, as valid Python',
+ prompt_output,
+ )
self.assertIn("You can accept the default 'timezone.now' by pressing 'Enter'", prompt_output)
+ self.assertIn("Type 'exit' to exit this prompt", prompt_output)
self.assertIn("Add field creation_date to entry", output)
+ @mock.patch('builtins.input', return_value='2')
+ def test_makemigrations_auto_now_add_interactive_quit(self, mock_input):
+ class Author(models.Model):
+ publishing_date = models.DateField(auto_now_add=True)
+
+ class Meta:
+ app_label = 'migrations'
+
+ with self.temporary_migration_module(module='migrations.test_migrations'):
+ with captured_stdout():
+ with self.assertRaises(SystemExit):
+ call_command('makemigrations', 'migrations', interactive=True)
+
class SquashMigrationsTests(MigrationTestBase):
"""