咱们以manage.py做为出发点,来看看它的实现。python
import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv)
manage.py设置环境变量,来指定settings模块。django
它最后调用了execute_from_command_line函数。app
def execute_from_command_line(argv=None): """ A simple method that runs a ManagementUtility. """ utility = ManagementUtility(argv) utility.execute()
execute_from_command_line方法只是经过argv实例化了ManagementUtility。而后调用excute方法。ide
首先看ManagementUtility的__init__方法:函数
class ManagementUtility(object): def __init__(self, argv=None): self.argv = argv or sys.argv[:] self.prog_name = os.path.basename(self.argv[0]) self.settings_exception = None
而后看execute方法:fetch
def execute(self): """ Given the command-line arguments, this figures out which subcommand is being run, creates a parser appropriate to that command, and runs it. """ try: subcommand = self.argv[1] except IndexError: subcommand = 'help' # Display help if no arguments were given. # Preprocess options to extract --settings and --pythonpath. # These options could affect the commands that are available, so they # must be processed early. parser = CommandParser(None, usage="%(prog)s subcommand [options] [args]", add_help=False) parser.add_argument('--settings') parser.add_argument('--pythonpath') parser.add_argument('args', nargs='*') # catch-all try: options, args = parser.parse_known_args(self.argv[2:]) handle_default_options(options) except CommandError: pass # Ignore any option errors at this point. no_settings_commands = [ 'help', 'version', '--help', '--version', '-h', 'compilemessages', 'makemessages', 'startapp', 'startproject', ] try: settings.INSTALLED_APPS except ImproperlyConfigured as exc: self.settings_exception = exc # A handful of built-in management commands work without settings. # Load the default settings -- where INSTALLED_APPS is empty. if subcommand in no_settings_commands: settings.configure() if settings.configured: django.setup() self.autocomplete() if subcommand == 'help': if '--commands' in args: sys.stdout.write(self.main_help_text(commands_only=True) + '\n') elif len(options.args) < 1: sys.stdout.write(self.main_help_text() + '\n') else: self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0]) # Special-cases: We want 'django-admin --version' and # 'django-admin --help' to work, for backwards compatibility. elif subcommand == 'version' or self.argv[1:] == ['--version']: sys.stdout.write(django.get_version() + '\n') elif self.argv[1:] in (['--help'], ['-h']): sys.stdout.write(self.main_help_text() + '\n') else: self.fetch_command(subcommand).run_from_argv(self.argv)
前面一部分主要负责解析和处理settings和pythonpath参数。ui
调用handle_default_options函数,在http://my.oschina.net/u/569730/blog/360017this
由于有些命令是不须要settings设置,因此会去读取默认的设置(global settings)。spa
由于global_settings是没有INSTALLED_APPS这个属性的,因此经过获取这个属性,捕获ImproperlyConfigured异常判断。.net
最后除去 help,version,--version,--help, -h这几个特殊的参数,
会调用fetch_command( )返回相应的command类,调用它的run_from_argv( )方法。
try: subcommand = self.argv[1] except IndexError: subcommand = 'help' # Display help if no arguments were given.
传入的subcommand是argv[ 1 ]。好比:python manage startapp app,那么subcommand就是startapp。
def fetch_command(self, subcommand): """ Tries to fetch the given subcommand, printing a message with the appropriate command called from the command line (usually "django-admin" or "manage.py") if it can't be found. """ # Get commands outside of try block to prevent swallowing exceptions commands = get_commands() try: app_name = commands[subcommand] except KeyError: # This might trigger ImproperlyConfigured (masked in get_commands) settings.INSTALLED_APPS sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % (subcommand, self.prog_name)) sys.exit(1) if isinstance(app_name, BaseCommand): # If the command is already loaded, use it directly. klass = app_name else: klass = load_command_class(app_name, subcommand) return klass
首先会调用get_commands( )方法。
@lru_cache.lru_cache(maxsize=None) def get_commands(): """ Returns a dictionary mapping command names to their callback applications. This works by looking for a management.commands package in django.core, and in each installed application -- if a commands package exists, all commands in that package are registered. Core commands are always included. If a settings module has been specified, user-defined commands will also be included. The dictionary is in the format {command_name: app_name}. Key-value pairs from this dictionary can then be used in calls to load_command_class(app_name, command_name) If a specific version of a command must be loaded (e.g., with the startapp command), the instantiated module can be placed in the dictionary in place of the application name. The dictionary is cached on the first call and reused on subsequent calls. """ commands = {name: 'django.core' for name in find_commands(__path__[0])} if not settings.configured: return commands for app_config in reversed(list(apps.get_app_configs())): path = os.path.join(app_config.path, 'management') commands.update({name: app_config.name for name in find_commands(path)}) return commands
这里get_commands( )返回的结果是一个字典, { 命令: 对应app的路径或者是‘django.core'。}
def find_commands(management_dir): """ Given a path to a management directory, returns a list of all the command names that are available. Returns an empty list if no commands are defined. """ command_dir = os.path.join(management_dir, 'commands') try: return [f[:-3] for f in os.listdir(command_dir) if not f.startswith('_') and f.endswith('.py')] except OSError: return []
find_commands查找management_dir目录下的python文件名。
fetch_commands首先寻找django.core目录下, 而后分别找INSTALLED_APPS的下面的management目录。、
而后返回命令对应的模块,判断是否实例化,若是没有调用load_command_class()方法实例化。
def load_command_class(app_name, name): """ Given a command name and an application name, returns the Command class instance. All errors raised by the import process (ImportError, AttributeError) are allowed to propagate. """ module = import_module('%s.management.commands.%s' % (app_name, name)) return module.Command()
load_command_class会在相应的模块里,import对应的命令所在的模块。而后实例化Command类。
因此自定义的command,应该在app的目录下,新建/management/commands/name.py
name就是自定义命令的名称。而后在name.py里,新建类Command,继承BaseCommand。
实现相应的方法。