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.