Skip to content

Commit

Permalink
Fixed #23302 -- Added --name/-n option to makemigrations command
Browse files Browse the repository at this point in the history
  • Loading branch information
rsalmaso authored and timgraham committed Sep 3, 2014
1 parent bda2809 commit 1435cfb
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 6 deletions.
10 changes: 9 additions & 1 deletion django/core/management/commands/makemigrations.py
Expand Up @@ -28,6 +28,8 @@ def add_arguments(self, parser):
help="Create an empty migration.")
parser.add_argument('--noinput', action='store_false', dest='interactive', default=True,
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):

Expand All @@ -36,6 +38,7 @@ def handle(self, *app_labels, **options):
self.dry_run = options.get('dry_run', False)
self.merge = options.get('merge', False)
self.empty = options.get('empty', False)
self.migration_name = options.get('name', None)

# Make sure the app they asked for exists
app_labels = set(app_labels)
Expand Down Expand Up @@ -98,7 +101,11 @@ def handle(self, *app_labels, **options):
(app, [Migration("custom", app)])
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)
return

Expand All @@ -107,6 +114,7 @@ def handle(self, *app_labels, **options):
graph=loader.graph,
trim_to_apps=app_labels or None,
convert_apps=app_labels or None,
migration_name=self.migration_name,
)

# No changes? Tell them.
Expand Down
10 changes: 5 additions & 5 deletions django/db/migrations/autodetector.py
Expand Up @@ -31,14 +31,14 @@ def __init__(self, from_state, to_state, questioner=None):
self.to_state = to_state
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.
Takes a graph to base names on and an optional set of apps
to try and restrict to (restriction is not guaranteed)
"""
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:
changes = self._trim_to_apps(changes, trim_to_apps)
return changes
Expand Down Expand Up @@ -951,7 +951,7 @@ def generate_altered_order_with_respect_to(self):
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,
and fixes the names and dependencies of the changes so they
Expand Down Expand Up @@ -985,11 +985,11 @@ def arrange_for_graph(self, changes, graph):
if i == 0 and app_leaf:
migration.dependencies.append(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:
new_name = "%04i_%s" % (
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)
next_number += 1
Expand Down
7 changes: 7 additions & 0 deletions docs/ref/django-admin.txt
Expand Up @@ -683,6 +683,13 @@ written.

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>]]
---------------------------------------

Expand Down
3 changes: 3 additions & 0 deletions docs/releases/1.8.txt
Expand Up @@ -228,6 +228,9 @@ Management Commands
* The :djadmin:`dbshell` command now supports MySQL's optional SSL certificate
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
^^^^^^

Expand Down
7 changes: 7 additions & 0 deletions docs/topics/migrations.txt 100755 → 100644
Expand Up @@ -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
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
Expand Down
25 changes: 25 additions & 0 deletions tests/migrations/test_autodetector.py
Expand Up @@ -188,6 +188,31 @@ def test_trim_apps(self):
self.assertEqual(changes["otherapp"][0].name, "0001_initial")
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):
"Tests autodetection of new models"
# Make state
Expand Down
31 changes: 31 additions & 0 deletions tests/migrations/test_commands.py
Expand Up @@ -546,3 +546,34 @@ def test_makemigrations_unspecified_app_with_conflict_merge(self):
questioner.input = old_input
if os.path.exists(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)

0 comments on commit 1435cfb

Please sign in to comment.