diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 9a565a9393..4d26c98d09 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -3,6 +3,7 @@ import os import pkgutil import sys from collections import OrderedDict, defaultdict +from difflib import get_close_matches from importlib import import_module import django @@ -203,10 +204,11 @@ class ManagementUtility: settings.INSTALLED_APPS else: sys.stderr.write("No Django settings specified.\n") - sys.stderr.write( - "Unknown command: %r\nType '%s help' for usage.\n" - % (subcommand, self.prog_name) - ) + possible_matches = get_close_matches(subcommand, commands) + sys.stderr.write('Unknown command: %r' % subcommand) + if possible_matches: + sys.stderr.write('. Did you mean %s?' % possible_matches[0]) + sys.stderr.write("\nType '%s help' for usage.\n" % self.prog_name) sys.exit(1) if isinstance(app_name, BaseCommand): # If the command is already loaded, use it directly. diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index e9f5a1897b..d34f95ea40 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -2256,3 +2256,23 @@ class MainModule(AdminScriptTestCase): def test_program_name_in_help(self): out, err = self.run_test('-m', ['django', 'help']) self.assertOutput(out, "Type 'python -m django help ' for help on a specific subcommand.") + + +class DjangoAdminSuggestions(AdminScriptTestCase): + def setUp(self): + self.write_settings('settings.py') + + def tearDown(self): + self.remove_settings('settings.py') + + def test_suggestions(self): + args = ['rnserver', '--settings=test_project.settings'] + out, err = self.run_django_admin(args) + self.assertNoOutput(out) + self.assertOutput(err, "Unknown command: 'rnserver'. Did you mean runserver?") + + def test_no_suggestions(self): + args = ['abcdef', '--settings=test_project.settings'] + out, err = self.run_django_admin(args) + self.assertNoOutput(out) + self.assertNotInOutput(err, 'Did you mean')