Fixed #23302 -- Added --name/-n option to makemigrations command

This commit is contained in:
Raffaele Salmaso 2014-08-19 15:24:31 +02:00 committed by Tim Graham
parent bda2809712
commit 1435cfbe8d
7 changed files with 87 additions and 6 deletions

View File

@ -28,6 +28,8 @@ class Command(BaseCommand):
help="Create an empty migration.") help="Create an empty migration.")
parser.add_argument('--noinput', action='store_false', dest='interactive', default=True, parser.add_argument('--noinput', action='store_false', dest='interactive', default=True,
help='Tells Django to NOT prompt the user for input of any kind.') help='Tells Django to NOT prompt the user for input of any kind.')
parser.add_argument('-n', '--name', action='store', dest='name', default=None,
help="Use this name for migration file(s).")
def handle(self, *app_labels, **options): def handle(self, *app_labels, **options):
@ -36,6 +38,7 @@ class Command(BaseCommand):
self.dry_run = options.get('dry_run', False) self.dry_run = options.get('dry_run', False)
self.merge = options.get('merge', False) self.merge = options.get('merge', False)
self.empty = options.get('empty', False) self.empty = options.get('empty', False)
self.migration_name = options.get('name', None)
# Make sure the app they asked for exists # Make sure the app they asked for exists
app_labels = set(app_labels) app_labels = set(app_labels)
@ -98,7 +101,11 @@ class Command(BaseCommand):
(app, [Migration("custom", app)]) (app, [Migration("custom", app)])
for app in app_labels for app in app_labels
) )
changes = autodetector.arrange_for_graph(changes, loader.graph) changes = autodetector.arrange_for_graph(
changes=changes,
graph=loader.graph,
migration_name=self.migration_name,
)
self.write_migration_files(changes) self.write_migration_files(changes)
return return
@ -107,6 +114,7 @@ class Command(BaseCommand):
graph=loader.graph, graph=loader.graph,
trim_to_apps=app_labels or None, trim_to_apps=app_labels or None,
convert_apps=app_labels or None, convert_apps=app_labels or None,
migration_name=self.migration_name,
) )
# No changes? Tell them. # No changes? Tell them.

View File

@ -31,14 +31,14 @@ class MigrationAutodetector(object):
self.to_state = to_state self.to_state = to_state
self.questioner = questioner or MigrationQuestioner() self.questioner = questioner or MigrationQuestioner()
def changes(self, graph, trim_to_apps=None, convert_apps=None): def changes(self, graph, trim_to_apps=None, convert_apps=None, migration_name=None):
""" """
Main entry point to produce a list of appliable changes. Main entry point to produce a list of appliable changes.
Takes a graph to base names on and an optional set of apps Takes a graph to base names on and an optional set of apps
to try and restrict to (restriction is not guaranteed) to try and restrict to (restriction is not guaranteed)
""" """
changes = self._detect_changes(convert_apps, graph) changes = self._detect_changes(convert_apps, graph)
changes = self.arrange_for_graph(changes, graph) changes = self.arrange_for_graph(changes, graph, migration_name)
if trim_to_apps: if trim_to_apps:
changes = self._trim_to_apps(changes, trim_to_apps) changes = self._trim_to_apps(changes, trim_to_apps)
return changes return changes
@ -951,7 +951,7 @@ class MigrationAutodetector(object):
dependencies=dependencies, dependencies=dependencies,
) )
def arrange_for_graph(self, changes, graph): def arrange_for_graph(self, changes, graph, migration_name=None):
""" """
Takes in a result from changes() and a MigrationGraph, Takes in a result from changes() and a MigrationGraph,
and fixes the names and dependencies of the changes so they and fixes the names and dependencies of the changes so they
@ -985,11 +985,11 @@ class MigrationAutodetector(object):
if i == 0 and app_leaf: if i == 0 and app_leaf:
migration.dependencies.append(app_leaf) migration.dependencies.append(app_leaf)
if i == 0 and not app_leaf: if i == 0 and not app_leaf:
new_name = "0001_initial" new_name = "0001_%s" % migration_name if migration_name else "0001_initial"
else: else:
new_name = "%04i_%s" % ( new_name = "%04i_%s" % (
next_number, next_number,
self.suggest_name(migration.operations)[:100], migration_name or self.suggest_name(migration.operations)[:100],
) )
name_map[(app_label, migration.name)] = (app_label, new_name) name_map[(app_label, migration.name)] = (app_label, new_name)
next_number += 1 next_number += 1

View File

@ -683,6 +683,13 @@ written.
The ``--merge`` option enables fixing of migration conflicts. The ``--merge`` option enables fixing of migration conflicts.
.. django-admin-option:: --name, -n
.. versionadded:: 1.8
The ``--name`` option allows you to give the migration(s) a custom name instead
of a generated one.
migrate [<app_label> [<migrationname>]] migrate [<app_label> [<migrationname>]]
--------------------------------------- ---------------------------------------

View File

@ -228,6 +228,9 @@ Management Commands
* The :djadmin:`dbshell` command now supports MySQL's optional SSL certificate * The :djadmin:`dbshell` command now supports MySQL's optional SSL certificate
authority setting (``--ssl-ca``). authority setting (``--ssl-ca``).
* The :djadminopt:`--name` option for :djadmin:`makemigrations` allows you to
to give the migration(s) a custom name instead of a generated one.
Models Models
^^^^^^ ^^^^^^

7
docs/topics/migrations.txt Executable file → Normal file
View File

@ -309,6 +309,13 @@ Note that this only works given two things:
that your database doesn't match your models, you'll just get errors when that your database doesn't match your models, you'll just get errors when
migrations try to modify those tables. migrations try to modify those tables.
.. versionadded:: 1.8
If you want to give the migration(s) a meaningful name instead of a generated one,
you can use the :djadminopt:`--name` option::
$ python manage.py makemigrations --name changed_my_model your_app_label
.. _historical-models: .. _historical-models:
Historical models Historical models

View File

@ -188,6 +188,31 @@ class AutodetectorTests(TestCase):
self.assertEqual(changes["otherapp"][0].name, "0001_initial") self.assertEqual(changes["otherapp"][0].name, "0001_initial")
self.assertNotIn("thirdapp", changes) self.assertNotIn("thirdapp", changes)
def test_custom_migration_name(self):
"Tests custom naming of migrations for graph matching."
# Make a fake graph
graph = MigrationGraph()
graph.add_node(("testapp", "0001_initial"), None)
graph.add_node(("testapp", "0002_foobar"), None)
graph.add_node(("otherapp", "0001_initial"), None)
graph.add_dependency("testapp.0002_foobar", ("testapp", "0002_foobar"), ("testapp", "0001_initial"))
# Use project state to make a new migration change set
before = self.make_project_state([])
after = self.make_project_state([self.author_empty, self.other_pony, self.other_stable])
autodetector = MigrationAutodetector(before, after)
changes = autodetector._detect_changes()
# Run through arrange_for_graph
migration_name = 'custom_name'
changes = autodetector.arrange_for_graph(changes, graph, migration_name)
# Make sure there's a new name, deps match, etc.
self.assertEqual(changes["testapp"][0].name, "0003_%s" % migration_name)
self.assertEqual(changes["testapp"][0].dependencies, [("testapp", "0002_foobar")])
self.assertEqual(changes["otherapp"][0].name, "0002_%s" % migration_name)
self.assertEqual(changes["otherapp"][0].dependencies, [("otherapp", "0001_initial")])
def test_new_model(self): def test_new_model(self):
"Tests autodetection of new models" "Tests autodetection of new models"
# Make state # Make state

View File

@ -546,3 +546,34 @@ class MakeMigrationsTests(MigrationTestBase):
questioner.input = old_input questioner.input = old_input
if os.path.exists(merge_file): if os.path.exists(merge_file):
os.remove(merge_file) os.remove(merge_file)
@override_system_checks([])
def test_makemigrations_with_custom_name(self):
"""
Makes sure that makemigrations generate a custom migration.
"""
def cmd(migration_count, migration_name, *args):
with override_settings(MIGRATION_MODULES={"migrations": self.migration_pkg}):
try:
call_command("makemigrations", "migrations", "--verbosity", "0", "--name", migration_name, *args)
except CommandError:
self.fail("Makemigrations errored in creating empty migration with custom name for a proper app.")
migration_file = os.path.join(self.migration_dir, "%s_%s.py" % (migration_count, migration_name))
# Check for existing migration file in migration folder
self.assertTrue(os.path.exists(migration_file))
with codecs.open(migration_file, "r", encoding="utf-8") as fp:
content = fp.read()
self.assertTrue("# -*- coding: utf-8 -*-" in content)
content = content.replace(" ", "")
return content
# generate an initial migration
migration_name_0001 = "my_initial_migration"
content = cmd("0001", migration_name_0001)
self.assertIn("dependencies=[\n]", content)
# generate an empty migration
migration_name_0002 = "my_custom_migration"
content = cmd("0002", migration_name_0002, "--empty")
self.assertIn("dependencies=[\n('migrations','0001_%s'),\n]" % migration_name_0001, content)
self.assertIn("operations=[\n]", content)