diff options
| author | Salvo Polizzi <101474753+salvo-polizzi@users.noreply.github.com> | 2025-01-09 17:00:29 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-01-09 13:00:29 -0300 |
| commit | fc28550fe4e0582952993976edc62971bd5345a8 (patch) | |
| tree | caf9428df753d1266dec97cffb3d4571cceacab7 /django/core | |
| parent | 8c118c0e00846091c261b97dbed9a5b89ceb79bf (diff) | |
Fixed #35515 -- Added automatic model imports to shell management command.
Thanks to Bhuvnesh Sharma and Adam Johnson for mentoring this Google
Summer of Code 2024 project. Thanks to Sarah Boyce, David Smith, Jacob
Walls and Natalia Bidart for reviews.
Diffstat (limited to 'django/core')
| -rw-r--r-- | django/core/management/commands/shell.py | 84 |
1 files changed, 79 insertions, 5 deletions
diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py index f55b346406..1619561fea 100644 --- a/django/core/management/commands/shell.py +++ b/django/core/management/commands/shell.py @@ -2,7 +2,9 @@ import os import select import sys import traceback +from collections import defaultdict +from django.apps import apps from django.core.management import BaseCommand, CommandError from django.utils.datastructures import OrderedSet @@ -27,6 +29,11 @@ class Command(BaseCommand): ), ) parser.add_argument( + "--no-imports", + action="store_true", + help="Disable automatic imports of models.", + ) + parser.add_argument( "-i", "--interface", choices=self.shells, @@ -47,18 +54,27 @@ class Command(BaseCommand): def ipython(self, options): from IPython import start_ipython - start_ipython(argv=[]) + start_ipython( + argv=[], + user_ns=self.get_and_report_namespace( + options["verbosity"], options["no_imports"] + ), + ) def bpython(self, options): import bpython - bpython.embed() + bpython.embed( + self.get_and_report_namespace(options["verbosity"], options["no_imports"]) + ) def python(self, options): import code # Set up a dictionary to serve as the environment for the shell. - imported_objects = {} + imported_objects = self.get_and_report_namespace( + options["verbosity"], options["no_imports"] + ) # We want to honor both $PYTHONSTARTUP and .pythonrc.py, so follow system # conventions and get $PYTHONSTARTUP first then .pythonrc.py. @@ -111,10 +127,68 @@ class Command(BaseCommand): # Start the interactive interpreter. code.interact(local=imported_objects) + def get_and_report_namespace(self, verbosity, no_imports=False): + if no_imports: + return {} + + namespace = self.get_namespace() + + if verbosity < 1: + return namespace + + amount = len(namespace) + msg = f"{amount} objects imported automatically" + + if verbosity < 2: + self.stdout.write(f"{msg} (use -v 2 for details).", self.style.SUCCESS) + return namespace + + imports_by_module = defaultdict(list) + for obj_name, obj in namespace.items(): + if hasattr(obj, "__module__") and ( + (hasattr(obj, "__qualname__") and obj.__qualname__.find(".") == -1) + or not hasattr(obj, "__qualname__") + ): + imports_by_module[obj.__module__].append(obj_name) + if not hasattr(obj, "__module__") and hasattr(obj, "__name__"): + tokens = obj.__name__.split(".") + if obj_name in tokens: + module = ".".join(t for t in tokens if t != obj_name) + imports_by_module[module].append(obj_name) + + import_string = "\n".join( + [ + f" from {module} import {objects}" + for module, imported_objects in imports_by_module.items() + if (objects := ", ".join(imported_objects)) + ] + ) + + try: + import isort + except ImportError: + pass + else: + import_string = isort.code(import_string) + + self.stdout.write( + f"{msg}, including:\n\n{import_string}", self.style.SUCCESS, ending="\n\n" + ) + + return namespace + + def get_namespace(self): + apps_models = apps.get_models() + namespace = {} + for model in reversed(apps_models): + if model.__module__: + namespace[model.__name__] = model + return namespace + def handle(self, **options): # Execute the command and exit. if options["command"]: - exec(options["command"], globals()) + exec(options["command"], {**globals(), **self.get_namespace()}) return # Execute stdin if it has anything to read and exit. @@ -124,7 +198,7 @@ class Command(BaseCommand): and not sys.stdin.isatty() and select.select([sys.stdin], [], [], 0)[0] ): - exec(sys.stdin.read(), globals()) + exec(sys.stdin.read(), {**globals(), **self.get_namespace()}) return available_shells = ( |
