django.core.management-command

    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

  1. 首先import相應的command類,而後調用run_from_argv( ), 傳入sys.argv看成參數。sql

  2. 而後調用create_parser( )方法,返回parser。django

  3. 解析參數,而且將解析後的參數傳入execute( ) 方法。app

  4. 執行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則爲解析參數後的字典。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息