Combine multiple `shell` commands across apps using `get_auto_imports`?

Django 5.2 introduced the ability to customize the shell management command by overriding the get_auto_imports() method in a management command subclass (see the release note or this page of the doc).

That's a nice feature and it works well for a single app, but I'm running into trouble trying to make it scale across multiple apps.

For instance, in app1/management/commands/shell.py:

from django.core.management.commands import shell

class Command(shell.Command):
    def get_auto_imports(self) -> list[str]:
        return [
            *super().get_auto_imports(),
            "app1.module1",
        ]

And in app2/management/commands/shell.py:

from django.core.management.commands import shell

class Command(shell.Command):
    def get_auto_imports(self) -> list[str]:
        return [
            *super().get_auto_imports(),
            "app2.module2",
        ]

The issue is that only one of these is picked up by Django — whichever app comes first in INSTALLED_APPS. This seems to be by design, as Django only uses the first matching management command it finds.

Is there a clean way to combine auto imports from multiple apps or extend the shell command across apps without having to manually centralize everything in one location?

I’m looking for a generic and scalable solution that allows each app to contribute its own imports to the shell, ideally still using get_auto_imports() so the logic stays clean and encapsulated per app.

I don't think you need to define multiple shell commands, you can probably let every app say what should be autoimported.

So:

# app_1/apps.py

from django.apps import AppConfig


class App1Config(AppConfig):
    # ...
    shell_auto_imports = ('app1.module1',)

and:

# app_2/apps.py

from django.apps import AppConfig


class App2Config(AppConfig):
    # ...
    shell_auto_imports = ('app2.module2',)

and work with one shell script:

from django.apps import apps
from django.core.management.commands import shell

class Command(shell.Command):
    def get_auto_imports(self) -> list[str]:
        extras = [
            item
            for config in apps.get_app_configs()
            for item in getattr(config, 'shell_auto_imports', ())
        ]
        return [
            *super().get_auto_imports(),
            *extras
        ]

Yep! Using shell_auto_imports as a custom attribute on each app’s AppConfig is clean and elegant.

For anyone looking for an alternative that keeps import declarations separate from AppConfig, I also tested a different approach:

Each app can define a small shell_imports.py module that declares its imports in an AUTO_IMPORTS list, and then a centralized shell command can dynamically collect them.

Example:

In app1/shell_imports.py:

AUTO_IMPORTS = ["app1.models.ModelOne", ]  # import ModelOne class directly
    # or use "app1.models" to import the entire module

In app2/shell_imports.py:

AUTO_IMPORTS = ["app2.models.ModelTwo", ] 

Central shell command (e.g., in core/management/commands/shell.py):

from django.core.management.commands.shell import Command as BaseShellCommand
from django.apps import apps

class Command(BaseShellCommand):
    def get_auto_imports(self):
        imports = super().get_auto_imports()
        for app_config in apps.get_app_configs():
            try:
                module = __import__(f"{app_config.name}.shell_imports", fromlist=["AUTO_IMPORTS"])
                imports.extend(getattr(module, "AUTO_IMPORTS", []))
            except ModuleNotFoundError:
                pass
        
        print("Final combined imports:", imports)

        return imports

This alternative works well if you prefer to keep your import logic outside of the apps.py metadata. Both methods solve the problem effectively — it’s just a matter of organizational style and project preferences.

Вернуться на верх