Skip to content

Allow migrate command subclasses to customize the migration executor #164

Description

@moaddib666

Code of Conduct

  • I agree to follow Django's Code of Conduct

Feature Description

Add an executor_class attribute to the migrate management command, defaulting to MigrationExecutor, so subclasses can customize migration execution without overriding the full command implementation.

This would follow the existing migration command pattern used by attributes such as autodetector.

Problem

The migrate command currently instantiates MigrationExecutor directly inside its implementation.

This makes customization difficult for projects or third-party packages that need a custom migration executor.

Today, the practical workaround is to subclass the migrate command and copy large parts of handle() just to replace the executor construction line.

That approach is fragile because the copied logic can drift from Django upstream over time. It also makes small customizations unnecessarily expensive to maintain.

This feature would provide a small, explicit extension point while preserving the current default behavior.

Request or proposal

proposal

Additional Details

The proposed change is backwards compatible.

Existing behavior would remain unchanged because executor_class would default to MigrationExecutor.

The intended usage would be:

from django.core.management.commands.migrate import Command as MigrateCommand
from .executors import CustomMigrationExecutor

class Command(MigrateCommand):
    executor_class = CustomMigrationExecutor

This is useful for projects that need to customize migration execution behavior while still relying on Django’s standard migrate command flow.

The change also aligns with the existing composition-style pattern used in Django’s migration commands, where collaborators can be exposed as class attributes rather than hard-coded directly inside command methods.

Implementation Suggestions

Add a class-level attribute to django.core.management.commands.migrate.Command:

class Command(BaseCommand):
    executor_class = MigrationExecutor

Then replace direct MigrationExecutor construction with self.executor_class.

For example:

executor = self.executor_class(connection, self.migration_progress_callback)

For consistency, BaseCommand.check_migrations() could also resolve the executor class from the command instance when available:

executor_class = getattr(self, "executor_class", MigrationExecutor)
executor = executor_class(connections[DEFAULT_DB_ALIAS])

Tests should cover:

  • migrate.Command.executor_class defaults to MigrationExecutor
  • migrate.Command.handle() uses self.executor_class
  • BaseCommand.check_migrations() respects a custom executor_class
  • existing behavior remains unchanged when executor_class is not overridden

Documentation should mention that subclasses of the migrate command may override executor_class to customize migration execution behavior.

POC:
django/django#21358

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    Idea

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions