1
0
mirror of https://github.com/django/django.git synced 2025-03-12 10:22:37 +00:00

[5.2.x] Fixed #36224 -- Fixed shell imports when settings not configured.

Thank you Raffaella for the report. Thank you Tim Schilling and Natalia Bidart
for the reviews.

Backport of de1117ea8eabe0ee0aa048e5a4e249eab7c4245e from main.
This commit is contained in:
Sarah Boyce 2025-03-03 17:19:24 +01:00
parent 2bfec6c84b
commit 4c7b737b30
2 changed files with 93 additions and 15 deletions

View File

@ -6,6 +6,7 @@ from collections import defaultdict
from importlib import import_module
from django.apps import apps
from django.core.exceptions import AppRegistryNotReady
from django.core.management import BaseCommand, CommandError
from django.utils.datastructures import OrderedSet
from django.utils.module_loading import import_string as import_dotted_path
@ -150,6 +151,22 @@ class Command(BaseCommand):
if options and options.get("no_imports"):
return {}
verbosity = options["verbosity"] if options else 0
try:
apps.check_models_ready()
except AppRegistryNotReady:
if verbosity > 0:
settings_env_var = os.getenv("DJANGO_SETTINGS_MODULE")
self.stdout.write(
"Automatic imports are disabled since settings are not configured."
f"\nDJANGO_SETTINGS_MODULE value is {settings_env_var!r}.\n"
"HINT: Ensure that the settings module is configured and set.",
self.style.ERROR,
ending="\n\n",
)
return {}
path_imports = self.get_auto_imports()
if path_imports is None:
return {}
@ -175,7 +192,6 @@ class Command(BaseCommand):
name: obj for items in auto_imports.values() for name, obj in items
}
verbosity = options["verbosity"] if options else 0
if verbosity < 1:
return namespace
@ -228,7 +244,7 @@ class Command(BaseCommand):
def handle(self, **options):
# Execute the command and exit.
if options["command"]:
exec(options["command"], {**globals(), **self.get_namespace()})
exec(options["command"], {**globals(), **self.get_namespace(**options)})
return
# Execute stdin if it has anything to read and exit.
@ -238,7 +254,7 @@ class Command(BaseCommand):
and not sys.stdin.isatty()
and select.select([sys.stdin], [], [], 0)[0]
):
exec(sys.stdin.read(), {**globals(), **self.get_namespace()})
exec(sys.stdin.read(), {**globals(), **self.get_namespace(**options)})
return
available_shells = (

View File

@ -1,3 +1,5 @@
import os
import subprocess
import sys
import unittest
from unittest import mock
@ -23,25 +25,85 @@ class ShellCommandTestCase(SimpleTestCase):
def test_command_option(self):
with self.assertLogs("test", "INFO") as cm:
call_command(
"shell",
command=(
"import django; from logging import getLogger; "
'getLogger("test").info(django.__version__)'
),
)
with captured_stdout():
call_command(
"shell",
command=(
"import django; from logging import getLogger; "
'getLogger("test").info(django.__version__)'
),
)
self.assertEqual(cm.records[0].getMessage(), __version__)
def test_command_option_globals(self):
with captured_stdout() as stdout:
call_command("shell", command=self.script_globals)
call_command("shell", command=self.script_globals, verbosity=0)
self.assertEqual(stdout.getvalue().strip(), "True")
def test_command_option_inline_function_call(self):
with captured_stdout() as stdout:
call_command("shell", command=self.script_with_inline_function)
call_command("shell", command=self.script_with_inline_function, verbosity=0)
self.assertEqual(stdout.getvalue().strip(), __version__)
@override_settings(INSTALLED_APPS=["shell"])
def test_no_settings(self):
test_environ = os.environ.copy()
if "DJANGO_SETTINGS_MODULE" in test_environ:
del test_environ["DJANGO_SETTINGS_MODULE"]
error = (
"Automatic imports are disabled since settings are not configured.\n"
"DJANGO_SETTINGS_MODULE value is None.\n"
"HINT: Ensure that the settings module is configured and set.\n\n"
)
for verbosity, assertError in [
("0", self.assertNotIn),
("1", self.assertIn),
("2", self.assertIn),
]:
with self.subTest(verbosity=verbosity, get_auto_imports="models"):
p = subprocess.run(
[
sys.executable,
"-m",
"django",
"shell",
"-c",
"print(globals())",
"-v",
verbosity,
],
capture_output=True,
env=test_environ,
text=True,
umask=-1,
)
assertError(error, p.stdout)
self.assertNotIn("Marker", p.stdout)
with self.subTest(verbosity=verbosity, get_auto_imports="without-models"):
with mock.patch(
"django.core.management.commands.shell.Command.get_auto_imports",
return_value=["django.urls.resolve"],
):
p = subprocess.run(
[
sys.executable,
"-m",
"django",
"shell",
"-c",
"print(globals())",
"-v",
verbosity,
],
capture_output=True,
env=test_environ,
text=True,
umask=-1,
)
assertError(error, p.stdout)
self.assertNotIn("resolve", p.stdout)
@unittest.skipIf(
sys.platform == "win32", "Windows select() doesn't support file descriptors."
)
@ -50,7 +112,7 @@ class ShellCommandTestCase(SimpleTestCase):
with captured_stdin() as stdin, captured_stdout() as stdout:
stdin.write("print(100)\n")
stdin.seek(0)
call_command("shell")
call_command("shell", verbosity=0)
self.assertEqual(stdout.getvalue().strip(), "100")
@unittest.skipIf(
@ -62,7 +124,7 @@ class ShellCommandTestCase(SimpleTestCase):
with captured_stdin() as stdin, captured_stdout() as stdout:
stdin.write(self.script_globals)
stdin.seek(0)
call_command("shell")
call_command("shell", verbosity=0)
self.assertEqual(stdout.getvalue().strip(), "True")
@unittest.skipIf(
@ -74,7 +136,7 @@ class ShellCommandTestCase(SimpleTestCase):
with captured_stdin() as stdin, captured_stdout() as stdout:
stdin.write(self.script_with_inline_function)
stdin.seek(0)
call_command("shell")
call_command("shell", verbosity=0)
self.assertEqual(stdout.getvalue().strip(), __version__)
def test_ipython(self):