mirror of https://github.com/django/django.git
Fixed #23408 -- Added migrations questioner prompt for adding unique fields with a callable default.
This commit is contained in:
parent
0f3e1a54bf
commit
47f791f132
|
@ -880,6 +880,12 @@ class MigrationAutodetector:
|
|||
field.default = self.questioner.ask_auto_now_add_addition(field_name, model_name)
|
||||
else:
|
||||
field.default = self.questioner.ask_not_null_addition(field_name, model_name)
|
||||
if (
|
||||
field.unique and
|
||||
field.default is not models.NOT_PROVIDED and
|
||||
callable(field.default)
|
||||
):
|
||||
self.questioner.ask_unique_callable_default_addition(field_name, model_name)
|
||||
self.add_operation(
|
||||
app_label,
|
||||
operations.AddField(
|
||||
|
|
|
@ -6,6 +6,7 @@ import sys
|
|||
from django.apps import apps
|
||||
from django.db.models import NOT_PROVIDED
|
||||
from django.utils import timezone
|
||||
from django.utils.version import get_docs_version
|
||||
|
||||
from .loader import MigrationLoader
|
||||
|
||||
|
@ -79,6 +80,11 @@ class MigrationQuestioner:
|
|||
# None means quit
|
||||
return None
|
||||
|
||||
def ask_unique_callable_default_addition(self, field_name, model_name):
|
||||
"""Adding a unique field with a callable default."""
|
||||
# None means continue.
|
||||
return None
|
||||
|
||||
|
||||
class InteractiveMigrationQuestioner(MigrationQuestioner):
|
||||
|
||||
|
@ -229,6 +235,27 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
|
|||
return self._ask_default(default='timezone.now')
|
||||
return None
|
||||
|
||||
def ask_unique_callable_default_addition(self, field_name, model_name):
|
||||
"""Adding a unique field with a callable default."""
|
||||
if not self.dry_run:
|
||||
version = get_docs_version()
|
||||
choice = self._choice_input(
|
||||
f'Callable default on unique field {model_name}.{field_name} '
|
||||
f'will not generate unique values upon migrating.\n'
|
||||
f'Please choose how to proceed:\n',
|
||||
[
|
||||
f'Continue making this migration as the first step in '
|
||||
f'writing a manual migration to generate unique values '
|
||||
f'described here: '
|
||||
f'https://docs.djangoproject.com/en/{version}/howto/'
|
||||
f'writing-migrations/#migrations-that-add-unique-fields.',
|
||||
'Quit and edit field options in models.py.',
|
||||
],
|
||||
)
|
||||
if choice == 2:
|
||||
sys.exit(3)
|
||||
return None
|
||||
|
||||
|
||||
class NonInteractiveMigrationQuestioner(MigrationQuestioner):
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@ 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 django.utils import timezone
|
||||
from django.utils.version import get_docs_version
|
||||
|
||||
from .models import UnicodeModel, UnserializableModel
|
||||
from .routers import TestRouter
|
||||
|
@ -1870,6 +1872,57 @@ class MakeMigrationsTests(MigrationTestBase):
|
|||
with self.assertRaises(SystemExit):
|
||||
call_command('makemigrations', 'migrations', interactive=True)
|
||||
|
||||
def test_makemigrations_interactive_unique_callable_default_addition(self):
|
||||
"""
|
||||
makemigrations prompts the user when adding a unique field with
|
||||
a callable default.
|
||||
"""
|
||||
class Book(models.Model):
|
||||
created = models.DateTimeField(unique=True, default=timezone.now)
|
||||
|
||||
class Meta:
|
||||
app_label = 'migrations'
|
||||
|
||||
version = get_docs_version()
|
||||
input_msg = (
|
||||
f'Callable default on unique field book.created will not generate '
|
||||
f'unique values upon migrating.\n'
|
||||
f'Please choose how to proceed:\n\n'
|
||||
f' 1) Continue making this migration as the first step in writing '
|
||||
f'a manual migration to generate unique values described here: '
|
||||
f'https://docs.djangoproject.com/en/{version}/howto/'
|
||||
f'writing-migrations/#migrations-that-add-unique-fields.\n'
|
||||
f' 2) Quit and edit field options in models.py.\n'
|
||||
)
|
||||
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)
|
||||
out_value = out.getvalue()
|
||||
self.assertIn(input_msg, out_value)
|
||||
self.assertNotIn('Add field created to book', out_value)
|
||||
# 1 - continue.
|
||||
with mock.patch('builtins.input', return_value='1'):
|
||||
with captured_stdout() as out:
|
||||
call_command('makemigrations', 'migrations', interactive=True)
|
||||
out_value = out.getvalue()
|
||||
self.assertIn(input_msg, out_value)
|
||||
self.assertIn('Add field created to book', out_value)
|
||||
|
||||
def test_makemigrations_non_interactive_unique_callable_default_addition(self):
|
||||
class Book(models.Model):
|
||||
created = models.DateTimeField(unique=True, default=timezone.now)
|
||||
|
||||
class Meta:
|
||||
app_label = 'migrations'
|
||||
|
||||
with self.temporary_migration_module(module='migrations.test_migrations'):
|
||||
with captured_stdout() as out:
|
||||
call_command('makemigrations', 'migrations', interactive=False)
|
||||
out_value = out.getvalue()
|
||||
self.assertIn('Add field created to book', out_value)
|
||||
|
||||
|
||||
class SquashMigrationsTests(MigrationTestBase):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue