diff --git a/django/core/management/commands/makemigrations.py b/django/core/management/commands/makemigrations.py index cdccb8e9b39..5782b583982 100644 --- a/django/core/management/commands/makemigrations.py +++ b/django/core/management/commands/makemigrations.py @@ -48,6 +48,10 @@ class Command(BaseCommand): '-n', '--name', help="Use this name for migration file(s).", ) + parser.add_argument( + '--no-header', action='store_false', dest='include_header', + help='Do not add header comments to new migration file(s).', + ) parser.add_argument( '--check', action='store_true', dest='check_changes', help='Exit with a non-zero status if model changes are missing migrations.', @@ -63,6 +67,7 @@ class Command(BaseCommand): self.migration_name = options['name'] if self.migration_name and not self.migration_name.isidentifier(): raise CommandError('The migration name must be a valid Python identifier.') + self.include_header = options['include_header'] check_changes = options['check_changes'] # Make sure the app they asked for exists @@ -188,7 +193,7 @@ class Command(BaseCommand): self.stdout.write(self.style.MIGRATE_HEADING("Migrations for '%s':" % app_label) + "\n") for migration in app_migrations: # Describe the migration - writer = MigrationWriter(migration) + writer = MigrationWriter(migration, self.include_header) if self.verbosity >= 1: # Display a relative path if it's below the current working # directory, or an absolute path otherwise. @@ -288,7 +293,7 @@ class Command(BaseCommand): 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, self.include_header) if not self.dry_run: # Write the merge migrations file to the disk diff --git a/django/core/management/commands/squashmigrations.py b/django/core/management/commands/squashmigrations.py index ee91241369e..4a0ab6af836 100644 --- a/django/core/management/commands/squashmigrations.py +++ b/django/core/management/commands/squashmigrations.py @@ -37,6 +37,10 @@ class Command(BaseCommand): '--squashed-name', help='Sets the name of the new squashed migration.', ) + parser.add_argument( + '--no-header', action='store_false', dest='include_header', + help='Do not add a header comment to the new squashed migration.', + ) def handle(self, **options): @@ -47,6 +51,7 @@ class Command(BaseCommand): migration_name = options['migration_name'] no_optimize = options['no_optimize'] squashed_name = options['squashed_name'] + include_header = options['include_header'] # Validate app_label. try: apps.get_app_config(app_label) @@ -178,7 +183,7 @@ class Command(BaseCommand): new_migration.initial = True # Write out the new migration file - writer = MigrationWriter(new_migration) + writer = MigrationWriter(new_migration, include_header) with open(writer.path, "w", encoding='utf-8') as fh: fh.write(writer.as_string()) diff --git a/django/db/migrations/writer.py b/django/db/migrations/writer.py index d056e006462..6a62b4cbf4f 100644 --- a/django/db/migrations/writer.py +++ b/django/db/migrations/writer.py @@ -132,8 +132,9 @@ class MigrationWriter: of the migration file from it. """ - def __init__(self, migration): + def __init__(self, migration, include_header=True): self.migration = migration + self.include_header = include_header self.needs_manual_porting = False def as_string(self): @@ -195,10 +196,13 @@ class MigrationWriter: if self.migration.replaces: items['replaces_str'] = "\n replaces = %s\n" % self.serialize(self.migration.replaces)[0] # Hinting that goes into comment - items.update( - version=get_version(), - timestamp=now().strftime("%Y-%m-%d %H:%M"), - ) + if self.include_header: + items['migration_header'] = MIGRATION_HEADER_TEMPLATE % { + 'version': get_version(), + 'timestamp': now().strftime("%Y-%m-%d %H:%M"), + } + else: + items['migration_header'] = "" if self.migration.initial: items['initial_str'] = "\n initial = True\n" @@ -279,10 +283,14 @@ class MigrationWriter: return serializer_factory(value).serialize() -MIGRATION_TEMPLATE = """\ +MIGRATION_HEADER_TEMPLATE = """\ # Generated by Django %(version)s on %(timestamp)s -%(imports)s +""" + + +MIGRATION_TEMPLATE = """\ +%(migration_header)s%(imports)s class Migration(migrations.Migration): %(replaces_str)s%(initial_str)s diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 19f1d046fe0..e8371897e2d 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -776,6 +776,12 @@ Enables fixing of migration conflicts. Allows naming the generated migration(s) instead of using a generated name. The name must be a valid Python :ref:`identifier `. +.. django-admin-option:: --no-header + +.. versionadded:: 2.2 + +Generate migration files without Django version and timestamp header. + .. django-admin-option:: --check Makes ``makemigrations`` exit with a non-zero status when model changes without @@ -1156,6 +1162,12 @@ Suppresses all user prompts. Sets the name of the squashed migration. When omitted, the name is based on the first and last migration, with ``_squashed_`` in between. +.. django-admin-option:: --no-header + +.. versionadded:: 2.2 + +Generate squashed migration file without Django version and timestamp header. + ``startapp`` ------------ diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt index e6162a7fe43..21786d6d633 100644 --- a/docs/releases/2.2.txt +++ b/docs/releases/2.2.txt @@ -194,6 +194,10 @@ Management Commands * On Oracle, :djadmin:`dbshell` is wrapped with ``rlwrap``, if available. ``rlwrap`` provides a command history and editing of keyboard input. +* The new :option:`makemigrations --no-header` option avoids writing header + comments in generated migration file(s). This option is also available for + :djadmin:`squashmigrations`. + Migrations ~~~~~~~~~~ diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py index f7ec6f4bd3f..467ff4475b2 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -613,16 +613,21 @@ class WriterTests(SimpleTestCase): }) dt = datetime.datetime(2015, 7, 31, 4, 40, 0, 0, tzinfo=utc) with mock.patch('django.db.migrations.writer.now', lambda: dt): - writer = MigrationWriter(migration) - output = writer.as_string() + for include_header in (True, False): + with self.subTest(include_header=include_header): + writer = MigrationWriter(migration, include_header) + output = writer.as_string() - self.assertTrue( - output.startswith( - "# Generated by Django %(version)s on 2015-07-31 04:40\n" % { - 'version': get_version(), - } - ) - ) + self.assertEqual( + include_header, + output.startswith( + "# Generated by Django %s on 2015-07-31 04:40\n\n" % get_version() + ) + ) + if not include_header: + # Make sure the output starts with something that's not + # a comment or indentation or blank line + self.assertRegex(output.splitlines(keepends=True)[0], r"^[^#\s]+") def test_models_import_omitted(self): """