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``.
这是命令执行的流程。
首先import相应的command类,然后调用run_from_argv( ), 传入sys.argv当作参数。
然后调用create_parser( )方法,返回parser。
解析参数,并且将解析后的参数传入execute( ) 方法。
执行handle( ),将返回的结果输出。
第一步会在以后讲,现在主要看后面处理的流程代码,是由BaseCommand实现。
因为BaseCommand类的实现代码比较多,就挑选相应的方法解析。
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])方法。
获得parser后,通过判断self.use_argparse,来确定返回的类型。因为目前有两种解析参数的方式,一种是optparse,将在django2.0后废弃。另一种是argparser。这是为了向后保持兼容性。
argv参数格式极为sys.argv,执行命令时的参数。举例:python manage runserver 0.0.0.0:8000
那么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则为解析参数后的字典。