Django源码之Commands

使用django开发,对python manage.py ***命令模式肯定不会陌生。比较常用的有runserver,migrate…本文讲述如何自定义扩展manage命令。

源码分析

1.manage.py的源码

  • 首先设置了settings文件,本例中TestDango指的是project_name。
  • 其次执行了一个函数django.core.management.execute_from_command_line(sys.argv),这个函数传入了命令行参数sys.argv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #!/usr/bin/env python
    import os
    import sys

    if __name__ == '__main__':
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TestDango.settings')
    try:
    from django.core.management import execute_from_command_line
    except ImportError as exc:
    raise ImportError(
    "Couldn't import Django. Are you sure it's installed and "
    "available on your PYTHONPATH environment variable? Did you "
    "forget to activate a virtual environment?"
    ) from exc
    execute_from_command_line(sys.argv)

2.execute_from_command_line

  • ManagementUtility 只是简单的赋值,主要跟踪execute
    1
    2
    3
    4
    def execute_from_command_line(argv=None):
    """Run a ManagementUtility."""
    utility = ManagementUtility(argv)
    utility.execute()

3.execute()

  • execute主要是解析命令名并做一些特殊命令(version,help,runserver)处理,直接看最后一段
    1
    2
    3
    4
    5
    6
    7
    8
    # 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)

4.fetch_command

  • get_commands 获取命令
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    def fetch_command(self, subcommand):
    """
    Try 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:
    if os.environ.get('DJANGO_SETTINGS_MODULE'):
    # If `subcommand` is missing due to misconfigured settings, the
    # following line will retrigger an ImproperlyConfigured exception
    # (get_commands() swallows the original one) so the user is
    # informed about it.
    settings.INSTALLED_APPS
    else:
    sys.stderr.write("No Django settings specified.\n")
    possible_matches = get_close_matches(subcommand, commands)
    sys.stderr.write('Unknown command: %r' % subcommand)
    if possible_matches:
    sys.stderr.write('. Did you mean %s?' % possible_matches[0])
    sys.stderr.write("\nType '%s help' for usage.\n" % 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

5.get_commands

  • 先遍历django.core下命令,如果app 配置成功 在遍历app 下命令
  • app 下找management目录
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def get_commands():

    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

6.find_commands

  • 遍历management/commands 路径下所有不再是包且不为’_’开头的子模块或子包
    1
    2
    3
    4
    5
    6
    7
    8
    def find_commands(management_dir):
    """
    Given a path to a management directory, return a list of all the command
    names that are available.
    """
    command_dir = os.path.join(management_dir, 'commands')
    return [name for _, name, is_pkg in pkgutil.iter_modules([command_dir])
    if not is_pkg and not name.startswith('_')]

如何实现自定义命令

我们然后再查看django 源码

1.源码路径

2.参考源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from django.core.management.base import BaseCommand, CommandError
from django.db import DEFAULT_DB_ALIAS, connections


class Command(BaseCommand):
help = (
"Runs the command-line client for specified database, or the "
"default database if none is provided."
)

requires_system_checks = False

def add_arguments(self, parser):
parser.add_argument(
'--database', action='store', dest='database', default=DEFAULT_DB_ALIAS,
help='Nominates a database onto which to open a shell. Defaults to the "default" database.',
)

def handle(self, **options):
connection = connections[options['database']]
try:
connection.client.runshell()
except OSError:
# Note that we're assuming OSError means that the client program
# isn't installed. There's a possibility OSError would be raised
# for some other reason, in which case this error message would be
# inaccurate. Still, this message catches the common case.
raise CommandError(
'You appear not to have the %r program installed or on your path.' %
connection.client.executable_name
)

综上

  1. 运行命名创建一个自己的app

    1
    python manage.py startapp testCmd
  2. 将app 名字配置到setting 中

  3. 创建包management.commands
  4. 创建hello.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    from django.core.management.base import BaseCommand, CommandError


    class Command(BaseCommand):
    def add_arguments(self, parser):

    parser.add_argument(
    '-n',
    '--name',
    action='store',
    dest='name',
    default='close',
    help='name of author.',
    )

    def handle(self, *args, **options):
    try:
    if options['name']:
    print('hello world, %s' % options['name'])
    #print(dir(self))
    self.stdout.write(self.style.SUCCESS('命令%s执行成功, 参数为%s' % (__file__, options['name'])))
    except CommandError as ex:
    self.stdout.write(self.style.ERROR('命令执行出错'))

5.测试命令

1
2
3
4
5
6
7
python manage.py hello -n helloword
#正常输出
命令D:\WorkSpace\JWeb\TestDango\testcmd\management\commands\hello.py执行成功, 参数为helloword
python manage.py -h
正常输出
[testcmd]
hello