I don’t know about you, but when I started with Django, I wondered about the “manage” script and what it did. But there were too many other things to learn, so I accepted that it worked and moved on, using the recipes I found in examples and documentation.
My curiosity has gotten the better of me, so now I’m going to take a break and examine what’s going on under the hood.
Naively, I had assumed that the manage script was some large, static beast with 1000’s of lines of code. (Too much C and make in my background, I guess). Of course, this is python, so manage.py is small, reflective, and quite dynamic.
On a brand new project, manage.py weighs in at a whopping 22 lines of code. Now, that’s not really a fair accounting as all that code is doing is importing a method from a module:
from django.core.management import execute_from_command_line
and then running it:
execute_from_command_line(sys.argv)
Th execute_from_command_line function is located in the __init__.py package in python/site-packages in your virtualenv (you are using virtualenv, right?).
This app does several bookkeeping things (managing command line args, deciding if it needs to do “help”, etc) and then calls the get_commands function. This is a very elegant function (the comment block explaining it is much longer than the code) that creates a { command_name : app_name } dictionary for all of the commands it can find. It starts by adding all of the django core commands by searching the management/commands directory in the core django directory. It then searches the management/commands subdirectory of each installed app.
It took a little digging, but I managed to figure out a bit more detail here which I found interesting. Django has a list called django.apps.apps. These are the apps listed in the INSTALLED_APPS list in your settings.py file. An entry is made for each app loaded with some details about the app. For our purposes here, the data that we care about is the path and the name.
Jumping back to the get_commands function and the __init__.py file in the django.core.management module, you can see at the top of the file that it imports this list of apps:
from django.apps import apps.
This allows the get_commands function to walk the list of apps, getting the name of each app and the path in which it’s stored. It appends the management/commands subdirectory to the path and adds each python file there as a command to the { command_name : app_name }, listed above. (Note that I’m skipping some details here). That’s how it figures it out.
So, to complete the loop, we can see that the apps listed in settings.py
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
show up in the lib/python3.4/site-packages/ directory structure:
django/ ├── core │ ├── management │ │ ├── commands │ │ │ ├── check.py │ │ │ ├── compilemessages.py │ │ │ ├── createcachetable.py │ │ │ └── [several commands removed] │ │ │ ├── runserver.py │ │ │ └── [several commands removed] ├── contrib │ ├── auth │ │ ├── management │ │ │ ├── commands │ │ │ │ ├── changepassword.py │ │ │ │ ├── createsuperuser.py │ ├── contenttypes │ │ ├── management │ │ │ ├── commands │ │ │ │ └── remove_stale_contenttypes.py │ ├── sessions │ │ ├── management │ │ │ ├── commands │ │ │ │ ├── clearsessions.py │ ├── staticfiles │ │ ├── management │ │ │ ├── commands │ │ │ │ ├── collectstatic.py │ │ │ │ ├── findstatic.py │ │ │ │ └── runserver.py
and are represented in the output of ./manage.py –help
Available subcommands: [auth] changepassword createsuperuser [contenttypes] remove_stale_contenttypes [django] check compilemessages createcachetable [several commands removed but NOT runserver] [sessions] clearsessions [staticfiles] collectstatic findstatic runserver
So, when you add django-extensions to your INSTALLED_APPS list, that is how the new commands show up!
Those of you that are really detail-oriented might have noticed that the runserver command is listed in both the django.core app as well as in the staticfiles app. It is only listed once in the command output. It turns out that the way the dictionary of commands is constructed, it uses the command name for the key and the app name for the value. This means that the last one found will be the command listed. This works out well in this case as the staticfiles version of the command add functionality you very likely want (mainly serving static files) on top of the core runserver command. In classic DRY style, the staticfiles version of the command makes use of the core version. It also has the nice effect of providing a runserver command which does NOT serve static files if you do not have the django.contrib.staticfiles app installed. Sweet!
Building your own extensions
If you follow all of that, the documentation that describes adding your own management commands for your app should seem fairly obvious. Basically you just create a management/commands directory in your app and populate it with the commands you wish to add. Note that there are a few other requirements (there needs to be an __init__.py file, the commands must all be subclasses of django.core.management.base.BaseCommand, etc), but the basic mechanism is exactly the same.