diff options
Diffstat (limited to 'django/core/management/commands/shell.py')
| -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 = ( |
