Chapter 21 · Management commands
21.1 Finding the command
How ManagementUtility gets from an argument list to a command object: the two options it reads early, the dictionary that get_commands builds from every management/commands directory, and what ManagementUtility does when the name is unknown or the settings are missing.
A command line names its command with one word, and nothing in a project lists the commands there are. No setting names them and no application registers them. The set is worked out from the file system when a command is asked for: a command exists because a file with its name lies in a directory with the right name, in Django or in an installed application. This page follows that lookup, from the word on the command line to an instance of a class.
Three entry points, one function
A command can be typed three ways, and all three call execute_from_command_line.
manage.pyis a file of the project, written by the startproject command. It sets"DJANGO_SETTINGS_MODULE"to the project’s settings module unless the environment has a value already, and calls the function withsys.argv.django-adminis a script that installing Django creates, declared as an entry point that names the function directly (pyproject.toml).python -m djangoruns Django’s__main__module, which calls it too.
The function makes a ManagementUtility from the argument list and calls its execute. The utility keeps the list, and the base name of its first item as the program’s name for messages; where that is "__main__.py" it says "python -m django" instead.
So the three differ in what surrounds the call. Only manage.py says which settings to use. And they differ in where Python will look for the project’s code: running manage.py puts the project’s directory first on the module search path, because Python does that with the directory of any script it is given, and python -m django gets the current directory there. django-admin gets neither, and has to be told where the project is, by the environment or by --pythonpath.
What execute does before it knows the command
ManagementUtility takes the second item of the argument list as the name of the subcommand, or "help" if there is none. It cannot look the name up at once, because which commands exist depends on the settings.
- It reads
--settingsand--pythonpath. A parser that knows only those two options is run over the rest of the line, ignoring everything it does not recognise and any error.handle_default_optionsthen acts on what was found: a settings module’s name is written to the environment variable, and a directory is put at the front of the module search path. - It tries the settings. It reads
settings, which loads the settings module if one is named. If the read raises.INSTALLED_APPS ImproperlyConfigured, whatever the cause, the exception is kept on the utility andexecutegoes on without settings. That is the exception raised when no settings module is named, when the named module itself is not found, and when a setting that must be a list is not one. An exception of any other class, from a missing package above the module or from the module’s own code, is not caught, and ends every command there with a traceback. - It starts Django, if the read worked, by calling
django.setup. For the runserver command with its reloader the call is made so that a failure does not end the process; Who callsdjango.setuptells that case. - It answers a request for shell completion, if the environment says that is what this run is (below).
- It dispatches on the subcommand. The words help and version, and
--help,-hor--versionstanding alone, are answered by the utility itself. Anything else is taken for a command’s name:ManagementUtilityreturns a command object, and its.fetch_command BaseCommandis called with the whole argument list..run_from_argv
The two options are read here and again later. Every command’s own parser declares --settings and --pythonpath too, so that they are accepted and shown in its help, and BaseCommand passes the parsed options through handle_default_options a second time. The early reading is the one that decides which commands there are.
The dictionary of commands: get_commands
get_commands returns a dictionary from each command’s name to the name of the application that provides it.
It starts with Django’s own commands, the modules of django/, each entered under the name "django.core". If the settings are not configured, that is the whole dictionary. Otherwise it goes through the installed applications in the reverse of their order in settings, and for each one adds the commands found in the application’s own management/commands directory, replacing any entry of the same name.
find_commands does the finding, and it imports nothing. It asks pkgutil for the modules in the directory and keeps the names that are not packages and do not begin with an underscore. A helper module can therefore sit beside the commands, under a name that starts with one.
The order of the loop is a rule of precedence. Because later entries replace earlier ones, and the applications are taken last to first, the application listed first wins, and any application wins over Django. The recorded project installs django and then two applications of its own, trees and press. Here is what each place offers, and which offers are not taken up.
The commands of the project: what find_commands finds in each place, in the order of INSTALLED_APPS
django.core
check, compilemessages, createcachetable, dbshell, diffsettings, dumpdata, flush,
inspectdb, listurls, loaddata, makemessages, makemigrations, migrate, optimizemigration,
runserver (not used: django.contrib.staticfiles has one), sendtestemail, shell,
showmigrations, sqlflush, sqlmigrate, sqlsequencereset, squashmigrations, startapp,
startproject, test, testserver
django.contrib.staticfiles
collectstatic, findstatic, runserver
trees
count, prune
press
count (not used: trees has one), juice
Replacement is by name and by nothing else. The runserver of django is a subclass of Django’s that adds the serving of static files, but that is its author’s choice: the dictionary would point just as well at a class that shared nothing with the one it displaced. A project replaces a command of Django’s, or of another application, by putting a file of the same name in an application listed earlier.
The dictionary is built once in a process, because get_commands is wrapped in functools.cache. The recordings in call_command: a command called from code show it: the directories are listed under the first call in a process and under none after it. In a process whose installed applications change, the test framework’s, a receiver of the setting_changed signal empties the cache whenever "INSTALLED_APPS" is overridden.
From a name to an object
ManagementUtility looks the subcommand up in the dictionary and hands the application’s name to load_command_class. That function imports the command’s module by its dotted path, the application’s name, then management, commands and the command’s own name, and returns an instance of the module’s class Command, made with no arguments. That is the whole of the contract a command module has to meet: the right place, and a class of that name. An error raised by the import is not caught.
The lookup stops in one of two ways, both ending the process with status 1.
The name is not in the dictionary. fetch_command complains on standard error. If difflib finds a known name near the one typed, it is offered. And where the settings were not loaded, the complaint is preceded by the reason the project’s commands may be missing: the exception kept from the attempt, if the environment names a settings module, or else a line saying that no settings were specified.
The command needs settings and there are none. Once the object exists, fetch_command looks at the exception that execute kept. If there is one and the command’s BaseCommand is true, which is the default, the exception’s message is printed. Four of Django’s commands set the attribute to false and run regardless: startproject, startapp, shell and makemessages.
Without any settings, then, a project’s command is unknown, a command of Django’s that needs settings is refused with the reason, and one that does not need them runs.
No settings: python -m django, with DJANGO_SETTINGS_MODULE not set
python -m django count 3
stderr | No Django settings specified.
stderr | Unknown command: 'count'
stderr | Type 'python -m django help' for usage.
exit status 1
python -m django check
stderr | Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.
exit status 1
python -m django shell --no-imports -c print(40+2)
stdout | 42
exit status 0
A settings module that is named and does not exist has the same two outcomes, with its own message. In the third run below nothing but the option names the settings, and the project is importable because python -m django was started in its directory.
The --settings option, for a module that does not exist and for one that does
python manage.py count 3 --settings=orchard.no_such_settings
stderr | No module named 'orchard.no_such_settings'.
stderr | Unknown command: 'count'
stderr | Type 'manage.py help' for usage.
exit status 1
python manage.py check --settings=orchard.no_such_settings
stderr | No module named 'orchard.no_such_settings'.
exit status 1
python -m django count 3 --settings=orchard.settings
stdout | 36 trees in 3 rows
exit status 0
A value in the dictionary may also be a command object in place of an application’s name: fetch_command and call_command both test for one and use it as it is. Django itself stores only names there.
Help, version and completion
Some things a command line can ask for are answered by ManagementUtility without running a command.
Help. With no subcommand, with help alone, or with --help or -h as the only argument, ManagementUtility lists every name in the dictionary under the application it comes from. If the settings could not be loaded, the list ends with a note that says so and gives the error, since the list is then Django’s commands only. Help followed by a name fetches that command, as above, and prints its parser’s help.
The version. The subcommand version, or --version as the only argument, prints what get_version returns.
Completion. ManagementUtility is called on every run, after the step that starts Django, and returns at once unless the environment has "DJANGO_AUTO_COMPLETE". The Bash completion script that comes with Django’s source sets that variable and runs the program again, with the words typed so far in "COMP_WORDS" and the index of the word being completed in "COMP_CWORD". The method prints the candidates for that word and exits: the names of the commands, or the options of the command already named, which it gets by fetching that command and building its parser.