1
0
mirror of https://github.com/django/django.git synced 2025-03-12 18:30:48 +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 importlib import import_module
from django.apps import apps from django.apps import apps
from django.core.exceptions import AppRegistryNotReady
from django.core.management import BaseCommand, CommandError from django.core.management import BaseCommand, CommandError
from django.utils.datastructures import OrderedSet from django.utils.datastructures import OrderedSet
from django.utils.module_loading import import_string as import_dotted_path 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"): if options and options.get("no_imports"):
return {} 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() path_imports = self.get_auto_imports()
if path_imports is None: if path_imports is None:
return {} return {}
@ -175,7 +192,6 @@ class Command(BaseCommand):
name: obj for items in auto_imports.values() for name, obj in items name: obj for items in auto_imports.values() for name, obj in items
} }
verbosity = options["verbosity"] if options else 0
if verbosity < 1: if verbosity < 1:
return namespace return namespace
@ -228,7 +244,7 @@ class Command(BaseCommand):
def handle(self, **options): def handle(self, **options):
# Execute the command and exit. # Execute the command and exit.
if options["command"]: if options["command"]:
exec(options["command"], {**globals(), **self.get_namespace()}) exec(options["command"], {**globals(), **self.get_namespace(**options)})
return return
# Execute stdin if it has anything to read and exit. # Execute stdin if it has anything to read and exit.
@ -238,7 +254,7 @@ class Command(BaseCommand):
and not sys.stdin.isatty() and not sys.stdin.isatty()
and select.select([sys.stdin], [], [], 0)[0] 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 return
available_shells = ( available_shells = (

View File

@ -1,3 +1,5 @@
import os
import subprocess
import sys import sys
import unittest import unittest
from unittest import mock from unittest import mock
@ -23,25 +25,85 @@ class ShellCommandTestCase(SimpleTestCase):
def test_command_option(self): def test_command_option(self):
with self.assertLogs("test", "INFO") as cm: with self.assertLogs("test", "INFO") as cm:
call_command( with captured_stdout():
"shell", call_command(
command=( "shell",
"import django; from logging import getLogger; " command=(
'getLogger("test").info(django.__version__)' "import django; from logging import getLogger; "
), 'getLogger("test").info(django.__version__)'
) ),
)
self.assertEqual(cm.records[0].getMessage(), __version__) self.assertEqual(cm.records[0].getMessage(), __version__)
def test_command_option_globals(self): def test_command_option_globals(self):
with captured_stdout() as stdout: 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") self.assertEqual(stdout.getvalue().strip(), "True")
def test_command_option_inline_function_call(self): def test_command_option_inline_function_call(self):
with captured_stdout() as stdout: 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__) 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( @unittest.skipIf(
sys.platform == "win32", "Windows select() doesn't support file descriptors." 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: with captured_stdin() as stdin, captured_stdout() as stdout:
stdin.write("print(100)\n") stdin.write("print(100)\n")
stdin.seek(0) stdin.seek(0)
call_command("shell") call_command("shell", verbosity=0)
self.assertEqual(stdout.getvalue().strip(), "100") self.assertEqual(stdout.getvalue().strip(), "100")
@unittest.skipIf( @unittest.skipIf(
@ -62,7 +124,7 @@ class ShellCommandTestCase(SimpleTestCase):
with captured_stdin() as stdin, captured_stdout() as stdout: with captured_stdin() as stdin, captured_stdout() as stdout:
stdin.write(self.script_globals) stdin.write(self.script_globals)
stdin.seek(0) stdin.seek(0)
call_command("shell") call_command("shell", verbosity=0)
self.assertEqual(stdout.getvalue().strip(), "True") self.assertEqual(stdout.getvalue().strip(), "True")
@unittest.skipIf( @unittest.skipIf(
@ -74,7 +136,7 @@ class ShellCommandTestCase(SimpleTestCase):
with captured_stdin() as stdin, captured_stdout() as stdout: with captured_stdin() as stdin, captured_stdout() as stdout:
stdin.write(self.script_with_inline_function) stdin.write(self.script_with_inline_function)
stdin.seek(0) stdin.seek(0)
call_command("shell") call_command("shell", verbosity=0)
self.assertEqual(stdout.getvalue().strip(), __version__) self.assertEqual(stdout.getvalue().strip(), __version__)
def test_ipython(self): def test_ipython(self):