1. ``django-admin`` or ``manage.py`` loads the command class and calls its ``run_from_argv()`` method. 2. The ``run_from_argv()`` method calls ``create_parser()`` to get an ``ArgumentParser`` for the arguments, parses them, performs any environment changes requested by options like ``pythonpath``, and then calls the ``execute()`` method, passing the parsed arguments. 3. The ``execute()`` method attempts to carry out the command by calling the ``handle()`` method with the parsed arguments; any output produced by ``handle()`` will be printed to standard output and, if the command is intended to produce a block of SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``. 4. If ``handle()`` or ``execute()`` raised any exception (e.g. ``CommandError``), ``run_from_argv()`` will instead print an error message to ``stderr``.
這是命令執行的流程。python
首先import相應的command類,而後調用run_from_argv( ), 傳入sys.argv看成參數。sql
而後調用create_parser( )方法,返回parser。django
解析參數,而且將解析後的參數傳入execute( ) 方法。app
執行handle( ),將返回的結果輸出。ide
第一步會在之後講,如今主要看後面處理的流程代碼,是由BaseCommand實現。ui
由於BaseCommand類的實現代碼比較多,就挑選相應的方法解析。this
def run_from_argv(self, argv): """ Set up any environment changes requested (e.g., Python path and Django settings), then run this command. If the command raises a ``CommandError``, intercept it and print it sensibly to stderr. If the ``--traceback`` option is present or the raised ``Exception`` is not ``CommandError``, raise it. """ self._called_from_command_line = True parser = self.create_parser(argv[0], argv[1]) if self.use_argparse: options = parser.parse_args(argv[2:]) cmd_options = vars(options) # Move positional args out of options to mimic legacy optparse args = cmd_options.pop('args', ()) else: options, args = parser.parse_args(argv[2:]) cmd_options = vars(options) handle_default_options(options) try: self.execute(*args, **cmd_options) except Exception as e: if options.traceback or not isinstance(e, CommandError): raise # SystemCheckError takes care of its own formatting. if isinstance(e, SystemCheckError): self.stderr.write(str(e), lambda x: x) else: self.stderr.write('%s: %s' % (e.__class__.__name__, e)) sys.exit(1)
run_from_argv( )首先調用parser = self.create_parser(argv[0], argv[1])方法。spa
得到parser後,經過判斷self.use_argparse,來肯定返回的類型。由於目前有兩種解析參數的方式,一種是optparse,將在django2.0後廢棄。另外一種是argparser。這是爲了向後保持兼容性。命令行
argv參數格式極爲sys.argv,執行命令時的參數。舉例:python manage runserver 0.0.0.0:8000
code
那麼argv = ['manage', 'runserver', '0.0.0.0:8000']。
如今看create_parser方法的具體代碼。
def create_parser(self, prog_name, subcommand): """ Create and return the ``ArgumentParser`` which will be used to parse the arguments to this command. """ if not self.use_argparse: # Backwards compatibility: use deprecated optparse module warnings.warn("OptionParser usage for Django management commands " "is deprecated, use ArgumentParser instead", RemovedInDjango20Warning) parser = OptionParser(prog=prog_name, usage=self.usage(subcommand), version=self.get_version()) parser.add_option('-v', '--verbosity', action='store', dest='verbosity', default='1', type='choice', choices=['0', '1', '2', '3'], help='Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output') parser.add_option('--settings', help=( 'The Python path to a settings module, e.g. ' '"myproject.settings.main". If this isn\'t provided, the ' 'DJANGO_SETTINGS_MODULE environment variable will be used.' ), ) parser.add_option('--pythonpath', help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".'), parser.add_option('--traceback', action='store_true', help='Raise on CommandError exceptions') parser.add_option('--no-color', action='store_true', dest='no_color', default=False, help="Don't colorize the command output.") for opt in self.option_list: parser.add_option(opt) else: parser = CommandParser(self, prog="%s %s" % (os.path.basename(prog_name), subcommand), description=self.help or None) parser.add_argument('--version', action='version', version=self.get_version()) parser.add_argument('-v', '--verbosity', action='store', dest='verbosity', default='1', type=int, choices=[0, 1, 2, 3], help='Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output') parser.add_argument('--settings', help=( 'The Python path to a settings module, e.g. ' '"myproject.settings.main". If this isn\'t provided, the ' 'DJANGO_SETTINGS_MODULE environment variable will be used.' ), ) parser.add_argument('--pythonpath', help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".') parser.add_argument('--traceback', action='store_true', help='Raise on CommandError exceptions') parser.add_argument('--no-color', action='store_true', dest='no_color', default=False, help="Don't colorize the command output.") if self.args: # Keep compatibility and always accept positional arguments, like optparse when args is set parser.add_argument('args', nargs='*') self.add_arguments(parser) return parser
首先判斷self.use_argparse,
@property def use_argparse(self): return not bool(self.option_list)
經過判斷option_list屬性否爲爲空, option_list在BaseCommand類中, 默認爲空元祖 。
option_list = ()
由於django目前傾向於使用argpase,因此這裏只說明一下使用argparse的這一部分。
這裏又出現了一個新的類CommandParser。
class CommandParser(ArgumentParser): """ Customized ArgumentParser class to improve some error messages and prevent SystemExit in several occasions, as SystemExit is unacceptable when a command is called programmatically. """ def __init__(self, cmd, **kwargs): self.cmd = cmd super(CommandParser, self).__init__(**kwargs) def parse_args(self, args=None, namespace=None): # Catch missing argument for a better error message if (hasattr(self.cmd, 'missing_args_message') and not (args or any(not arg.startswith('-') for arg in args))): self.error(self.cmd.missing_args_message) return super(CommandParser, self).parse_args(args, namespace) def error(self, message): if self.cmd._called_from_command_line: super(CommandParser, self).error(message) else: raise CommandError("Error: %s" % message)
CommandParser只是重寫了parse_args方法。增長missing_args_message這種狀況。
那麼它是怎麼判斷參數缺乏的狀況:
not (args or any(not arg.startswith('-') for arg in args))
這條語句放回true就表示參數缺乏。
那麼
args or any(not arg.startswith('-') for arg in args)
就應該返回false。這裏使用了or操做符,若是args不爲空的話,就返回args,那麼上條語句就至關於返回true了。
若是args爲空,就會返回後面的
any(not arg.startswith('-') for arg in args)
。那麼args= (),
not arg.startswith('-') for arg in args)
返回仍舊是一個(),這樣就沒什麼意思了。
還重寫了error()方法,只是改變拋出異常。
self.cmd._called_from_command_line,在run_from_args( )就直接賦值爲true。
再接着看create_parser方法,實例化CommandParser後, 添加'--version', '--verbosity', '--settings', '--pythonpath', '--traceback', '--no-color'可選參數。
注意下self.args這個屬性,若是self.args不爲空, 那麼後面的參數都會被收集在args中。
parser.add_argument('args', nargs='*')
這是爲了向後保持兼容性,而設定的。若是設置args屬性,就會致使後面的self.add_arguments(parser)無做用。
def add_arguments(self, parser): """ Entry point for subclassed commands to add custom arguments. """ pass
add_arguments( )方法,由子類實現,用於添加自定義的參數。
如今回到run_from_argv()方法中, 建立完parser後,就是解析參數了。
options = parser.parse_args(argv[2:]) cmd_options = vars(options) # Move positional args out of options to mimic legacy optparse args = cmd_options.pop('args', ())
vars將返回的NameSpace對象options,轉化爲字典cmd_options 。
單獨提出args這個參數。
接着調用handle_default_options(options)方法。
def handle_default_options(options): """ Include any default options that all commands should accept here so that ManagementUtility can handle them before searching for user commands. """ if options.settings: os.environ['DJANGO_SETTINGS_MODULE'] = options.settings if options.pythonpath: sys.path.insert(0, options.pythonpath)
主要是負責settings和pythonpath這兩個參數的處理。settings制定配置文件,經過改變環境變量DJANGO_SETTINGS_MODULE。pythonpath則增長搜索包和模塊的路徑,經過sys.path變量改變。
接着調用self.execute(*args, **cmd_options)方法。
def execute(self, *args, **options): """ Try to execute this command, performing system checks if needed (as controlled by attributes ``self.requires_system_checks`` and ``self.requires_model_validation``, except if force-skipped). """ if options.get('no_color'): self.style = no_style() self.stderr.style_func = None if options.get('stdout'): self.stdout = OutputWrapper(options['stdout']) if options.get('stderr'): self.stderr = OutputWrapper(options.get('stderr'), self.stderr.style_func) if self.can_import_settings: from django.conf import settings # NOQA saved_locale = None if not self.leave_locale_alone: # Only mess with locales if we can assume we have a working # settings file, because django.utils.translation requires settings # (The final saying about whether the i18n machinery is active will be # found in the value of the USE_I18N setting) if not self.can_import_settings: raise CommandError("Incompatible values of 'leave_locale_alone' " "(%s) and 'can_import_settings' (%s) command " "options." % (self.leave_locale_alone, self.can_import_settings)) # Switch to US English, because django-admin creates database # content like permissions, and those shouldn't contain any # translations. from django.utils import translation saved_locale = translation.get_language() translation.activate('en-us') try: if (self.requires_system_checks and not options.get('skip_validation') and # Remove at the end of deprecation for `skip_validation`. not options.get('skip_checks')): self.check() output = self.handle(*args, **options) if output: if self.output_transaction: # This needs to be imported here, because it relies on # settings. from django.db import connections, DEFAULT_DB_ALIAS connection = connections[options.get('database', DEFAULT_DB_ALIAS)] if connection.ops.start_transaction_sql(): self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql())) self.stdout.write(output) if self.output_transaction: self.stdout.write('\n' + self.style.SQL_KEYWORD(connection.ops.end_transaction_sql())) finally: if saved_locale is not None: translation.activate(saved_locale)
前面處理no_color,stdout,stderr的參數和self.can_import_settings屬性。
而後根據self.leave_locale_alone屬性,配置translation。
而後根據參數skip_validation, skip_checks,和self.requires_system_checks屬性,調用self.check( )檢查錯誤。
最後調用handle()方法,將返回的結果輸出。
整個命令的執行過程就是上面說的。
對於咱們要自定義命令,則主要經過設置一些類屬性。
實現add_arguments( )添加自定義的參數。
實現handle( )方法,實現本身的邏輯。
類屬性:
``args`` A string listing the arguments accepted by the command, suitable for use in help messages; e.g., a command which takes a list of application names might set this to '<app_label app_label ...>'. ``can_import_settings`` A boolean indicating whether the command needs to be able to import Django settings; if ``True``, ``execute()`` will verify that this is possible before proceeding. Default value is ``True``. ``help`` A short description of the command, which will be printed in help messages. ``option_list`` This is the list of ``optparse`` options which will be fed into the command's ``OptionParser`` for parsing arguments. Deprecated and will be removed in Django 2.0. ``output_transaction`` A boolean indicating whether the command outputs SQL statements; if ``True``, the output will automatically be wrapped with ``BEGIN;`` and ``COMMIT;``. Default value is ``False``. ``requires_system_checks`` A boolean; if ``True``, entire Django project will be checked for errors prior to executing the command. Default value is ``True``. To validate an individual application's models rather than all applications' models, call ``self.check(app_configs)`` from ``handle()``, where ``app_configs`` is the list of application's configuration provided by the app registry. ``requires_model_validation`` DEPRECATED - This value will only be used if requires_system_checks has not been provided. Defining both ``requires_system_checks`` and ``requires_model_validation`` will result in an error. A boolean; if ``True``, validation of installed models will be performed prior to executing the command. Default value is ``True``. To validate an individual application's models rather than all applications' models, call ``self.validate(app_config)`` from ``handle()``, where ``app_config`` is the application's configuration provided by the app registry. ``leave_locale_alone`` A boolean indicating whether the locale set in settings should be preserved during the execution of the command instead of being forcibly set to 'en-us'. Default value is ``False``. Make sure you know what you are doing if you decide to change the value of this option in your custom command if it creates database content that is locale-sensitive and such content shouldn't contain any translations (like it happens e.g. with django.contrim.auth permissions) as making the locale differ from the de facto default 'en-us' might cause unintended effects. This option can't be False when the can_import_settings option is set to False too because attempting to set the locale needs access to settings. This condition will generate a CommandError. """
實現add_arguments( ):
注意若是指定了args參數,就會致使add_arguments( )添加的參數沒用。
實現handle(self, *args, **options)方法:
若是制定了args屬性,則options只有 --version,--verbosity,--settings,--pythonpath值幾個屬性。
args參數則是命令行其他的參數。
若是沒有指定args屬性,則args爲空元組()。options則爲解析參數後的字典。