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则为解析参数后的字典。