summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaphael Gaschignard <r.gaschignard@gmail.com>2016-05-11 21:19:19 +0900
committerTim Graham <timograham@gmail.com>2016-05-11 08:19:19 -0400
commit8f6a1a15516629b30e2fa2c48d5e682f7955868c (patch)
tree9c0401fcdd2c61a2267bf5d17aa538d22bc76e3f
parent535660b852c8899a17b82d20a06c926d8d437db3 (diff)
Fixed #26429 -- Added a timestamp to merge migration names.
This reduces the possibility of a naming conflict, especially after squashing migrations.
-rw-r--r--django/core/management/__init__.py16
-rw-r--r--django/core/management/commands/makemigrations.py7
-rw-r--r--django/db/migrations/autodetector.py7
-rw-r--r--django/db/migrations/utils.py5
-rw-r--r--tests/migrations/test_commands.py33
5 files changed, 48 insertions, 20 deletions
diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py
index aa649176d2..739dc25297 100644
--- a/django/core/management/__init__.py
+++ b/django/core/management/__init__.py
@@ -76,7 +76,7 @@ def get_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.
@@ -95,25 +95,25 @@ def call_command(name, *args, **options):
call_command(cmd, verbosity=0, interactive=False)
# Do something with cmd ...
"""
- if isinstance(name, BaseCommand):
+ if isinstance(command_name, BaseCommand):
# Command object passed in.
- command = name
- name = command.__class__.__module__.split('.')[-1]
+ command = command_name
+ command_name = command.__class__.__module__.split('.')[-1]
else:
# Load the command object by name.
try:
- app_name = get_commands()[name]
+ app_name = get_commands()[command_name]
except KeyError:
- raise CommandError("Unknown command: %r" % name)
+ raise CommandError("Unknown command: %r" % command_name)
if isinstance(app_name, BaseCommand):
# If the command is already loaded, use it directly.
command = app_name
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).
- parser = command.create_parser('', name)
+ parser = command.create_parser('', command_name)
# Use the `dest` option name from the parser option
opt_mapping = {
sorted(s_opt.option_strings)[0].lstrip('-').replace('-', '_'): s_opt.dest
diff --git a/django/core/management/commands/makemigrations.py b/django/core/management/commands/makemigrations.py
index 6766434193..b8c05486fc 100644
--- a/django/core/management/commands/makemigrations.py
+++ b/django/core/management/commands/makemigrations.py
@@ -14,6 +14,7 @@ from django.db.migrations.questioner import (
NonInteractiveMigrationQuestioner,
)
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.utils.deprecation import RemovedInDjango20Warning
from django.utils.six import iteritems
@@ -283,7 +284,11 @@ class Command(BaseCommand):
subclass = type("Migration", (Migration, ), {
"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)
if not self.dry_run:
diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py
index 85e50746db..08baf473b0 100644
--- a/django/db/migrations/autodetector.py
+++ b/django/db/migrations/autodetector.py
@@ -1,6 +1,5 @@
from __future__ import unicode_literals
-import datetime
import functools
import re
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.optimizer import MigrationOptimizer
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 .topological_sort import stable_topological_sort
@@ -1154,7 +1155,7 @@ class MigrationAutodetector(object):
elif len(ops) > 1:
if all(isinstance(o, operations.CreateModel) 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
def parse_number(cls, name):
diff --git a/django/db/migrations/utils.py b/django/db/migrations/utils.py
index 5bea82dc51..b54d7ed120 100644
--- a/django/db/migrations/utils.py
+++ b/django/db/migrations/utils.py
@@ -1,3 +1,4 @@
+import datetime
import re
COMPILED_REGEX_TYPE = type(re.compile(''))
@@ -10,3 +11,7 @@ class RegexObject(object):
def __eq__(self, other):
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")
diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py
index 4d1be3f58f..cc655f112f 100644
--- a/tests/migrations/test_commands.py
+++ b/tests/migrations/test_commands.py
@@ -2,6 +2,7 @@
from __future__ import unicode_literals
import codecs
+import datetime
import importlib
import os
import sys
@@ -705,7 +706,7 @@ class MakeMigrationsTests(MigrationTestBase):
# Monkeypatch interactive questioner to auto reject
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:
- 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')
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')):
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)
+ call_command("makemigrations", "migrations", name="merge", merge=True, interactive=True, stdout=out)
merge_file = os.path.join(migration_dir, '0003_merge.py')
self.assertTrue(os.path.exists(merge_file))
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):
"""
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()
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')
self.assertTrue(os.path.exists(merge_file))
output = force_text(out.getvalue())
@@ -809,7 +821,10 @@ class MakeMigrationsTests(MigrationTestBase):
"""
out = six.StringIO()
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')
self.assertFalse(os.path.exists(merge_file))
output = force_text(out.getvalue())
@@ -825,8 +840,10 @@ class MakeMigrationsTests(MigrationTestBase):
"""
out = six.StringIO()
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, verbosity=3)
+ call_command(
+ "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')
self.assertFalse(os.path.exists(merge_file))
output = force_text(out.getvalue())
@@ -928,7 +945,7 @@ class MakeMigrationsTests(MigrationTestBase):
out = six.StringIO()
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:
- 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')
# This will fail if interactive is False by default
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')):
out = six.StringIO()
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')
self.assertFalse(os.path.exists(merge_file))
self.assertIn("No conflicts detected to merge.", out.getvalue())