Fixed #26429 -- Added a timestamp to merge migration names.
This reduces the possibility of a naming conflict, especially after squashing migrations.
This commit is contained in:
parent
535660b852
commit
8f6a1a1551
@ -76,7 +76,7 @@ def get_commands():
|
|||||||
return commands
|
return commands
|
||||||
|
|
||||||
|
|
||||||
def call_command(name, *args, **options):
|
def call_command(command_name, *args, **options):
|
||||||
"""
|
"""
|
||||||
Calls the given command, with the given options and args/kwargs.
|
Calls the given command, with the given options and args/kwargs.
|
||||||
|
|
||||||
@ -95,25 +95,25 @@ def call_command(name, *args, **options):
|
|||||||
call_command(cmd, verbosity=0, interactive=False)
|
call_command(cmd, verbosity=0, interactive=False)
|
||||||
# Do something with cmd ...
|
# Do something with cmd ...
|
||||||
"""
|
"""
|
||||||
if isinstance(name, BaseCommand):
|
if isinstance(command_name, BaseCommand):
|
||||||
# Command object passed in.
|
# Command object passed in.
|
||||||
command = name
|
command = command_name
|
||||||
name = command.__class__.__module__.split('.')[-1]
|
command_name = command.__class__.__module__.split('.')[-1]
|
||||||
else:
|
else:
|
||||||
# Load the command object by name.
|
# Load the command object by name.
|
||||||
try:
|
try:
|
||||||
app_name = get_commands()[name]
|
app_name = get_commands()[command_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise CommandError("Unknown command: %r" % name)
|
raise CommandError("Unknown command: %r" % command_name)
|
||||||
|
|
||||||
if isinstance(app_name, BaseCommand):
|
if isinstance(app_name, BaseCommand):
|
||||||
# If the command is already loaded, use it directly.
|
# If the command is already loaded, use it directly.
|
||||||
command = app_name
|
command = app_name
|
||||||
else:
|
else:
|
||||||
command = load_command_class(app_name, name)
|
command = load_command_class(app_name, command_name)
|
||||||
|
|
||||||
# Simulate argument parsing to get the option defaults (see #10080 for details).
|
# Simulate argument parsing to get the option defaults (see #10080 for details).
|
||||||
parser = command.create_parser('', name)
|
parser = command.create_parser('', command_name)
|
||||||
# Use the `dest` option name from the parser option
|
# Use the `dest` option name from the parser option
|
||||||
opt_mapping = {
|
opt_mapping = {
|
||||||
sorted(s_opt.option_strings)[0].lstrip('-').replace('-', '_'): s_opt.dest
|
sorted(s_opt.option_strings)[0].lstrip('-').replace('-', '_'): s_opt.dest
|
||||||
|
@ -14,6 +14,7 @@ from django.db.migrations.questioner import (
|
|||||||
NonInteractiveMigrationQuestioner,
|
NonInteractiveMigrationQuestioner,
|
||||||
)
|
)
|
||||||
from django.db.migrations.state import ProjectState
|
from django.db.migrations.state import ProjectState
|
||||||
|
from django.db.migrations.utils import get_migration_name_timestamp
|
||||||
from django.db.migrations.writer import MigrationWriter
|
from django.db.migrations.writer import MigrationWriter
|
||||||
from django.utils.deprecation import RemovedInDjango20Warning
|
from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
from django.utils.six import iteritems
|
from django.utils.six import iteritems
|
||||||
@ -283,7 +284,11 @@ class Command(BaseCommand):
|
|||||||
subclass = type("Migration", (Migration, ), {
|
subclass = type("Migration", (Migration, ), {
|
||||||
"dependencies": [(app_label, migration.name) for migration in merge_migrations],
|
"dependencies": [(app_label, migration.name) for migration in merge_migrations],
|
||||||
})
|
})
|
||||||
new_migration = subclass("%04i_merge" % (biggest_number + 1), app_label)
|
migration_name = "%04i_%s" % (
|
||||||
|
biggest_number + 1,
|
||||||
|
self.migration_name or ("merge_%s" % get_migration_name_timestamp())
|
||||||
|
)
|
||||||
|
new_migration = subclass(migration_name, app_label)
|
||||||
writer = MigrationWriter(new_migration)
|
writer = MigrationWriter(new_migration)
|
||||||
|
|
||||||
if not self.dry_run:
|
if not self.dry_run:
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
|
||||||
import functools
|
import functools
|
||||||
import re
|
import re
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
@ -12,7 +11,9 @@ from django.db.migrations.migration import Migration
|
|||||||
from django.db.migrations.operations.models import AlterModelOptions
|
from django.db.migrations.operations.models import AlterModelOptions
|
||||||
from django.db.migrations.optimizer import MigrationOptimizer
|
from django.db.migrations.optimizer import MigrationOptimizer
|
||||||
from django.db.migrations.questioner import MigrationQuestioner
|
from django.db.migrations.questioner import MigrationQuestioner
|
||||||
from django.db.migrations.utils import COMPILED_REGEX_TYPE, RegexObject
|
from django.db.migrations.utils import (
|
||||||
|
COMPILED_REGEX_TYPE, RegexObject, get_migration_name_timestamp,
|
||||||
|
)
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
from .topological_sort import stable_topological_sort
|
from .topological_sort import stable_topological_sort
|
||||||
@ -1154,7 +1155,7 @@ class MigrationAutodetector(object):
|
|||||||
elif len(ops) > 1:
|
elif len(ops) > 1:
|
||||||
if all(isinstance(o, operations.CreateModel) for o in ops):
|
if all(isinstance(o, operations.CreateModel) for o in ops):
|
||||||
return "_".join(sorted(o.name_lower for o in ops))
|
return "_".join(sorted(o.name_lower for o in ops))
|
||||||
return "auto_%s" % datetime.datetime.now().strftime("%Y%m%d_%H%M")
|
return "auto_%s" % get_migration_name_timestamp()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_number(cls, name):
|
def parse_number(cls, name):
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import datetime
|
||||||
import re
|
import re
|
||||||
|
|
||||||
COMPILED_REGEX_TYPE = type(re.compile(''))
|
COMPILED_REGEX_TYPE = type(re.compile(''))
|
||||||
@ -10,3 +11,7 @@ class RegexObject(object):
|
|||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.pattern == other.pattern and self.flags == other.flags
|
return self.pattern == other.pattern and self.flags == other.flags
|
||||||
|
|
||||||
|
|
||||||
|
def get_migration_name_timestamp():
|
||||||
|
return datetime.datetime.now().strftime("%Y%m%d_%H%M")
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import codecs
|
import codecs
|
||||||
|
import datetime
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@ -705,7 +706,7 @@ class MakeMigrationsTests(MigrationTestBase):
|
|||||||
# Monkeypatch interactive questioner to auto reject
|
# Monkeypatch interactive questioner to auto reject
|
||||||
with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='N')):
|
with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='N')):
|
||||||
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
||||||
call_command("makemigrations", "migrations", merge=True, interactive=True, verbosity=0)
|
call_command("makemigrations", "migrations", name="merge", merge=True, interactive=True, verbosity=0)
|
||||||
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
||||||
self.assertFalse(os.path.exists(merge_file))
|
self.assertFalse(os.path.exists(merge_file))
|
||||||
|
|
||||||
@ -717,11 +718,22 @@ class MakeMigrationsTests(MigrationTestBase):
|
|||||||
with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='y')):
|
with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='y')):
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
||||||
call_command("makemigrations", "migrations", merge=True, interactive=True, stdout=out)
|
call_command("makemigrations", "migrations", name="merge", merge=True, interactive=True, stdout=out)
|
||||||
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
||||||
self.assertTrue(os.path.exists(merge_file))
|
self.assertTrue(os.path.exists(merge_file))
|
||||||
self.assertIn("Created new merge migration", force_text(out.getvalue()))
|
self.assertIn("Created new merge migration", force_text(out.getvalue()))
|
||||||
|
|
||||||
|
@mock.patch('django.db.migrations.utils.datetime')
|
||||||
|
def test_makemigrations_default_merge_name(self, mock_datetime):
|
||||||
|
mock_datetime.datetime.now.return_value = datetime.datetime(2016, 1, 2, 3, 4)
|
||||||
|
with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='y')):
|
||||||
|
out = six.StringIO()
|
||||||
|
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
||||||
|
call_command("makemigrations", "migrations", merge=True, interactive=True, stdout=out)
|
||||||
|
merge_file = os.path.join(migration_dir, '0003_merge_20160102_0304.py')
|
||||||
|
self.assertTrue(os.path.exists(merge_file))
|
||||||
|
self.assertIn("Created new merge migration", force_text(out.getvalue()))
|
||||||
|
|
||||||
def test_makemigrations_non_interactive_not_null_addition(self):
|
def test_makemigrations_non_interactive_not_null_addition(self):
|
||||||
"""
|
"""
|
||||||
Tests that non-interactive makemigrations fails when a default is missing on a new not-null field.
|
Tests that non-interactive makemigrations fails when a default is missing on a new not-null field.
|
||||||
@ -793,7 +805,7 @@ class MakeMigrationsTests(MigrationTestBase):
|
|||||||
"""
|
"""
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
||||||
call_command("makemigrations", "migrations", merge=True, interactive=False, stdout=out)
|
call_command("makemigrations", "migrations", name="merge", merge=True, interactive=False, stdout=out)
|
||||||
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
||||||
self.assertTrue(os.path.exists(merge_file))
|
self.assertTrue(os.path.exists(merge_file))
|
||||||
output = force_text(out.getvalue())
|
output = force_text(out.getvalue())
|
||||||
@ -809,7 +821,10 @@ class MakeMigrationsTests(MigrationTestBase):
|
|||||||
"""
|
"""
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
||||||
call_command("makemigrations", "migrations", dry_run=True, merge=True, interactive=False, stdout=out)
|
call_command(
|
||||||
|
"makemigrations", "migrations", name="merge", dry_run=True,
|
||||||
|
merge=True, interactive=False, stdout=out,
|
||||||
|
)
|
||||||
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
||||||
self.assertFalse(os.path.exists(merge_file))
|
self.assertFalse(os.path.exists(merge_file))
|
||||||
output = force_text(out.getvalue())
|
output = force_text(out.getvalue())
|
||||||
@ -825,8 +840,10 @@ class MakeMigrationsTests(MigrationTestBase):
|
|||||||
"""
|
"""
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
||||||
call_command("makemigrations", "migrations", dry_run=True, merge=True, interactive=False,
|
call_command(
|
||||||
stdout=out, verbosity=3)
|
"makemigrations", "migrations", name="merge", dry_run=True,
|
||||||
|
merge=True, interactive=False, stdout=out, verbosity=3,
|
||||||
|
)
|
||||||
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
||||||
self.assertFalse(os.path.exists(merge_file))
|
self.assertFalse(os.path.exists(merge_file))
|
||||||
output = force_text(out.getvalue())
|
output = force_text(out.getvalue())
|
||||||
@ -928,7 +945,7 @@ class MakeMigrationsTests(MigrationTestBase):
|
|||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='N')):
|
with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='N')):
|
||||||
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
||||||
call_command("makemigrations", "migrations", merge=True, stdout=out)
|
call_command("makemigrations", "migrations", name="merge", merge=True, stdout=out)
|
||||||
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
||||||
# This will fail if interactive is False by default
|
# This will fail if interactive is False by default
|
||||||
self.assertFalse(os.path.exists(merge_file))
|
self.assertFalse(os.path.exists(merge_file))
|
||||||
@ -959,7 +976,7 @@ class MakeMigrationsTests(MigrationTestBase):
|
|||||||
with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='y')):
|
with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='y')):
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
with self.temporary_migration_module(app_label="migrated_app") as migration_dir:
|
with self.temporary_migration_module(app_label="migrated_app") as migration_dir:
|
||||||
call_command("makemigrations", "migrated_app", merge=True, interactive=True, stdout=out)
|
call_command("makemigrations", "migrated_app", name="merge", merge=True, interactive=True, stdout=out)
|
||||||
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
||||||
self.assertFalse(os.path.exists(merge_file))
|
self.assertFalse(os.path.exists(merge_file))
|
||||||
self.assertIn("No conflicts detected to merge.", out.getvalue())
|
self.assertIn("No conflicts detected to merge.", out.getvalue())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user