예제 #1
0
 def write_migration_files(self, changes):
     """
     Takes a changes dict and writes them out as migration files.
     """
     directory_created = {}
     for app_label, app_migrations in changes.items():
         if self.verbosity >= 1:
             self.stdout.write(self.style.MIGRATE_HEADING("Migrations for '%s':" % app_label) + "\n")
         for migration in app_migrations:
             # Describe the migration
             writer = MigrationWriter(migration)
             if self.verbosity >= 1:
                 self.stdout.write("  %s:\n" % (self.style.MIGRATE_LABEL(writer.filename),))
                 for operation in migration.operations:
                     self.stdout.write("    - %s\n" % operation.describe())
             if not self.dry_run:
                 # Write the migrations file to the disk.
                 migrations_directory = os.path.dirname(writer.path)
                 if not directory_created.get(app_label, False):
                     if not os.path.isdir(migrations_directory):
                         os.mkdir(migrations_directory)
                     init_path = os.path.join(migrations_directory, "__init__.py")
                     if not os.path.isfile(init_path):
                         open(init_path, "w").close()
                     # We just do this once per app
                     directory_created[app_label] = True
                 migration_string = writer.as_string()
                 with open(writer.path, "wb") as fh:
                     fh.write(migration_string)
             elif self.verbosity == 3:
                 # Alternatively, makemigrations --dry-run --verbosity 3
                 # will output the migrations to stdout rather than saving
                 # the file to the disk.
                 self.stdout.write(self.style.MIGRATE_HEADING("Full migrations file '%s':" % writer.filename) + "\n")
                 self.stdout.write("%s\n" % writer.as_string())
예제 #2
0
def test_user_foreign_key_supports_migration():
    """Tests serializing UserForeignKey in a simple migration.

    Since `UserForeignKey` is a ForeignKey migrations pass `to=` explicitly
    and we have to pop it in our __init__.
    """
    fields = {
        'charfield': UserForeignKey(),
    }

    migration = type(str('Migration'), (migrations.Migration,), {
        'operations': [
            migrations.CreateModel(
                name='MyModel', fields=tuple(fields.items()),
                bases=(models.Model,)
            ),
        ],
    })
    writer = MigrationWriter(migration)
    output = writer.as_string()

    # Just make sure it runs and that things look alright.
    result = safe_exec(output, globals_=globals())

    assert 'Migration' in result
예제 #3
0
    def test_total_deconstruct(self):
        loader = MigrationLoader(None, load=True, ignore_no_migrations=True)
        loader.disk_migrations = {t: v for t, v in loader.disk_migrations.items() if t[0] != 'testapp'}
        app_labels = {"testapp"}
        questioner = NonInteractiveMigrationQuestioner(specified_apps=app_labels, dry_run=True)
        autodetector = MigrationAutodetector(
            loader.project_state(),
            ProjectState.from_apps(apps),
            questioner,
        )
        # Detect changes
        changes = autodetector.changes(
            graph=loader.graph,
            trim_to_apps=app_labels or None,
            convert_apps=app_labels or None,
            migration_name="my_fake_migration_for_test_deconstruct",
        )
        self.assertGreater(len(changes), 0)
        for app_label, app_migrations in changes.items():
            for migration in app_migrations:
                # Describe the migration
                writer = MigrationWriter(migration)

                migration_string = writer.as_string()
                self.assertNotEqual(migration_string, "")
예제 #4
0
    def test_migration(self):
        """
        Tests making migrations with Django 1.7+'s migration framework
        """

        import oscar
        from django.db import migrations
        from django.db.migrations.writer import MigrationWriter
        from django.utils import six
        from oscar.models.fields import AutoSlugField
        fields = {
            'autoslugfield': AutoSlugField(populate_from='otherfield'),
        }

        migration = type(str("Migration"), (migrations.Migration,), {
            "operations": [
                migrations.CreateModel("MyModel", tuple(fields.items()),
                                       {'populate_from': 'otherfield'},
                                       (models.Model,)),
            ],
        })
        writer = MigrationWriter(migration)
        output = writer.as_string()

        if isinstance(output, six.text_type):
            output = output.encode('utf-8')

        # We don't test the output formatting - that's too fragile.
        # Just make sure it runs for now, and that things look alright.
        context = {
            'migrations': migrations,
            'oscar': oscar,
        }
        result = self.safe_exec(output, context=context)
        self.assertIn("Migration", result)
예제 #5
0
    def test_simple_migration(self):
        """
        Tests serializing a simple migration.
        """
        fields = {
            'charfield': models.DateTimeField(default=datetime.datetime.utcnow),
            'datetimefield': models.DateTimeField(default=datetime.datetime.utcnow),
        }

        options = {
            'verbose_name': 'My model',
            'verbose_name_plural': 'My models',
        }

        migration = type("Migration", (migrations.Migration,), {
            "operations": [
                migrations.CreateModel("MyModel", tuple(fields.items()), options, (models.Model,)),
                migrations.CreateModel("MyModel2", tuple(fields.items()), bases=(models.Model,)),
                migrations.CreateModel(
                    name="MyModel3", fields=tuple(fields.items()), options=options, bases=(models.Model,)
                ),
                migrations.DeleteModel("MyModel"),
                migrations.AddField("OtherModel", "datetimefield", fields["datetimefield"]),
            ],
            "dependencies": [("testapp", "some_other_one")],
        })
        writer = MigrationWriter(migration)
        output = writer.as_string()
        # We don't test the output formatting - that's too fragile.
        # Just make sure it runs for now, and that things look alright.
        result = self.safe_exec(output)
        self.assertIn("Migration", result)
예제 #6
0
 def test_simple_migration(self):
     """
     Tests serializing a simple migration.
     """
     migration = type(
         str("Migration"),
         (migrations.Migration,),
         {
             "operations": [
                 migrations.DeleteModel("MyModel"),
                 migrations.AddField(
                     "OtherModel", "field_name", models.DateTimeField(default=datetime.datetime.utcnow)
                 ),
             ],
             "dependencies": [("testapp", "some_other_one")],
         },
     )
     writer = MigrationWriter(migration)
     output = writer.as_string()
     # It should NOT be unicode.
     self.assertIsInstance(output, six.binary_type, "Migration as_string returned unicode")
     # We don't test the output formatting - that's too fragile.
     # Just make sure it runs for now, and that things look alright.
     result = self.safe_exec(output)
     self.assertIn("Migration", result)
    def test_17_migration(self):
        """
        Tests making migrations with Django 1.7+'s migration framework
        """

        fields = {
            'autoslugfield': AutoSlugField(populate_from='otherfield'),
        }

        migration = type(str("Migration"), (migrations.Migration,), {
            "operations": [
                migrations.CreateModel("MyModel", tuple(fields.items()),
                                       {'populate_from': 'otherfield'},
                                       (models.Model,)),
            ],
        })
        writer = MigrationWriter(migration)
        output = writer.as_string()
        # It should NOT be unicode.
        self.assertIsInstance(output, six.binary_type,
                              "Migration as_string returned unicode")
        # We don't test the output formatting - that's too fragile.
        # Just make sure it runs for now, and that things look alright.
        result = self.safe_exec(output)
        self.assertIn("Migration", result)
예제 #8
0
 def handle_merge(self, loader, conflicts):
     """
     Handles merging together conflicted migrations interactively,
     if it's safe; otherwise, advises on how to fix it.
     """
     if self.interactive:
         questioner = InteractiveMigrationQuestioner()
     else:
         questioner = MigrationQuestioner(defaults={"ask_merge": True})
     for app_label, migration_names in conflicts.items():
         # Grab out the migrations in question, and work out their
         # common ancestor.
         merge_migrations = []
         for migration_name in migration_names:
             migration = loader.get_migration(app_label, migration_name)
             migration.ancestry = loader.graph.forwards_plan((app_label, migration_name))
             merge_migrations.append(migration)
         all_items_equal = lambda seq: all(item == seq[0] for item in seq[1:])
         merge_migrations_generations = zip(*[m.ancestry for m in merge_migrations])
         common_ancestor_count = sum(
             1 for common_ancestor_generation in takewhile(all_items_equal, merge_migrations_generations)
         )
         if not common_ancestor_count:
             raise ValueError("Could not find common ancestor of %s" % migration_names)
         # Now work out the operations along each divergent branch
         for migration in merge_migrations:
             migration.branch = migration.ancestry[common_ancestor_count:]
             migrations_ops = (
                 loader.get_migration(node_app, node_name).operations for node_app, node_name in migration.branch
             )
             migration.merged_operations = sum(migrations_ops, [])
         # In future, this could use some of the Optimizer code
         # (can_optimize_through) to automatically see if they're
         # mergeable. For now, we always just prompt the user.
         if self.verbosity > 0:
             self.stdout.write(self.style.MIGRATE_HEADING("Merging %s" % app_label))
             for migration in merge_migrations:
                 self.stdout.write(self.style.MIGRATE_LABEL("  Branch %s" % migration.name))
                 for operation in migration.merged_operations:
                     self.stdout.write("    - %s\n" % operation.describe())
         if questioner.ask_merge(app_label):
             # If they still want to merge it, then write out an empty
             # file depending on the migrations needing merging.
             numbers = [MigrationAutodetector.parse_number(migration.name) for migration in merge_migrations]
             try:
                 biggest_number = max([x for x in numbers if x is not None])
             except ValueError:
                 biggest_number = 1
             subclass = type(
                 "Migration",
                 (Migration,),
                 {"dependencies": [(app_label, migration.name) for migration in merge_migrations]},
             )
             new_migration = subclass("%04i_merge" % (biggest_number + 1), app_label)
             writer = MigrationWriter(new_migration)
             with open(writer.path, "wb") as fh:
                 fh.write(writer.as_string())
             if self.verbosity > 0:
                 self.stdout.write("\nCreated new merge migration %s" % writer.path)
예제 #9
0
 def write_migration_files(self, changes):
     """
     Take a changes dict and write them out as migration files.
     """
     directory_created = {}
     for app_label, app_migrations in changes.items():
         if self.verbosity >= 1:
             self.stdout.write(self.style.MIGRATE_HEADING("Migrations for '%s':" % app_label) + "\n")
         for migration in app_migrations:
             # Describe the migration
             writer = MigrationWriter(migration)
             if self.verbosity >= 1:
                 # Display a relative path if it's below the current working
                 # directory, or an absolute path otherwise.
                 try:
                     migration_string = os.path.relpath(writer.path)
                 except ValueError:
                     migration_string = writer.path
                 if migration_string.startswith('..'):
                     migration_string = writer.path
                 self.stdout.write("  %s\n" % (self.style.MIGRATE_LABEL(migration_string),))
                 for operation in migration.operations:
                     self.stdout.write("    - %s\n" % operation.describe())
             if not self.dry_run:
                 # Write the migrations file to the disk.
                 migrations_directory = os.path.dirname(writer.path)
                 if not directory_created.get(app_label):
                     if not os.path.isdir(migrations_directory):
                         os.mkdir(migrations_directory)
                     init_path = os.path.join(migrations_directory, "__init__.py")
                     if not os.path.isfile(init_path):
                         open(init_path, "w").close()
                     # We just do this once per app
                     directory_created[app_label] = True
                 migration_string = writer.as_string()
                 with open(writer.path, "w", encoding='utf-8') as fh:
                     fh.write(migration_string)
             elif self.verbosity == 3:
                 # Alternatively, makemigrations --dry-run --verbosity 3
                 # will output the migrations to stdout rather than saving
                 # the file to the disk.
                 self.stdout.write(self.style.MIGRATE_HEADING(
                     "Full migrations file '%s':" % writer.filename) + "\n"
                 )
                 self.stdout.write("%s\n" % writer.as_string())
예제 #10
0
    def write_migration(self, migration):
        loader = MigrationLoader(None, ignore_no_migrations=True)
        autodetector = MigrationAutodetector(loader.project_state(), ProjectState.from_apps(apps),)
        changes = autodetector.arrange_for_graph(changes={'share': [migration]}, graph=loader.graph,)

        for m in changes['share']:
            writer = MigrationWriter(m)
            with open(writer.path, 'wb') as fp:
                fp.write(writer.as_string())
    def write_migration_files(self, changes):
        """ Takes a changes dict and writes them out as migration files. """

        MIGRATE_HEADING = self.style.MIGRATE_HEADING
        MIGRATE_LABEL = self.style.MIGRATE_LABEL
        write = self.stdout.write

        migrations_dir = os.path.join(
            settings.BASE_DIR, PROJECT_MIGRATIONS_MODULE_NAME)

        for app_label, app_migrations in changes.items():
            if self.verbosity >= 1:
                write(MIGRATE_HEADING(
                    "Migrations for '%s':" % app_label) + "\n"
                )
            for migration in app_migrations:
                # Describe the migration
                writer = MigrationWriter(migration)
                migration_name = app_label + "_" + writer.filename
                filename = os.path.join(migrations_dir, migration_name)

                if self.verbosity >= 1:
                    write("  %s:\n" % (MIGRATE_LABEL(migration_name),))
                    for operation in migration.operations:
                        write("    - %s\n" % operation.describe())
                if not self.dry_run:
                    # Write the migrations file to the disk.
                    migration_string = writer.as_string()
                    with open(filename, "wb") as fh:
                        fh.write(migration_string)
                elif self.verbosity == 3:
                    # Alternatively, makemigrations --dry-run --verbosity 3
                    # will output the migrations to stdout rather than saving
                    # the file to the disk.
                    write(MIGRATE_HEADING(
                        "Full migrations file '%s':" % migration_name) + "\n"
                    )
                    write("%s\n" % writer.as_string())
예제 #12
0
 def test_models_import_omitted(self):
     """
     django.db.models shouldn't be imported if unused.
     """
     migration = type("Migration", (migrations.Migration,), {
         "operations": [
             migrations.AlterModelOptions(
                 name='model',
                 options={'verbose_name': 'model', 'verbose_name_plural': 'models'},
             ),
         ]
     })
     writer = MigrationWriter(migration)
     output = writer.as_string()
     self.assertIn("from django.db import migrations\n", output)
예제 #13
0
파일: test_writer.py 프로젝트: 01-/django
    def test_simple_migration(self):
        """
        Tests serializing a simple migration.
        """
        fields = {
            'charfield': models.DateTimeField(default=datetime.datetime.utcnow),
            'datetimefield': models.DateTimeField(default=datetime.datetime.utcnow),
        }

        options = {
            'verbose_name': 'My model',
            'verbose_name_plural': 'My models',
        }

        migration = type(str("Migration"), (migrations.Migration,), {
            "operations": [
                migrations.CreateModel("MyModel", tuple(fields.items()), options, (models.Model,)),
                migrations.CreateModel("MyModel2", tuple(fields.items()), bases=(models.Model,)),
                migrations.CreateModel(
                    name="MyModel3", fields=tuple(fields.items()), options=options, bases=(models.Model,)
                ),
                migrations.DeleteModel("MyModel"),
                migrations.AddField("OtherModel", "datetimefield", fields["datetimefield"]),
            ],
            "dependencies": [("testapp", "some_other_one")],
        })
        writer = MigrationWriter(migration)
        output = writer.as_string()
        # It should NOT be unicode.
        self.assertIsInstance(output, six.binary_type, "Migration as_string returned unicode")
        # We don't test the output formatting - that's too fragile.
        # Just make sure it runs for now, and that things look alright.
        result = self.safe_exec(output)
        self.assertIn("Migration", result)
        # In order to preserve compatibility with Python 3.2 unicode literals
        # prefix shouldn't be added to strings.
        tokens = tokenize.generate_tokens(six.StringIO(str(output)).readline)
        for token_type, token_source, (srow, scol), __, line in tokens:
            if token_type == tokenize.STRING:
                self.assertFalse(
                    token_source.startswith('u'),
                    "Unicode literal prefix found at %d:%d: %r" % (
                        srow, scol, line.strip()
                    )
                )
예제 #14
0
 def test_custom_operation(self):
     migration = type("Migration", (migrations.Migration,), {
         "operations": [
             custom_migration_operations.operations.TestOperation(),
             custom_migration_operations.operations.CreateModel(),
             migrations.CreateModel("MyModel", (), {}, (models.Model,)),
             custom_migration_operations.more_operations.TestOperation()
         ],
         "dependencies": []
     })
     writer = MigrationWriter(migration)
     output = writer.as_string()
     result = self.safe_exec(output)
     self.assertIn("custom_migration_operations", result)
     self.assertNotEqual(
         result['custom_migration_operations'].operations.TestOperation,
         result['custom_migration_operations'].more_operations.TestOperation
     )
예제 #15
0
	def test_migration(self):
		fields = {
			'field1':EnumField(TestEnum)
		}
		migration = type(str("Migration"), (migrations.Migration,), {
			'operations' : [
				migrations.CreateModel("Model1", tuple(fields.items()), {}, (models.Model))
			]
		})
		writer = MigrationWriter(migration)
		output = writer.as_string()
		self.assertIsInstance(output, six.binary_type)
		r = {}
		try:
			exec(output, globals(), r)
		except Exception as e:
			self.fail("Could not exec {!r}: {}".format(output.strip(), e))
		self.assertIn("Migration", r)
예제 #16
0
    def test_migration_file_header_comments(self):
        """
        Test comments at top of file.
        """
        migration = type("Migration", (migrations.Migration,), {
            "operations": []
        })
        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()

        self.assertTrue(
            output.startswith(
                "# Generated by Django %(version)s on 2015-07-31 04:40\n" % {
                    'version': get_version(),
                }
            )
        )
예제 #17
0
 def test_sorted_imports(self):
     """
     #24155 - Tests ordering of imports.
     """
     migration = type("Migration", (migrations.Migration,), {
         "operations": [
             migrations.AddField("mymodel", "myfield", models.DateTimeField(
                 default=datetime.datetime(2012, 1, 1, 1, 1, tzinfo=utc),
             )),
         ]
     })
     writer = MigrationWriter(migration)
     output = writer.as_string()
     self.assertIn(
         "import datetime\n"
         "from django.db import migrations, models\n"
         "from django.utils.timezone import utc\n",
         output
     )
예제 #18
0
 def test_custom_operation(self):
     migration = type(
         "Migration",
         (migrations.Migration,),
         {
             "operations": [
                 custom_migration_operations.operations.TestOperation(),
                 custom_migration_operations.operations.CreateModel(),
                 migrations.CreateModel("MyModel", (), {}, (models.Model,)),
                 custom_migration_operations.more_operations.TestOperation(),
             ],
             "dependencies": [],
         },
     )
     writer = MigrationWriter(migration)
     output = writer.as_string()
     result = self.safe_exec(output)
     self.assertIn("custom_migration_operations", result)
     self.assertNotEqual(
         result["custom_migration_operations"].operations.TestOperation,
         result["custom_migration_operations"].more_operations.TestOperation,
     )
예제 #19
0
    def test_simple_migration(self):
        """
        Tests serializing a simple migration.
        """
        fields = {
            'charfield': models.DateTimeField(default=datetime.datetime.now),
            'datetimefield':
            models.DateTimeField(default=datetime.datetime.now),
        }

        options = {
            'verbose_name': 'My model',
            'verbose_name_plural': 'My models',
        }

        migration = type(
            "Migration", (migrations.Migration, ), {
                "operations": [
                    migrations.CreateModel("MyModel", tuple(fields.items()),
                                           options, (models.Model, )),
                    migrations.CreateModel("MyModel2",
                                           tuple(fields.items()),
                                           bases=(models.Model, )),
                    migrations.CreateModel(name="MyModel3",
                                           fields=tuple(fields.items()),
                                           options=options,
                                           bases=(models.Model, )),
                    migrations.DeleteModel("MyModel"),
                    migrations.AddField("OtherModel", "datetimefield",
                                        fields["datetimefield"]),
                ],
                "dependencies": [("testapp", "some_other_one")],
            })
        writer = MigrationWriter(migration)
        output = writer.as_string()
        # We don't test the output formatting - that's too fragile.
        # Just make sure it runs for now, and that things look alright.
        result = self.safe_exec(output)
        self.assertIn("Migration", result)
예제 #20
0
 def test_models_import_omitted(self):
     """
     django.db.models shouldn't be imported if unused.
     """
     migration = type(
         "Migration",
         (migrations.Migration,),
         {
             "operations": [
                 migrations.AlterModelOptions(
                     name="model",
                     options={
                         "verbose_name": "model",
                         "verbose_name_plural": "models",
                     },
                 ),
             ]
         },
     )
     writer = MigrationWriter(migration)
     output = writer.as_string()
     self.assertIn("from django.db import migrations\n", output)
예제 #21
0
    def test_migration_file_header_comments(self):
        """
        Test comments at top of file.
        """
        migration = type("Migration", (migrations.Migration, ),
                         {"operations": []})
        dt = datetime.datetime(2015, 7, 31, 4, 40, 0, 0, tzinfo=utc)
        with mock.patch('django.db.migrations.writer.now', lambda: dt):
            for include_header in (True, False):
                with self.subTest(include_header=include_header):
                    writer = MigrationWriter(migration, include_header)
                    output = writer.as_string()

                    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]+")
예제 #22
0
    def handle(self, *args, **options):
        changes = {}
        if options.get('providers'):
            configs = [apps.get_app_config(label) for label in options['providers']]
        else:
            configs = apps.get_app_configs()

        for config in configs:
            if isinstance(config, RobotAppConfig) and (options.get('disabled') or not getattr(config, 'disabled', False)):
                changes[config.name] = RobotMigrations(config).migrations()

        for migrations in changes.values():
            for m in migrations:
                writer = MigrationWriter(m)
                os.makedirs(os.path.dirname(writer.path), exist_ok=True)

                if not os.path.exists(os.path.join(os.path.dirname(writer.path), '__init__.py')):
                    with open(os.path.join(os.path.dirname(writer.path), '__init__.py'), 'wb') as fp:
                        fp.write(b'')

                if not os.path.exists(writer.path):
                    with open(writer.path, 'wb') as fp:
                        fp.write(writer.as_string())
예제 #23
0
 def test_simple_migration(self):
     """
     Tests serializing a simple migration.
     """
     migration = type(
         str("Migration"), (migrations.Migration, ), {
             "operations": [
                 migrations.DeleteModel("MyModel"),
                 migrations.AddField(
                     "OtherModel", "field_name",
                     models.DateTimeField(default=datetime.datetime.utcnow))
             ],
             "dependencies": [("testapp", "some_other_one")],
         })
     writer = MigrationWriter(migration)
     output = writer.as_string()
     # It should NOT be unicode.
     self.assertIsInstance(output, six.binary_type,
                           "Migration as_string returned unicode")
     # We don't test the output formatting - that's too fragile.
     # Just make sure it runs for now, and that things look alright.
     result = self.safe_exec(output)
     self.assertIn("Migration", result)
예제 #24
0
 def test_sorted_imports(self):
     """
     #24155 - Tests ordering of imports.
     """
     migration = type(
         "Migration",
         (migrations.Migration, ),
         {
             "operations": [
                 migrations.AddField(
                     "mymodel",
                     "myfield",
                     models.DateTimeField(default=datetime.datetime(
                         2012, 1, 1, 1, 1, tzinfo=datetime.timezone.utc), ),
                 ),
             ]
         },
     )
     writer = MigrationWriter(migration)
     output = writer.as_string()
     self.assertIn(
         "import datetime\nfrom django.db import migrations, models\n",
         output,
     )
예제 #25
0
    def test_migration(self):
        """
        Tests making migrations with Django 1.7+'s migration framework
        """

        import oscar
        from django.db import migrations
        from django.db.migrations.writer import MigrationWriter
        from django.utils import six
        from oscar.models.fields import AutoSlugField
        fields = {
            'autoslugfield': AutoSlugField(populate_from='otherfield'),
        }

        migration = type(
            str("Migration"), (migrations.Migration, ), {
                "operations": [
                    migrations.CreateModel("MyModel", tuple(
                        fields.items()), {'populate_from': 'otherfield'},
                                           (models.Model, )),
                ],
            })
        writer = MigrationWriter(migration)
        output = writer.as_string()

        if isinstance(output, six.text_type):
            output = output.encode('utf-8')

        # We don't test the output formatting - that's too fragile.
        # Just make sure it runs for now, and that things look alright.
        context = {
            'migrations': migrations,
            'oscar': oscar,
        }
        result = self.safe_exec(output, context=context)
        self.assertIn("Migration", result)
예제 #26
0
파일: test_writer.py 프로젝트: acdha/django
    def test_migration_file_header_comments(self):
        """
        Test comments at top of file.
        """
        migration = type("Migration", (migrations.Migration,), {
            "operations": []
        })
        dt = datetime.datetime(2015, 7, 31, 4, 40, 0, 0, tzinfo=utc)
        with mock.patch('django.db.migrations.writer.now', lambda: dt):
            for include_header in (True, False):
                with self.subTest(include_header=include_header):
                    writer = MigrationWriter(migration, include_header)
                    output = writer.as_string()

                    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]+")
예제 #27
0
    def test_only_black(self):
        writer = MigrationWriter(self.get_migration())
        writer._isort_installed = False
        with mock.patch("black.parse_pyproject_toml",
                        return_value={"line_length": 100}):
            # We only call `parse_pyproject_toml()` when we found a config file.
            # In that case, we mock the config here to be different to the
            # default one (e.g. 100 vs 88 chars per line)
            output = writer.as_string()
        expected = textwrap.dedent("""
                import datetime
                from django.db import migrations, models


                class Migration(migrations.Migration):

                    dependencies = [
                        ("testapp", "some_other_one"),
                    ]

                    operations = [
                        migrations.CreateModel(
                            name="MyModel",
                            fields=[
                                ("charfield", models.DateTimeField(default=datetime.datetime.utcnow)),
                                ("datetimefield", models.DateTimeField(default=datetime.datetime.utcnow)),
                            ],
                            options={
                                "verbose_name": "My model",
                                "verbose_name_plural": "My models",
                            },
                        ),
                        migrations.CreateModel(
                            name="MyModel2",
                            fields=[
                                ("charfield", models.DateTimeField(default=datetime.datetime.utcnow)),
                                ("datetimefield", models.DateTimeField(default=datetime.datetime.utcnow)),
                            ],
                        ),
                        migrations.CreateModel(
                            name="MyModel3",
                            fields=[
                                ("charfield", models.DateTimeField(default=datetime.datetime.utcnow)),
                                ("datetimefield", models.DateTimeField(default=datetime.datetime.utcnow)),
                            ],
                            options={
                                "verbose_name": "My model",
                                "verbose_name_plural": "My models",
                            },
                        ),
                        migrations.DeleteModel(
                            name="MyModel",
                        ),
                        migrations.AddField(
                            model_name="OtherModel",
                            name="datetimefield",
                            field=models.DateTimeField(default=datetime.datetime.utcnow),
                        ),
                    ]
            """

                                   # noqa
                                   )
        self.assertInOutput(expected, output)
예제 #28
0
    def handle_merge(self, loader, conflicts):
        """
        Handles merging together conflicted migrations interactively,
        if it's safe; otherwise, advises on how to fix it.
        """
        if self.interactive:
            questioner = InteractiveMigrationQuestioner()
        else:
            questioner = MigrationQuestioner(defaults={"ask_merge": True})

        for app_label, migration_names in conflicts.items():
            # Grab out the migrations in question, and work out their
            # common ancestor.
            merge_migrations = []
            for migration_name in migration_names:
                migration = loader.get_migration(app_label, migration_name)
                migration.ancestry = [
                    mig for mig in loader.graph.forwards_plan((app_label,
                                                               migration_name))
                    if mig[0] == migration.app_label
                ]
                merge_migrations.append(migration)

            def all_items_equal(seq):
                return all(item == seq[0] for item in seq[1:])

            merge_migrations_generations = zip(*(m.ancestry
                                                 for m in merge_migrations))
            common_ancestor_count = sum(
                1 for common_ancestor_generation in takewhile(
                    all_items_equal, merge_migrations_generations))
            if not common_ancestor_count:
                raise ValueError("Could not find common ancestor of %s" %
                                 migration_names)
            # Now work out the operations along each divergent branch
            for migration in merge_migrations:
                migration.branch = migration.ancestry[common_ancestor_count:]
                migrations_ops = (loader.get_migration(node_app,
                                                       node_name).operations
                                  for node_app, node_name in migration.branch)
                migration.merged_operations = sum(migrations_ops, [])
            # In future, this could use some of the Optimizer code
            # (can_optimize_through) to automatically see if they're
            # mergeable. For now, we always just prompt the user.
            if self.verbosity > 0:
                self.stdout.write(
                    self.style.MIGRATE_HEADING("Merging %s" % app_label))
                for migration in merge_migrations:
                    self.stdout.write(
                        self.style.MIGRATE_LABEL("  Branch %s" %
                                                 migration.name))
                    for operation in migration.merged_operations:
                        self.stdout.write("    - %s" % operation.describe())
            if questioner.ask_merge(app_label):
                # If they still want to merge it, then write out an empty
                # file depending on the migrations needing merging.
                numbers = [
                    MigrationAutodetector.parse_number(migration.name)
                    for migration in merge_migrations
                ]
                try:
                    biggest_number = max(x for x in numbers if x is not None)
                except ValueError:
                    biggest_number = 1
                subclass = type(
                    "Migration",
                    (Migration, ),
                    {
                        "dependencies": [(app_label, migration.name)
                                         for migration in merge_migrations],
                    },
                )
                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, self.include_header)

                if not self.dry_run:
                    # Write the merge migrations file to the disk
                    with open(writer.path, "w", encoding="utf-8") as fh:
                        fh.write(writer.as_string())
                    if self.verbosity > 0:
                        self.stdout.write("\nCreated new merge migration %s" %
                                          writer.path)
                elif self.verbosity == 3:
                    # Alternatively, makemigrations --merge --dry-run --verbosity 3
                    # will output the merge migrations to stdout rather than saving
                    # the file to the disk.
                    self.stdout.write(
                        self.style.MIGRATE_HEADING(
                            "Full merge migrations file '%s':" %
                            writer.filename))
                    self.stdout.write(writer.as_string())
예제 #29
0
    def handle(self, *app_labels, **options):

        self.verbosity = int(options.get('verbosity'))
        self.interactive = options.get('interactive')

        # Make sure the app they asked for exists
        app_labels = set(app_labels)
        bad_app_labels = set()
        for app_label in app_labels:
            try:
                cache.get_app(app_label)
            except ImproperlyConfigured:
                bad_app_labels.add(app_label)
        if bad_app_labels:
            for app_label in bad_app_labels:
                self.stderr.write("App '%s' could not be found. Is it in INSTALLED_APPS?" % app_label)
            sys.exit(2)

        # Load the current graph state. Takes a connection, but it's not used
        # (makemigrations doesn't look at the database state).
        loader = MigrationLoader(connections[DEFAULT_DB_ALIAS])

        # Detect changes
        autodetector = MigrationAutodetector(
            loader.graph.project_state(),
            ProjectState.from_app_cache(cache),
            InteractiveMigrationQuestioner(specified_apps=app_labels),
        )
        changes = autodetector.changes(graph=loader.graph, trim_to_apps=app_labels or None)

        # No changes? Tell them.
        if not changes and self.verbosity >= 1:
            if len(app_labels) == 1:
                self.stdout.write("No changes detected in app '%s'" % app_labels.pop())
            elif len(app_labels) > 1:
                self.stdout.write("No changes detected in apps '%s'" % ("', '".join(app_labels)))
            else:
                self.stdout.write("No changes detected")
            return

        directory_created = {}
        for app_label, migrations in changes.items():
            if self.verbosity >= 1:
                self.stdout.write(self.style.MIGRATE_HEADING("Migrations for '%s':" % app_label) + "\n")
            for migration in migrations:
                # Describe the migration
                writer = MigrationWriter(migration)
                if self.verbosity >= 1:
                    self.stdout.write("  %s:\n" % (self.style.MIGRATE_LABEL(writer.filename),))
                    for operation in migration.operations:
                        self.stdout.write("    - %s\n" % operation.describe())
                # Write it
                migrations_directory = os.path.dirname(writer.path)
                if not directory_created.get(app_label, False):
                    if not os.path.isdir(migrations_directory):
                        os.mkdir(migrations_directory)
                    init_path = os.path.join(migrations_directory, "__init__.py")
                    if not os.path.isfile(init_path):
                        open(init_path, "w").close()
                    # We just do this once per app
                    directory_created[app_label] = True
                migration_string = writer.as_string()
                with open(writer.path, "wb") as fh:
                    fh.write(migration_string)
예제 #30
0
    def handle(self, **options):

        self.verbosity = options['verbosity']
        self.interactive = options['interactive']
        app_label = options['app_label']
        start_migration_name = options['start_migration_name']
        migration_name = options['migration_name']
        no_optimize = options['no_optimize']

        # Load the current graph state, check the app and migration they asked for exists
        loader = MigrationLoader(connections[DEFAULT_DB_ALIAS])
        if app_label not in loader.migrated_apps:
            raise CommandError(
                "App '%s' does not have migrations (so squashmigrations on "
                "it makes no sense)" % app_label
            )

        migration = self.find_migration(loader, app_label, migration_name)

        # Work out the list of predecessor migrations
        migrations_to_squash = [
            loader.get_migration(al, mn)
            for al, mn in loader.graph.forwards_plan((migration.app_label, migration.name))
            if al == migration.app_label
        ]

        if start_migration_name:
            start_migration = self.find_migration(loader, app_label, start_migration_name)
            start = loader.get_migration(start_migration.app_label, start_migration.name)
            try:
                start_index = migrations_to_squash.index(start)
                migrations_to_squash = migrations_to_squash[start_index:]
            except ValueError:
                raise CommandError(
                    "The migration '%s' cannot be found. Maybe it comes after "
                    "the migration '%s'?\n"
                    "Have a look at:\n"
                    "  python manage.py showmigrations %s\n"
                    "to debug this issue." % (start_migration, migration, app_label)
                )

        # Tell them what we're doing and optionally ask if we should proceed
        if self.verbosity > 0 or self.interactive:
            self.stdout.write(self.style.MIGRATE_HEADING("Will squash the following migrations:"))
            for migration in migrations_to_squash:
                self.stdout.write(" - %s" % migration.name)

            if self.interactive:
                answer = None
                while not answer or answer not in "yn":
                    answer = six.moves.input("Do you wish to proceed? [yN] ")
                    if not answer:
                        answer = "n"
                        break
                    else:
                        answer = answer[0].lower()
                if answer != "y":
                    return

        # Load the operations from all those migrations and concat together,
        # along with collecting external dependencies and detecting
        # double-squashing
        operations = []
        dependencies = set()
        # We need to take all dependencies from the first migration in the list
        # as it may be 0002 depending on 0001
        first_migration = True
        for smigration in migrations_to_squash:
            if smigration.replaces:
                raise CommandError(
                    "You cannot squash squashed migrations! Please transition "
                    "it to a normal migration first: "
                    "https://docs.djangoproject.com/en/%s/topics/migrations/#squashing-migrations" % get_docs_version()
                )
            operations.extend(smigration.operations)
            for dependency in smigration.dependencies:
                if isinstance(dependency, SwappableTuple):
                    if settings.AUTH_USER_MODEL == dependency.setting:
                        dependencies.add(("__setting__", "AUTH_USER_MODEL"))
                    else:
                        dependencies.add(dependency)
                elif dependency[0] != smigration.app_label or first_migration:
                    dependencies.add(dependency)
            first_migration = False

        if no_optimize:
            if self.verbosity > 0:
                self.stdout.write(self.style.MIGRATE_HEADING("(Skipping optimization.)"))
            new_operations = operations
        else:
            if self.verbosity > 0:
                self.stdout.write(self.style.MIGRATE_HEADING("Optimizing..."))

            optimizer = MigrationOptimizer()
            new_operations = optimizer.optimize(operations, migration.app_label)

            if self.verbosity > 0:
                if len(new_operations) == len(operations):
                    self.stdout.write("  No optimizations possible.")
                else:
                    self.stdout.write(
                        "  Optimized from %s operations to %s operations." %
                        (len(operations), len(new_operations))
                    )

        # Work out the value of replaces (any squashed ones we're re-squashing)
        # need to feed their replaces into ours
        replaces = []
        for migration in migrations_to_squash:
            if migration.replaces:
                replaces.extend(migration.replaces)
            else:
                replaces.append((migration.app_label, migration.name))

        # Make a new migration with those operations
        subclass = type("Migration", (migrations.Migration, ), {
            "dependencies": dependencies,
            "operations": new_operations,
            "replaces": replaces,
        })
        if start_migration_name:
            new_migration = subclass("%s_squashed_%s" % (start_migration.name, migration.name), app_label)
        else:
            new_migration = subclass("0001_squashed_%s" % migration.name, app_label)
            new_migration.initial = True

        # Write out the new migration file
        writer = MigrationWriter(new_migration)
        with open(writer.path, "wb") as fh:
            fh.write(writer.as_string())

        if self.verbosity > 0:
            self.stdout.write(self.style.MIGRATE_HEADING("Created new squashed migration %s" % writer.path))
            self.stdout.write("  You should commit this migration but leave the old ones in place;")
            self.stdout.write("  the new migration will be used for new installs. Once you are sure")
            self.stdout.write("  all instances of the codebase have applied the migrations you squashed,")
            self.stdout.write("  you can delete them.")
            if writer.needs_manual_porting:
                self.stdout.write(self.style.MIGRATE_HEADING("Manual porting required"))
                self.stdout.write("  Your migrations contained functions that must be manually copied over,")
                self.stdout.write("  as we could not safely copy their implementation.")
                self.stdout.write("  See the comment at the top of the squashed migration for details.")
예제 #31
0
    def handle(self, app_label=None, migration_name=None, **options):

        self.verbosity = int(options.get('verbosity'))
        self.interactive = options.get('interactive')

        if app_label is None or migration_name is None:
            self.stderr.write(self.usage_str)
            sys.exit(1)

        # Load the current graph state, check the app and migration they asked for exists
        executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
        if app_label not in executor.loader.migrated_apps:
            raise CommandError(
                "App '%s' does not have migrations (so squashmigrations on it makes no sense)"
                % app_label)
        try:
            migration = executor.loader.get_migration_by_prefix(
                app_label, migration_name)
        except AmbiguityError:
            raise CommandError(
                "More than one migration matches '%s' in app '%s'. Please be more specific."
                % (migration_name, app_label))
        except KeyError:
            raise CommandError(
                "Cannot find a migration matching '%s' from app '%s'." %
                (migration_name, app_label))

        # Work out the list of predecessor migrations
        migrations_to_squash = [
            executor.loader.get_migration(al, mn)
            for al, mn in executor.loader.graph.forwards_plan((
                migration.app_label, migration.name))
            if al == migration.app_label
        ]

        # Tell them what we're doing and optionally ask if we should proceed
        if self.verbosity > 0 or self.interactive:
            self.stdout.write(
                self.style.MIGRATE_HEADING(
                    "Will squash the following migrations:"))
            for migration in migrations_to_squash:
                self.stdout.write(" - %s" % migration.name)

            if self.interactive:
                answer = None
                while not answer or answer not in "yn":
                    answer = six.moves.input("Do you wish to proceed? [yN] ")
                    if not answer:
                        answer = "n"
                        break
                    else:
                        answer = answer[0].lower()
                if answer != "y":
                    return

        # Load the operations from all those migrations and concat together
        operations = []
        for smigration in migrations_to_squash:
            operations.extend(smigration.operations)

        if self.verbosity > 0:
            self.stdout.write(self.style.MIGRATE_HEADING("Optimizing..."))

        optimizer = MigrationOptimizer()
        new_operations = optimizer.optimize(operations, migration.app_label)

        if self.verbosity > 0:
            if len(new_operations) == len(operations):
                self.stdout.write("  No optimizations possible.")
            else:
                self.stdout.write(
                    "  Optimized from %s operations to %s operations." %
                    (len(operations), len(new_operations)))

        # Work out the value of replaces (any squashed ones we're re-squashing)
        # need to feed their replaces into ours
        replaces = []
        for migration in migrations_to_squash:
            if migration.replaces:
                replaces.extend(migration.replaces)
            else:
                replaces.append((migration.app_label, migration.name))

        # Make a new migration with those operations
        subclass = type(
            "Migration", (migrations.Migration, ), {
                "dependencies": [],
                "operations": new_operations,
                "replaces": replaces,
            })
        new_migration = subclass("0001_squashed_%s" % migration.name,
                                 app_label)

        # Write out the new migration file
        writer = MigrationWriter(new_migration)
        with open(writer.path, "wb") as fh:
            fh.write(writer.as_string())

        if self.verbosity > 0:
            self.stdout.write(
                self.style.MIGRATE_HEADING(
                    "Created new squashed migration %s" % writer.path))
            self.stdout.write(
                "  You should commit this migration but leave the old ones in place;"
            )
            self.stdout.write(
                "  the new migration will be used for new installs. Once you are sure"
            )
            self.stdout.write(
                "  all instances of the codebase have applied the migrations you squashed,"
            )
            self.stdout.write("  you can delete them.")
예제 #32
0
    def handle(self, *app_labels, **options):

        self.verbosity = int(options.get('verbosity'))
        self.interactive = options.get('interactive')
        self.dry_run = options.get('dry_run', False)
        self.merge = options.get('merge', False)

        # Make sure the app they asked for exists
        app_labels = set(app_labels)
        bad_app_labels = set()
        for app_label in app_labels:
            try:
                apps.get_app_config(app_label)
            except LookupError:
                bad_app_labels.add(app_label)
        if bad_app_labels:
            for app_label in bad_app_labels:
                self.stderr.write("App '%s' could not be found. Is it in INSTALLED_APPS?" % app_label)
            sys.exit(2)

        # Load the current graph state. Takes a connection, but it's not used
        # (makemigrations doesn't look at the database state).
        # Also make sure the graph is built without unmigrated apps shoehorned in.
        loader = MigrationLoader(connections[DEFAULT_DB_ALIAS])
        loader.build_graph(ignore_unmigrated=True)

        # Before anything else, see if there's conflicting apps and drop out
        # hard if there are any and they don't want to merge
        conflicts = loader.detect_conflicts()
        if conflicts and not self.merge:
            name_str = "; ".join(
                "%s in %s" % (", ".join(names), app)
                for app, names in conflicts.items()
            )
            raise CommandError("Conflicting migrations detected (%s).\nTo fix them run 'python manage.py makemigrations --merge'" % name_str)

        # If they want to merge and there's nothing to merge, then politely exit
        if self.merge and not conflicts:
            self.stdout.write("No conflicts detected to merge.")
            return

        # If they want to merge and there is something to merge, then
        # divert into the merge code
        if self.merge and conflicts:
            return self.handle_merge(loader, conflicts)

        # Detect changes
        autodetector = MigrationAutodetector(
            loader.graph.project_state(),
            ProjectState.from_apps(apps),
            InteractiveMigrationQuestioner(specified_apps=app_labels),
        )
        changes = autodetector.changes(graph=loader.graph, trim_to_apps=app_labels or None)

        # No changes? Tell them.
        if not changes and self.verbosity >= 1:
            if len(app_labels) == 1:
                self.stdout.write("No changes detected in app '%s'" % app_labels.pop())
            elif len(app_labels) > 1:
                self.stdout.write("No changes detected in apps '%s'" % ("', '".join(app_labels)))
            else:
                self.stdout.write("No changes detected")
            return

        directory_created = {}
        for app_label, app_migrations in changes.items():
            if self.verbosity >= 1:
                self.stdout.write(self.style.MIGRATE_HEADING("Migrations for '%s':" % app_label) + "\n")
            for migration in app_migrations:
                # Describe the migration
                writer = MigrationWriter(migration)
                if self.verbosity >= 1:
                    self.stdout.write("  %s:\n" % (self.style.MIGRATE_LABEL(writer.filename),))
                    for operation in migration.operations:
                        self.stdout.write("    - %s\n" % operation.describe())
                # Write it
                if not self.dry_run:
                    migrations_directory = os.path.dirname(writer.path)
                    if not directory_created.get(app_label, False):
                        if not os.path.isdir(migrations_directory):
                            os.mkdir(migrations_directory)
                        init_path = os.path.join(migrations_directory, "__init__.py")
                        if not os.path.isfile(init_path):
                            open(init_path, "w").close()
                        # We just do this once per app
                        directory_created[app_label] = True
                    migration_string = writer.as_string()
                    with open(writer.path, "wb") as fh:
                        fh.write(migration_string)
    def handle(self, *args, **options):
        self.verbosity = options.get('verbosity')
        self.no_optimize = options.get('no_optimize')
        migrations_dir = options.get('output_dir')

        try:
            default_output_dir = os.path.join(
                settings.BASE_DIR, DEFAULT_PENDING_MIGRATIONS_DIRECTORY)
        except AttributeError:
            default_output_dir = None

        if migrations_dir is None:
            if not default_output_dir:
                raise CommandError(
                    "No output directory to collect migrations to. Either set "
                    "BASE_DIR in your settings or provide a directory path "
                    "via the --output-dir option.")
            else:
                migrations_dir = default_output_dir
        elif not migrations_dir:
            raise CommandError(
                "Provide a real directory path via the --output-dir option.")

        db = options.get('database')
        connection = connections[db]

        MIGRATE_HEADING = self.style.MIGRATE_HEADING
        MIGRATE_LABEL = self.style.MIGRATE_LABEL

        if self.verbosity > 0:
            apps_with_models = []

            for app_config in apps.get_app_configs():
                if app_config.models_module is not None:
                    apps_with_models.append(app_config.label)

            app_list = ", ".join(sorted(apps_with_models))

            self.stdout.write(MIGRATE_HEADING("Operations to perform:"))
            self.stdout.write(
                MIGRATE_LABEL("  Collect all migrations: ") + app_list
            )

        loader = ProjectMigrationLoader(connection, ignore_no_migrations=True)
        app_migrations = defaultdict(list)

        # Check for conflicts
        conflicts = loader.detect_conflicts()
        if conflicts:
            name_str = "; ".join(
                "%s in %s" % (", ".join(names), app)
                for app, names in conflicts.items()
            )
            raise CommandError(
                "Conflicting migrations detected (%s).\nTo fix them run "
                "'python manage.py makemigrations --merge'" % name_str
            )

        # Only collect migrations that haven't been applied
        # NOTE: It's very important to keep the migrations sorted here,
        #       otherwise they may get out of order and the optimizer goes
        #       all out of whack because it's not good at out of order items
        for migration_key, migration in sorted(loader.disk_migrations.items()):
            if migration_key not in loader.applied_migrations:
                app_label, migration_name = migration_key
                app_migrations[app_label].append(migration)

        def list_of_lists():
            result_list = []
            result_list.append([])

            return result_list

        leaf_nodes = loader.graph.leaf_nodes()
        new_app_migrations = defaultdict(list_of_lists)
        new_app_leaf_migrations = {}

        # NOTE: Grabbed from Django mainline and modified
        def _find_common_ancestors(nodes):
            # Grab out the migrations in question, and work out their
            # common ancestor.
            migrations = []
            for migration_key in nodes:
                migration = loader.get_migration(*migration_key)
                forwards_plan = loader.graph.forwards_plan(migration_key)
                applied = loader.applied_migrations

                def valid_node(node):
                    return node not in applied and node not in leaf_nodes

                ancestry = [node for node in forwards_plan if valid_node(node)]
                migration.ancestry = ancestry
                migrations.append(migration)
            items_equal = lambda seq: all(item == seq[0] for item in seq[1:])
            migrations_gens = zip(*[m.ancestry for m in migrations])
            common_ancestors = takewhile(items_equal, migrations_gens)

            return list(common_ancestors)

        def walk_nodes(current_app, migration_key):
            if migration_key not in loader.applied_migrations:
                app_label, migration_name = migration_key
                key_result = loader.check_key(migration_key, current_app)
                migration_key = key_result or migration_key
                migration = loader.get_migration(*migration_key)

                if app_label == current_app:
                    if migration not in new_app_migrations[app_label][0]:
                        new_app_migrations[app_label][0].append(migration)
                for dep_key in migration.dependencies:
                    walk_nodes(app_label, dep_key)

        for migration_key in loader.graph.leaf_nodes():
            app_label, migration_name = migration_key
            new_app_leaf_migrations[app_label] = (app_label, migration_name)
            walk_nodes(app_label, migration_key)

        migrating_apps = []

        def find_common_ancestors(node1, node2):
            dependent_apps_1 = set()
            dependent_apps_2 = set()

            migration1 = loader.get_migration(node1[0], node1[1])
            migration2 = loader.get_migration(node2[0], node2[1])

            def walk_dependencies(migration, app_set):
                migration_key = (migration.app_label, migration.name)
                forwards_plan = loader.graph.forwards_plan(migration_key)

                for dependency in list(forwards_plan)[:-1]:
                    app_label, migration_name = dependency

                    if app_label not in migrating_apps:
                        continue
                    else:
                        app_set.add(app_label)

                    dep_migration = loader.get_migration(*dependency)
                    walk_dependencies(dep_migration, app_set)

            walk_dependencies(migration1, dependent_apps_1)
            walk_dependencies(migration2, dependent_apps_2)

            if dependent_apps_1.intersection(dependent_apps_2):
                return _find_common_ancestors([node1, node2])
            else:  # pragma: no cover
                return []

        for migration_key in leaf_nodes:
            app_label, migration_name = migration_key

            if migration_key not in loader.applied_migrations:
                migrating_apps.append(app_label)

        app_pairs = combinations(migrating_apps, 2)

        for app_pair in app_pairs:
            migration1 = new_app_leaf_migrations[app_pair[0]]
            migration2 = new_app_leaf_migrations[app_pair[1]]
            common_ancestors = find_common_ancestors(migration1, migration2)

            if common_ancestors:
                most_recently_common = common_ancestors[-1][0]

                app_label, migration_name = most_recently_common
                migration = loader.get_migration(app_label, migration_name)

                # Check if we need to split out migrations to prevent cycles
                for migrations in new_app_migrations[app_label]:  # pragma: nb
                    if migration in migrations:  # pragma: no branch
                        idx = migrations.index(migration)
                        split_migration = migrations[idx:]
                        new_app_migrations[app_label].insert(
                            0, split_migration)

                        for migration in split_migration:
                            migrations.remove(migration)

                        break

        app_migrations = new_app_migrations

        if self.verbosity > 0:
            self.stdout.write(MIGRATE_HEADING("Collecting migrations:"))

            for app in sorted(apps_with_models):
                write = self.stdout.write

                if app in app_migrations:
                    write("  Migrations collected for app '%s'" % app)
                else:
                    write("  No unapplied migrations for app '%s'" % app)

        # No migrations to bundle up, so return early
        if not app_migrations:
            return

        try:
            # Delete the output dir to avoid a combination of new and old files
            if os.path.exists(migrations_dir):
                shutil.rmtree(migrations_dir)

            os.mkdir(migrations_dir)

            project_migrations = defaultdict(list)

            # Create migrations for each individual app
            for app_label in app_migrations:
                migration_sets = app_migrations[app_label]
                for idx, migration_set in enumerate(migration_sets):
                    index_name = self._make_name(idx)
                    project_migrations[app_label].append(
                        self.create_app_migration(
                            app_label, index_name, migration_set
                        )
                    )

            # Resolve dependencies between the consolidated migrations and save
            for app_label, migrations in project_migrations.items():
                for migration_idx, migration in enumerate(migrations):
                    for dependency in copy(migration.dependencies):
                        dep_app = dependency[0]

                        # If there is a project level migration for the dep app
                        if dep_app in project_migrations:
                            other = None
                            migration_key = (dep_app, dependency[1])
                            result = loader.check_key(migration_key, app_label)
                            migration_key = result or migration_key
                            dep_mig = loader.get_migration(*migration_key)
                            dep_migs = app_migrations[dep_app]

                            for idx, migration_set in enumerate(dep_migs):
                                if dep_mig in migration_set:
                                    other_migs = project_migrations[dep_app]
                                    other = other_migs[idx]
                                    break

                            if other is None:
                                continue
                            elif other == migration:  # pragma: no cover
                                raise CircularDependencyError(
                                    "Migration has self for "
                                    "dependency: %s" % (migration,)
                                )

                            # And the dependency is a replaced migration
                            if dependency in other.replaces:
                                migration.dependencies.remove(dependency)
                                index = self._make_name(idx)
                                migration.dependencies.append(
                                    (dep_app, index + '_project'))

                    # Write the migration to disk
                    index = self._make_name(migration_idx)
                    filename = app_label + '_' + index + '_project.py'
                    file_path = os.path.join(migrations_dir, filename)
                    writer = MigrationWriter(migration)

                    with open(file_path, 'wb') as output_file:
                        output = writer.as_string()
                        output_file.write(output)  # pragma: no branch
        except:
            # Delete the output dir to avoid a combination of new and old files
            if os.path.exists(migrations_dir):
                shutil.rmtree(migrations_dir)

            raise
예제 #34
0
    def test_only_black_without_config(self):
        writer = MigrationWriter(self.get_migration())
        writer._isort_installed = False
        with mock.patch("black.find_pyproject_toml", return_value=None):
            output = writer.as_string()
        expected = textwrap.dedent("""
                import datetime
                from django.db import migrations, models


                class Migration(migrations.Migration):

                    dependencies = [
                        ("testapp", "some_other_one"),
                    ]

                    operations = [
                        migrations.CreateModel(
                            name="MyModel",
                            fields=[
                                ("charfield", models.DateTimeField(default=datetime.datetime.utcnow)),
                                (
                                    "datetimefield",
                                    models.DateTimeField(default=datetime.datetime.utcnow),
                                ),
                            ],
                            options={
                                "verbose_name": "My model",
                                "verbose_name_plural": "My models",
                            },
                        ),
                        migrations.CreateModel(
                            name="MyModel2",
                            fields=[
                                ("charfield", models.DateTimeField(default=datetime.datetime.utcnow)),
                                (
                                    "datetimefield",
                                    models.DateTimeField(default=datetime.datetime.utcnow),
                                ),
                            ],
                        ),
                        migrations.CreateModel(
                            name="MyModel3",
                            fields=[
                                ("charfield", models.DateTimeField(default=datetime.datetime.utcnow)),
                                (
                                    "datetimefield",
                                    models.DateTimeField(default=datetime.datetime.utcnow),
                                ),
                            ],
                            options={
                                "verbose_name": "My model",
                                "verbose_name_plural": "My models",
                            },
                        ),
                        migrations.DeleteModel(
                            name="MyModel",
                        ),
                        migrations.AddField(
                            model_name="OtherModel",
                            name="datetimefield",
                            field=models.DateTimeField(default=datetime.datetime.utcnow),
                        ),
                    ]
            """

                                   # noqa
                                   )
        self.assertInOutput(expected, output)
예제 #35
0
    def test_black_and_isort(self):
        writer = MigrationWriter(self.get_migration())
        output = writer.as_string()
        expected = textwrap.dedent("""
                import datetime

                from django.db import migrations, models


                class Migration(migrations.Migration):

                    dependencies = [
                        ("testapp", "some_other_one"),
                    ]

                    operations = [
                        migrations.CreateModel(
                            name="MyModel",
                            fields=[
                                ("charfield", models.DateTimeField(default=datetime.datetime.utcnow)),
                                (
                                    "datetimefield",
                                    models.DateTimeField(default=datetime.datetime.utcnow),
                                ),
                            ],
                            options={
                                "verbose_name": "My model",
                                "verbose_name_plural": "My models",
                            },
                        ),
                        migrations.CreateModel(
                            name="MyModel2",
                            fields=[
                                ("charfield", models.DateTimeField(default=datetime.datetime.utcnow)),
                                (
                                    "datetimefield",
                                    models.DateTimeField(default=datetime.datetime.utcnow),
                                ),
                            ],
                        ),
                        migrations.CreateModel(
                            name="MyModel3",
                            fields=[
                                ("charfield", models.DateTimeField(default=datetime.datetime.utcnow)),
                                (
                                    "datetimefield",
                                    models.DateTimeField(default=datetime.datetime.utcnow),
                                ),
                            ],
                            options={
                                "verbose_name": "My model",
                                "verbose_name_plural": "My models",
                            },
                        ),
                        migrations.DeleteModel(
                            name="MyModel",
                        ),
                        migrations.AddField(
                            model_name="OtherModel",
                            name="datetimefield",
                            field=models.DateTimeField(default=datetime.datetime.utcnow),
                        ),
                    ]
            """

                                   # noqa
                                   )
        self.assertInOutput(expected, output)
예제 #36
0
    def handle(self, *app_labels, **options):

        self.verbosity = int(options.get('verbosity'))
        self.interactive = options.get('interactive')

        # Make sure the app they asked for exists
        app_labels = set(app_labels)
        bad_app_labels = set()
        for app_label in app_labels:
            try:
                cache.get_app(app_label)
            except ImproperlyConfigured:
                bad_app_labels.add(app_label)
        if bad_app_labels:
            for app_label in bad_app_labels:
                self.stderr.write(
                    "App '%s' could not be found. Is it in INSTALLED_APPS?" %
                    app_label)
            sys.exit(2)

        # Load the current graph state. Takes a connection, but it's not used
        # (makemigrations doesn't look at the database state).
        loader = MigrationLoader(connections[DEFAULT_DB_ALIAS])

        # Detect changes
        autodetector = MigrationAutodetector(
            loader.graph.project_state(),
            ProjectState.from_app_cache(cache),
            InteractiveMigrationQuestioner(specified_apps=app_labels),
        )
        changes = autodetector.changes(graph=loader.graph,
                                       trim_to_apps=app_labels or None)

        # No changes? Tell them.
        if not changes and self.verbosity >= 1:
            if len(app_labels) == 1:
                self.stdout.write("No changes detected in app '%s'" %
                                  app_labels.pop())
            elif len(app_labels) > 1:
                self.stdout.write("No changes detected in apps '%s'" %
                                  ("', '".join(app_labels)))
            else:
                self.stdout.write("No changes detected")
            return

        directory_created = {}
        for app_label, migrations in changes.items():
            if self.verbosity >= 1:
                self.stdout.write(
                    self.style.MIGRATE_HEADING("Migrations for '%s':" %
                                               app_label) + "\n")
            for migration in migrations:
                # Describe the migration
                writer = MigrationWriter(migration)
                if self.verbosity >= 1:
                    self.stdout.write(
                        "  %s:\n" %
                        (self.style.MIGRATE_LABEL(writer.filename), ))
                    for operation in migration.operations:
                        self.stdout.write("    - %s\n" % operation.describe())
                # Write it
                migrations_directory = os.path.dirname(writer.path)
                if not directory_created.get(app_label, False):
                    if not os.path.isdir(migrations_directory):
                        os.mkdir(migrations_directory)
                    init_path = os.path.join(migrations_directory,
                                             "__init__.py")
                    if not os.path.isfile(init_path):
                        open(init_path, "w").close()
                    # We just do this once per app
                    directory_created[app_label] = True
                migration_string = writer.as_string()
                with open(writer.path, "wb") as fh:
                    fh.write(migration_string)
예제 #37
0
    def handle_merge(self, loader, conflicts):
        """
        Handles merging together conflicted migrations interactively,
        if it's safe; otherwise, advises on how to fix it.
        """
        if self.interactive:
            questioner = InteractiveMigrationQuestioner()
        else:
            questioner = MigrationQuestioner(defaults={'ask_merge': True})
        for app_label, migration_names in conflicts.items():
            # Grab out the migrations in question, and work out their
            # common ancestor.
            merge_migrations = []
            for migration_name in migration_names:
                migration = loader.get_migration(app_label, migration_name)
                migration.ancestry = loader.graph.forwards_plan((app_label, migration_name))
                merge_migrations.append(migration)
            common_ancestor = None
            for level in zip(*[m.ancestry for m in merge_migrations]):
                if reduce(operator.eq, level):
                    common_ancestor = level[0]
                else:
                    break
            if common_ancestor is None:
                raise ValueError("Could not find common ancestor of %s" % migration_names)
            # Now work out the operations along each divergent branch
            for migration in merge_migrations:
                migration.branch = migration.ancestry[
                    (migration.ancestry.index(common_ancestor) + 1):
                ]
                migration.merged_operations = []
                for node_app, node_name in migration.branch:
                    migration.merged_operations.extend(
                        loader.get_migration(node_app, node_name).operations
                    )
            # In future, this could use some of the Optimizer code
            # (can_optimize_through) to automatically see if they're
            # mergeable. For now, we always just prompt the user.
            if self.verbosity > 0:
                self.stdout.write(self.style.MIGRATE_HEADING("Merging %s" % app_label))
                for migration in merge_migrations:
                    self.stdout.write(self.style.MIGRATE_LABEL("  Branch %s" % migration.name))
                    for operation in migration.merged_operations:
                        self.stdout.write("    - %s\n" % operation.describe())
            if questioner.ask_merge(app_label):
                # If they still want to merge it, then write out an empty
                # file depending on the migrations needing merging.
                numbers = [
                    MigrationAutodetector.parse_number(migration.name)
                    for migration in merge_migrations
                ]
                try:
                    biggest_number = max([x for x in numbers if x is not None])
                except ValueError:
                    biggest_number = 1
                subclass = type("Migration", (Migration, ), {
                    "dependencies": [(app_label, migration.name) for migration in merge_migrations],
                })
                new_migration = subclass("%04i_merge" % (biggest_number + 1), app_label)
                writer = MigrationWriter(new_migration)

                if not self.dry_run:
                    # Write the merge migrations file to the disk
                    with open(writer.path, "wb") as fh:
                        fh.write(writer.as_string())
                    if self.verbosity > 0:
                        self.stdout.write("\nCreated new merge migration %s" % writer.path)
                elif self.verbosity == 3:
                    # Alternatively, makemigrations --merge --dry-run --verbosity 3
                    # will output the merge migrations to stdout rather than saving
                    # the file to the disk.
                    self.stdout.write(self.style.MIGRATE_HEADING(
                        "Full merge migrations file '%s':" % writer.filename) + "\n"
                    )
                    self.stdout.write("%s\n" % writer.as_string())
예제 #38
0
 def handle_merge(self, loader, conflicts):
     """
     Handles merging together conflicted migrations interactively,
     if it's safe; otherwise, advises on how to fix it.
     """
     if self.interactive:
         questioner = InteractiveMigrationQuestioner()
     else:
         questioner = MigrationQuestioner()
     for app_label, migration_names in conflicts.items():
         # Grab out the migrations in question, and work out their
         # common ancestor.
         merge_migrations = []
         for migration_name in migration_names:
             migration = loader.get_migration(app_label, migration_name)
             migration.ancestry = loader.graph.forwards_plan(
                 (app_label, migration_name))
             merge_migrations.append(migration)
         common_ancestor = None
         for level in zip(*[m.ancestry for m in merge_migrations]):
             if reduce(operator.eq, level):
                 common_ancestor = level[0]
             else:
                 break
         if common_ancestor is None:
             raise ValueError("Could not find common ancestor of %s" %
                              migration_names)
         # Now work out the operations along each divergent branch
         for migration in merge_migrations:
             migration.branch = migration.ancestry[(
                 migration.ancestry.index(common_ancestor) + 1):]
             migration.merged_operations = []
             for node_app, node_name in migration.branch:
                 migration.merged_operations.extend(
                     loader.get_migration(node_app, node_name).operations)
         # In future, this could use some of the Optimizer code
         # (can_optimize_through) to automatically see if they're
         # mergeable. For now, we always just prompt the user.
         if self.verbosity > 0:
             self.stdout.write(
                 self.style.MIGRATE_HEADING("Merging %s" % app_label))
             for migration in merge_migrations:
                 self.stdout.write(
                     self.style.MIGRATE_LABEL("  Branch %s" %
                                              migration.name))
                 for operation in migration.merged_operations:
                     self.stdout.write("    - %s\n" % operation.describe())
         if questioner.ask_merge(app_label):
             # If they still want to merge it, then write out an empty
             # file depending on the migrations needing merging.
             numbers = [
                 MigrationAutodetector.parse_number(migration.name)
                 for migration in merge_migrations
             ]
             try:
                 biggest_number = max([x for x in numbers if x is not None])
             except ValueError:
                 biggest_number = 1
             subclass = type(
                 "Migration", (migrations.Migration, ), {
                     "dependencies": [(app_label, migration.name)
                                      for migration in merge_migrations],
                 })
             new_migration = subclass("%04i_merge" % (biggest_number + 1),
                                      app_label)
             writer = MigrationWriter(new_migration)
             with open(writer.path, "wb") as fh:
                 fh.write(writer.as_string())
             if self.verbosity > 0:
                 self.stdout.write("\nCreated new merge migration %s" %
                                   writer.path)
예제 #39
0
    def handle(self, **options):

        self.verbosity = options.get('verbosity')
        self.interactive = options.get('interactive')
        app_label, migration_name = options['app_label'], options['migration_name']

        # Load the current graph state, check the app and migration they asked for exists
        executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
        if app_label not in executor.loader.migrated_apps:
            raise CommandError("App '%s' does not have migrations (so squashmigrations on it makes no sense)" % app_label)
        try:
            migration = executor.loader.get_migration_by_prefix(app_label, migration_name)
        except AmbiguityError:
            raise CommandError("More than one migration matches '%s' in app '%s'. Please be more specific." % (migration_name, app_label))
        except KeyError:
            raise CommandError("Cannot find a migration matching '%s' from app '%s'." % (migration_name, app_label))

        # Work out the list of predecessor migrations
        migrations_to_squash = [
            executor.loader.get_migration(al, mn)
            for al, mn in executor.loader.graph.forwards_plan((migration.app_label, migration.name))
            if al == migration.app_label
        ]

        # Tell them what we're doing and optionally ask if we should proceed
        if self.verbosity > 0 or self.interactive:
            self.stdout.write(self.style.MIGRATE_HEADING("Will squash the following migrations:"))
            for migration in migrations_to_squash:
                self.stdout.write(" - %s" % migration.name)

            if self.interactive:
                answer = None
                while not answer or answer not in "yn":
                    answer = six.moves.input("Do you wish to proceed? [yN] ")
                    if not answer:
                        answer = "n"
                        break
                    else:
                        answer = answer[0].lower()
                if answer != "y":
                    return

        # Load the operations from all those migrations and concat together
        operations = []
        for smigration in migrations_to_squash:
            operations.extend(smigration.operations)

        if self.verbosity > 0:
            self.stdout.write(self.style.MIGRATE_HEADING("Optimizing..."))

        optimizer = MigrationOptimizer()
        new_operations = optimizer.optimize(operations, migration.app_label)

        if self.verbosity > 0:
            if len(new_operations) == len(operations):
                self.stdout.write("  No optimizations possible.")
            else:
                self.stdout.write("  Optimized from %s operations to %s operations." % (len(operations), len(new_operations)))

        # Work out the value of replaces (any squashed ones we're re-squashing)
        # need to feed their replaces into ours
        replaces = []
        for migration in migrations_to_squash:
            if migration.replaces:
                replaces.extend(migration.replaces)
            else:
                replaces.append((migration.app_label, migration.name))

        # Make a new migration with those operations
        subclass = type("Migration", (migrations.Migration, ), {
            "dependencies": [],
            "operations": new_operations,
            "replaces": replaces,
        })
        new_migration = subclass("0001_squashed_%s" % migration.name, app_label)

        # Write out the new migration file
        writer = MigrationWriter(new_migration)
        with open(writer.path, "wb") as fh:
            fh.write(writer.as_string())

        if self.verbosity > 0:
            self.stdout.write(self.style.MIGRATE_HEADING("Created new squashed migration %s" % writer.path))
            self.stdout.write("  You should commit this migration but leave the old ones in place;")
            self.stdout.write("  the new migration will be used for new installs. Once you are sure")
            self.stdout.write("  all instances of the codebase have applied the migrations you squashed,")
            self.stdout.write("  you can delete them.")
예제 #40
0
 def write_migration(self, migration):
     writer = MigrationWriter(migration)
     os.makedirs(os.path.dirname(writer.path), exist_ok=True)
     with open(writer.path, 'w') as fp:
         fp.write(writer.as_string())
 def write_migration(self, migration):
     writer = MigrationWriter(migration)
     os.makedirs(os.path.dirname(writer.path), exist_ok=True)
     with open(writer.path, 'w') as fp:
         fp.write(writer.as_string())
import os
예제 #43
0
    def handle(self, **options):

        self.verbosity = options.get('verbosity')
        self.interactive = options.get('interactive')
        app_label, migration_name = options['app_label'], options[
            'migration_name']

        # Load the current graph state, check the app and migration they asked for exists
        executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
        if app_label not in executor.loader.migrated_apps:
            raise CommandError(
                "App '%s' does not have migrations (so squashmigrations on it makes no sense)"
                % app_label)
        try:
            migration = executor.loader.get_migration_by_prefix(
                app_label, migration_name)
        except AmbiguityError:
            raise CommandError(
                "More than one migration matches '%s' in app '%s'. Please be more specific."
                % (migration_name, app_label))
        except KeyError:
            raise CommandError(
                "Cannot find a migration matching '%s' from app '%s'." %
                (migration_name, app_label))

        # Work out the list of predecessor migrations
        migrations_to_squash = [
            executor.loader.get_migration(al, mn)
            for al, mn in executor.loader.graph.forwards_plan((
                migration.app_label, migration.name))
            if al == migration.app_label
        ]

        # Tell them what we're doing and optionally ask if we should proceed
        if self.verbosity > 0 or self.interactive:
            self.stdout.write(
                self.style.MIGRATE_HEADING(
                    "Will squash the following migrations:"))
            for migration in migrations_to_squash:
                self.stdout.write(" - %s" % migration.name)

            if self.interactive:
                answer = None
                while not answer or answer not in "yn":
                    answer = six.moves.input("Do you wish to proceed? [yN] ")
                    if not answer:
                        answer = "n"
                        break
                    else:
                        answer = answer[0].lower()
                if answer != "y":
                    return

        # Load the operations from all those migrations and concat together,
        # along with collecting external dependencies and detecting
        # double-squashing
        operations = []
        dependencies = set()
        for smigration in migrations_to_squash:
            if smigration.replaces:
                raise CommandError(
                    "You cannot squash squashed migrations! Please transition it to a normal migration first: https://docs.djangoproject.com/en/1.7/topics/migrations/#squashing-migrations"
                )
            operations.extend(smigration.operations)
            for dependency in smigration.dependencies:
                if isinstance(dependency, SwappableTuple):
                    if settings.AUTH_USER_MODEL == dependency.setting:
                        dependencies.add(("__setting__", "AUTH_USER_MODEL"))
                    else:
                        dependencies.add(dependency)
                elif dependency[0] != smigration.app_label:
                    dependencies.add(dependency)

        if self.verbosity > 0:
            self.stdout.write(self.style.MIGRATE_HEADING("Optimizing..."))

        optimizer = MigrationOptimizer()
        new_operations = optimizer.optimize(operations, migration.app_label)

        if self.verbosity > 0:
            if len(new_operations) == len(operations):
                self.stdout.write("  No optimizations possible.")
            else:
                self.stdout.write(
                    "  Optimized from %s operations to %s operations." %
                    (len(operations), len(new_operations)))

        # Work out the value of replaces (any squashed ones we're re-squashing)
        # need to feed their replaces into ours
        replaces = []
        for migration in migrations_to_squash:
            if migration.replaces:
                replaces.extend(migration.replaces)
            else:
                replaces.append((migration.app_label, migration.name))

        # Make a new migration with those operations
        subclass = type(
            "Migration", (migrations.Migration, ), {
                "dependencies": dependencies,
                "operations": new_operations,
                "replaces": replaces,
            })
        new_migration = subclass("0001_squashed_%s" % migration.name,
                                 app_label)

        # Write out the new migration file
        writer = MigrationWriter(new_migration)
        with open(writer.path, "wb") as fh:
            fh.write(writer.as_string())

        if self.verbosity > 0:
            self.stdout.write(
                self.style.MIGRATE_HEADING(
                    "Created new squashed migration %s" % writer.path))
            self.stdout.write(
                "  You should commit this migration but leave the old ones in place;"
            )
            self.stdout.write(
                "  the new migration will be used for new installs. Once you are sure"
            )
            self.stdout.write(
                "  all instances of the codebase have applied the migrations you squashed,"
            )
            self.stdout.write("  you can delete them.")
            if writer.needs_manual_porting:
                self.stdout.write(
                    self.style.MIGRATE_HEADING("Manual porting required"))
                self.stdout.write(
                    "  Your migrations contained functions that must be manually copied over,"
                )
                self.stdout.write(
                    "  as we could not safely copy their implementation.")
                self.stdout.write(
                    "  See the comment at the top of the squashed migration for details."
                )
예제 #44
0
    def handle(self, *app_labels, **options):

        self.verbosity = int(options.get('verbosity'))
        self.interactive = options.get('interactive')
        self.dry_run = options.get('dry_run', False)
        self.merge = options.get('merge', False)

        # Make sure the app they asked for exists
        app_labels = set(app_labels)
        bad_app_labels = set()
        for app_label in app_labels:
            try:
                apps.get_app_config(app_label)
            except LookupError:
                bad_app_labels.add(app_label)
        if bad_app_labels:
            for app_label in bad_app_labels:
                self.stderr.write("App '%s' could not be found. Is it in INSTALLED_APPS?" % app_label)
            sys.exit(2)

        # Load the current graph state. Takes a connection, but it's not used
        # (makemigrations doesn't look at the database state).
        loader = MigrationLoader(connections[DEFAULT_DB_ALIAS])

        # Before anything else, see if there's conflicting apps and drop out
        # hard if there are any and they don't want to merge
        conflicts = loader.detect_conflicts()
        if conflicts and not self.merge:
            name_str = "; ".join(
                "%s in %s" % (", ".join(names), app)
                for app, names in conflicts.items()
            )
            raise CommandError("Conflicting migrations detected (%s).\nTo fix them run 'python manage.py makemigrations --merge'" % name_str)

        # If they want to merge and there's nothing to merge, then politely exit
        if self.merge and not conflicts:
            self.stdout.write("No conflicts detected to merge.")
            return

        # If they want to merge and there is something to merge, then
        # divert into the merge code
        if self.merge and conflicts:
            return self.handle_merge(loader, conflicts)

        # Detect changes
        autodetector = MigrationAutodetector(
            loader.graph.project_state(),
            ProjectState.from_apps(apps),
            InteractiveMigrationQuestioner(specified_apps=app_labels),
        )
        changes = autodetector.changes(graph=loader.graph, trim_to_apps=app_labels or None)

        # No changes? Tell them.
        if not changes and self.verbosity >= 1:
            if len(app_labels) == 1:
                self.stdout.write("No changes detected in app '%s'" % app_labels.pop())
            elif len(app_labels) > 1:
                self.stdout.write("No changes detected in apps '%s'" % ("', '".join(app_labels)))
            else:
                self.stdout.write("No changes detected")
            return

        directory_created = {}
        for app_label, app_migrations in changes.items():
            if self.verbosity >= 1:
                self.stdout.write(self.style.MIGRATE_HEADING("Migrations for '%s':" % app_label) + "\n")
            for migration in app_migrations:
                # Describe the migration
                writer = MigrationWriter(migration)
                if self.verbosity >= 1:
                    self.stdout.write("  %s:\n" % (self.style.MIGRATE_LABEL(writer.filename),))
                    for operation in migration.operations:
                        self.stdout.write("    - %s\n" % operation.describe())
                # Write it
                if not self.dry_run:
                    migrations_directory = os.path.dirname(writer.path)
                    if not directory_created.get(app_label, False):
                        if not os.path.isdir(migrations_directory):
                            os.mkdir(migrations_directory)
                        init_path = os.path.join(migrations_directory, "__init__.py")
                        if not os.path.isfile(init_path):
                            open(init_path, "w").close()
                        # We just do this once per app
                        directory_created[app_label] = True
                    migration_string = writer.as_string()
                    with open(writer.path, "wb") as fh:
                        fh.write(migration_string)
예제 #45
0
    def handle(self, **options):

        self.verbosity = options['verbosity']
        self.interactive = options['interactive']
        app_label = options['app_label']
        start_migration_name = options['start_migration_name']
        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)
        except LookupError as err:
            raise CommandError(str(err))
        # Load the current graph state, check the BLOG and migration they asked for exists
        loader = MigrationLoader(connections[DEFAULT_DB_ALIAS])
        if app_label not in loader.migrated_apps:
            raise CommandError(
                "App '%s' does not have migrations (so squashmigrations on "
                "it makes no sense)" % app_label)

        migration = self.find_migration(loader, app_label, migration_name)

        # Work out the list of predecessor migrations
        migrations_to_squash = [
            loader.get_migration(al, mn)
            for al, mn in loader.graph.forwards_plan((migration.app_label,
                                                      migration.name))
            if al == migration.app_label
        ]

        if start_migration_name:
            start_migration = self.find_migration(loader, app_label,
                                                  start_migration_name)
            start = loader.get_migration(start_migration.app_label,
                                         start_migration.name)
            try:
                start_index = migrations_to_squash.index(start)
                migrations_to_squash = migrations_to_squash[start_index:]
            except ValueError:
                raise CommandError(
                    "The migration '%s' cannot be found. Maybe it comes after "
                    "the migration '%s'?\n"
                    "Have a look at:\n"
                    "  python manage.py showmigrations %s\n"
                    "to debug this issue." %
                    (start_migration, migration, app_label))

        # Tell them what we're doing and optionally ask if we should proceed
        if self.verbosity > 0 or self.interactive:
            self.stdout.write(
                self.style.MIGRATE_HEADING(
                    "Will squash the following migrations:"))
            for migration in migrations_to_squash:
                self.stdout.write(" - %s" % migration.name)

            if self.interactive:
                answer = None
                while not answer or answer not in "yn":
                    answer = input("Do you wish to proceed? [yN] ")
                    if not answer:
                        answer = "n"
                        break
                    else:
                        answer = answer[0].lower()
                if answer != "y":
                    return

        # Load the operations from all those migrations and concat together,
        # along with collecting external dependencies and detecting
        # double-squashing
        operations = []
        dependencies = set()
        # We need to take all dependencies from the first migration in the list
        # as it may be 0002 depending on 0001
        first_migration = True
        for smigration in migrations_to_squash:
            if smigration.replaces:
                raise CommandError(
                    "You cannot squash squashed migrations! Please transition "
                    "it to a normal migration first: "
                    "https://docs.djangoproject.com/en/%s/topics/migrations/#squashing-migrations"
                    % get_docs_version())
            operations.extend(smigration.operations)
            for dependency in smigration.dependencies:
                if isinstance(dependency, SwappableTuple):
                    if settings.AUTH_USER_MODEL == dependency.setting:
                        dependencies.add(("__setting__", "AUTH_USER_MODEL"))
                    else:
                        dependencies.add(dependency)
                elif dependency[0] != smigration.app_label or first_migration:
                    dependencies.add(dependency)
            first_migration = False

        if no_optimize:
            if self.verbosity > 0:
                self.stdout.write(
                    self.style.MIGRATE_HEADING("(Skipping optimization.)"))
            new_operations = operations
        else:
            if self.verbosity > 0:
                self.stdout.write(self.style.MIGRATE_HEADING("Optimizing..."))

            optimizer = MigrationOptimizer()
            new_operations = optimizer.optimize(operations,
                                                migration.app_label)

            if self.verbosity > 0:
                if len(new_operations) == len(operations):
                    self.stdout.write("  No optimizations possible.")
                else:
                    self.stdout.write(
                        "  Optimized from %s operations to %s operations." %
                        (len(operations), len(new_operations)))

        # Work out the value of replaces (any squashed ones we're re-squashing)
        # need to feed their replaces into ours
        replaces = []
        for migration in migrations_to_squash:
            if migration.replaces:
                replaces.extend(migration.replaces)
            else:
                replaces.append((migration.app_label, migration.name))

        # Make a new migration with those operations
        subclass = type(
            "Migration", (migrations.Migration, ), {
                "dependencies": dependencies,
                "operations": new_operations,
                "replaces": replaces,
            })
        if start_migration_name:
            if squashed_name:
                # Use the name from --squashed-name.
                prefix, _ = start_migration.name.split('_', 1)
                name = '%s_%s' % (prefix, squashed_name)
            else:
                # Generate a name.
                name = '%s_squashed_%s' % (start_migration.name,
                                           migration.name)
            new_migration = subclass(name, app_label)
        else:
            name = '0001_%s' % (squashed_name
                                or 'squashed_%s' % migration.name)
            new_migration = subclass(name, app_label)
            new_migration.initial = True

        # Write out the new migration file
        writer = MigrationWriter(new_migration, include_header)
        with open(writer.path, "w", encoding='utf-8') as fh:
            fh.write(writer.as_string())

        if self.verbosity > 0:
            self.stdout.write(
                self.style.MIGRATE_HEADING(
                    'Created new squashed migration %s' % writer.path) + '\n'
                '  You should commit this migration but leave the old ones in place;\n'
                '  the new migration will be used for new installs. Once you are sure\n'
                '  all instances of the codebase have applied the migrations you squashed,\n'
                '  you can delete them.')
            if writer.needs_manual_porting:
                self.stdout.write(
                    self.style.MIGRATE_HEADING('Manual porting required') +
                    '\n'
                    '  Your migrations contained functions that must be manually copied over,\n'
                    '  as we could not safely copy their implementation.\n'
                    '  See the comment at the top of the squashed migration for details.'
                )
예제 #46
0
 def write_migration_files(self, changes):
     """
     Takes a changes dict and writes them out as migration files.
     """
     directory_created = {}
     for app_label, app_migrations in changes.items():
         if self.verbosity >= 1:
             self.stdout.write(
                 self.style.MIGRATE_HEADING("Migrations for '%s':" %
                                            app_label) + "\n")
         for migration in app_migrations:
             # Describe the migration
             writer = MigrationWriter(migration)
             if self.verbosity >= 1:
                 # Display a relative path if it's below the current working
                 # directory, or an absolute path otherwise.
                 try:
                     migration_string = os.path.relpath(writer.path)
                 except ValueError:
                     migration_string = writer.path
                 if migration_string.startswith('..'):
                     migration_string = writer.path
                 self.stdout.write(
                     "  %s\n" %
                     (self.style.MIGRATE_LABEL(migration_string), ))
                 for operation in migration.operations:
                     self.stdout.write("    - %s\n" % operation.describe())
             if not self.dry_run:
                 # Write the migrations file to the disk.
                 migrations_directory = os.path.dirname(writer.path)
                 if not directory_created.get(app_label):
                     if not os.path.isdir(migrations_directory):
                         os.mkdir(migrations_directory)
                     init_path = os.path.join(migrations_directory,
                                              "__init__.py")
                     if not os.path.isfile(init_path):
                         open(init_path, "w").close()
                     # We just do this once per app
                     directory_created[app_label] = True
                 migration_string = writer.as_string()
                 with io.open(writer.path, "w", encoding='utf-8') as fh:
                     fh.write(migration_string)
                 self.stdout.write(f"adding {writer.path} to git")
                 try:
                     subprocess.run(f"git add {writer.path}",
                                    shell=True,
                                    check=True)
                 except subprocess.CalledProcessError as exc:
                     self.stdout.write(
                         f"Got the following error so you'll want to manually add this: {exc}"
                     )
                 else:
                     self.stdout.write("file added!")
             elif self.verbosity == 3:
                 # Alternatively, makemigrations --dry-run --verbosity 3
                 # will output the migrations to stdout rather than saving
                 # the file to the disk.
                 self.stdout.write(
                     self.style.MIGRATE_HEADING(
                         "Full migrations file '%s':" % writer.filename) +
                     "\n")
                 self.stdout.write("%s\n" % writer.as_string())
예제 #47
0
    def handle_merge(self, loader, conflicts):
        """
        Handles merging together conflicted migrations interactively,
        if it's safe; otherwise, advises on how to fix it.
        """
        if self.interactive:
            questioner = InteractiveMigrationQuestioner()
        else:
            questioner = MigrationQuestioner(defaults={'ask_merge': True})

        for app_label, migration_names in conflicts.items():
            # Grab out the migrations in question, and work out their
            # common ancestor.
            merge_migrations = []
            for migration_name in migration_names:
                migration = loader.get_migration(app_label, migration_name)
                migration.ancestry = [
                    mig for mig in loader.graph.forwards_plan((app_label, migration_name))
                    if mig[0] == migration.app_label
                ]
                merge_migrations.append(migration)

            def all_items_equal(seq):
                return all(item == seq[0] for item in seq[1:])

            merge_migrations_generations = zip(*(m.ancestry for m in merge_migrations))
            common_ancestor_count = sum(1 for common_ancestor_generation
                                        in takewhile(all_items_equal, merge_migrations_generations))
            if not common_ancestor_count:
                raise ValueError("Could not find common ancestor of %s" % migration_names)
            # Now work out the operations along each divergent branch
            for migration in merge_migrations:
                migration.branch = migration.ancestry[common_ancestor_count:]
                migrations_ops = (loader.get_migration(node_app, node_name).operations
                                  for node_app, node_name in migration.branch)
                migration.merged_operations = sum(migrations_ops, [])
            # In future, this could use some of the Optimizer code
            # (can_optimize_through) to automatically see if they're
            # mergeable. For now, we always just prompt the user.
            if self.verbosity > 0:
                self.stdout.write(self.style.MIGRATE_HEADING("Merging %s" % app_label))
                for migration in merge_migrations:
                    self.stdout.write(self.style.MIGRATE_LABEL("  Branch %s" % migration.name))
                    for operation in migration.merged_operations:
                        self.stdout.write("    - %s\n" % operation.describe())
            if questioner.ask_merge(app_label):
                # If they still want to merge it, then write out an empty
                # file depending on the migrations needing merging.
                numbers = [
                    MigrationAutodetector.parse_number(migration.name)
                    for migration in merge_migrations
                ]
                try:
                    biggest_number = max(x for x in numbers if x is not None)
                except ValueError:
                    biggest_number = 1
                subclass = type("Migration", (Migration,), {
                    "dependencies": [(app_label, migration.name) for migration in merge_migrations],
                })
                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:
                    # Write the merge migrations file to the disk
                    with open(writer.path, "w", encoding='utf-8') as fh:
                        fh.write(writer.as_string())
                    if self.verbosity > 0:
                        self.stdout.write("\nCreated new merge migration %s" % writer.path)
                elif self.verbosity == 3:
                    # Alternatively, makemigrations --merge --dry-run --verbosity 3
                    # will output the merge migrations to stdout rather than saving
                    # the file to the disk.
                    self.stdout.write(self.style.MIGRATE_HEADING(
                        "Full merge migrations file '%s':" % writer.filename) + "\n"
                    )
                    self.stdout.write("%s\n" % writer.as_string())
예제 #48
0
 def write_migration_files(self, changes, update_previous_migration_paths=None):
     """
     Take a changes dict and write them out as migration files.
     """
     directory_created = {}
     for app_label, app_migrations in changes.items():
         if self.verbosity >= 1:
             self.log(self.style.MIGRATE_HEADING("Migrations for '%s':" % app_label))
         for migration in app_migrations:
             # Describe the 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.
                 migration_string = self.get_relative_path(writer.path)
                 self.log("  %s\n" % self.style.MIGRATE_LABEL(migration_string))
                 for operation in migration.operations:
                     self.log("    - %s" % operation.describe())
                 if self.scriptable:
                     self.stdout.write(migration_string)
             if not self.dry_run:
                 # Write the migrations file to the disk.
                 migrations_directory = os.path.dirname(writer.path)
                 if not directory_created.get(app_label):
                     os.makedirs(migrations_directory, exist_ok=True)
                     init_path = os.path.join(migrations_directory, "__init__.py")
                     if not os.path.isfile(init_path):
                         open(init_path, "w").close()
                     # We just do this once per app
                     directory_created[app_label] = True
                 migration_string = writer.as_string()
                 with open(writer.path, "w", encoding="utf-8") as fh:
                     fh.write(migration_string)
                     self.written_files.append(writer.path)
                 if update_previous_migration_paths:
                     prev_path = update_previous_migration_paths[app_label]
                     rel_prev_path = self.get_relative_path(prev_path)
                     if writer.needs_manual_porting:
                         migration_path = self.get_relative_path(writer.path)
                         self.log(
                             self.style.WARNING(
                                 f"Updated migration {migration_path} requires "
                                 f"manual porting.\n"
                                 f"Previous migration {rel_prev_path} was kept and "
                                 f"must be deleted after porting functions manually."
                             )
                         )
                     else:
                         os.remove(prev_path)
                         self.log(f"Deleted {rel_prev_path}")
             elif self.verbosity == 3:
                 # Alternatively, makemigrations --dry-run --verbosity 3
                 # will log the migrations rather than saving the file to
                 # the disk.
                 self.log(
                     self.style.MIGRATE_HEADING(
                         "Full migrations file '%s':" % writer.filename
                     )
                 )
                 self.log(writer.as_string())
     run_formatters(self.written_files)
    def handle(self, app_label=None, migration_name=None, no_optimize=None, **options):

        self.verbosity = int(options.get('verbosity'))
        self.interactive = options.get('interactive')

        if app_label is None or migration_name is None:
            self.stderr.write(self.usage_str)
            sys.exit(1)

        # Load the current graph state, check the app and migration they asked for exists
        executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
        if app_label not in executor.loader.migrated_apps:
            raise CommandError("App '%s' does not have migrations (so squashmigrations on it makes no sense)" % app_label)
        try:
            migration = executor.loader.get_migration_by_prefix(app_label, migration_name)
        except AmbiguityError:
            raise CommandError("More than one migration matches '%s' in app '%s'. Please be more specific." % (migration_name, app_label))
        except KeyError:
            raise CommandError("Cannot find a migration matching '%s' from app '%s'." % (migration_name, app_label))

        # Work out the list of predecessor migrations
        migrations_to_squash = [
            executor.loader.get_migration(al, mn)
            for al, mn in executor.loader.graph.forwards_plan((migration.app_label, migration.name))
            if al == migration.app_label
        ]

        # Tell them what we're doing and optionally ask if we should proceed
        if self.verbosity > 0 or self.interactive:
            self.stdout.write(self.style.MIGRATE_HEADING("Will squash the following migrations:"))
            for migration in migrations_to_squash:
                self.stdout.write(" - %s" % migration.name)

            if self.interactive:
                answer = None
                while not answer or answer not in "yn":
                    answer = six.moves.input("Do you wish to proceed? [yN] ")
                    if not answer:
                        answer = "n"
                        break
                    else:
                        answer = answer[0].lower()
                if answer != "y":
                    return

        # Load the operations from all those migrations and concat together,
        # along with collecting external dependencies and detecting
        # double-squashing
        operations = []
        dependencies = set()
        for smigration in migrations_to_squash:
            if smigration.replaces:
                raise CommandError("You cannot squash squashed migrations! Please transition it to a normal migration first: https://docs.djangoproject.com/en/1.7/topics/migrations/#squashing-migrations")
            operations.extend(smigration.operations)
            for dependency in smigration.dependencies:
                if isinstance(dependency, SwappableTuple):
                    if settings.AUTH_USER_MODEL == dependency.setting:
                        dependencies.add(("__setting__", "AUTH_USER_MODEL"))
                    else:
                        dependencies.add(dependency)
                elif dependency[0] != smigration.app_label:
                    dependencies.add(dependency)

        if no_optimize:
            if self.verbosity > 0:
                self.stdout.write(self.style.MIGRATE_HEADING("(Skipping optimization.)"))
            new_operations = operations
        else:
            if self.verbosity > 0:
                self.stdout.write(self.style.MIGRATE_HEADING("Optimizing..."))

            optimizer = MigrationOptimizer()
            new_operations = optimizer.optimize(operations, migration.app_label)

            if self.verbosity > 0:
                if len(new_operations) == len(operations):
                    self.stdout.write("  No optimizations possible.")
                else:
                    self.stdout.write(
                        "  Optimized from %s operations to %s operations." %
                        (len(operations), len(new_operations))
                    )

        # Work out the value of replaces (any squashed ones we're re-squashing)
        # need to feed their replaces into ours
        replaces = []
        for migration in migrations_to_squash:
            if migration.replaces:
                replaces.extend(migration.replaces)
            else:
                replaces.append((migration.app_label, migration.name))

        # Make a new migration with those operations
        subclass = type("Migration", (migrations.Migration, ), {
            "dependencies": dependencies,
            "operations": new_operations,
            "replaces": replaces,
        })
        new_migration = subclass("0001_squashed_%s" % migration.name, app_label)

        # Write out the new migration file
        writer = MigrationWriter(new_migration)
        with open(writer.path, "wb") as fh:
            fh.write(writer.as_string())

        if self.verbosity > 0:
            self.stdout.write(self.style.MIGRATE_HEADING("Created new squashed migration %s" % writer.path))
            self.stdout.write("  You should commit this migration but leave the old ones in place;")
            self.stdout.write("  the new migration will be used for new installs. Once you are sure")
            self.stdout.write("  all instances of the codebase have applied the migrations you squashed,")
            self.stdout.write("  you can delete them.")
            if writer.needs_manual_porting:
                self.stdout.write(self.style.MIGRATE_HEADING("Manual porting required"))
                self.stdout.write("  Your migrations contained functions that must be manually copied over,")
                self.stdout.write("  as we could not safely copy their implementation.")
                self.stdout.write("  See the comment at the top of the squashed migration for details.")
예제 #50
0
    def handle(self, *args, **options):
        verbosity = options["verbosity"]
        app_label = options["app_label"]
        migration_name = options["migration_name"]
        check = options["check"]

        # Validate app_label.
        try:
            apps.get_app_config(app_label)
        except LookupError as err:
            raise CommandError(str(err))

        # Load the current graph state.
        loader = MigrationLoader(None)
        if app_label not in loader.migrated_apps:
            raise CommandError(f"App '{app_label}' does not have migrations.")
        # Find a migration.
        try:
            migration = loader.get_migration_by_prefix(app_label, migration_name)
        except AmbiguityError:
            raise CommandError(
                f"More than one migration matches '{migration_name}' in app "
                f"'{app_label}'. Please be more specific."
            )
        except KeyError:
            raise CommandError(
                f"Cannot find a migration matching '{migration_name}' from app "
                f"'{app_label}'."
            )

        # Optimize the migration.
        optimizer = MigrationOptimizer()
        new_operations = optimizer.optimize(migration.operations, migration.app_label)
        if len(migration.operations) == len(new_operations):
            if verbosity > 0:
                self.stdout.write("No optimizations possible.")
            return
        else:
            if verbosity > 0:
                self.stdout.write(
                    "Optimizing from %d operations to %d operations."
                    % (len(migration.operations), len(new_operations))
                )
            if check:
                sys.exit(1)

        # Set the new migration optimizations.
        migration.operations = new_operations

        # Write out the optimized migration file.
        writer = MigrationWriter(migration)
        migration_file_string = writer.as_string()
        if writer.needs_manual_porting:
            if migration.replaces:
                raise CommandError(
                    "Migration will require manual porting but is already a squashed "
                    "migration.\nTransition to a normal migration first: "
                    "https://docs.djangoproject.com/en/%s/topics/migrations/"
                    "#squashing-migrations" % get_docs_version()
                )
            # Make a new migration with those operations.
            subclass = type(
                "Migration",
                (migrations.Migration,),
                {
                    "dependencies": migration.dependencies,
                    "operations": new_operations,
                    "replaces": [(migration.app_label, migration.name)],
                },
            )
            optimized_migration_name = "%s_optimized" % migration.name
            optimized_migration = subclass(optimized_migration_name, app_label)
            writer = MigrationWriter(optimized_migration)
            migration_file_string = writer.as_string()
            if verbosity > 0:
                self.stdout.write(
                    self.style.MIGRATE_HEADING("Manual porting required") + "\n"
                    "  Your migrations contained functions that must be manually "
                    "copied over,\n"
                    "  as we could not safely copy their implementation.\n"
                    "  See the comment at the top of the optimized migration for "
                    "details."
                )
                if shutil.which("black"):
                    self.stdout.write(
                        self.style.WARNING(
                            "Optimized migration couldn't be formatted using the "
                            '"black" command. You can call it manually.'
                        )
                    )
        with open(writer.path, "w", encoding="utf-8") as fh:
            fh.write(migration_file_string)
        run_formatters([writer.path])

        if verbosity > 0:
            self.stdout.write(
                self.style.MIGRATE_HEADING(f"Optimized migration {writer.path}")
            )