编写自定义 django-admin 命令P

应用能通过 manage.py 注册自定义活动。例如,你想为你发布的一个 Django 应用添加 manage.py 行为。我们将为 教程 中的 polls 应用构建自定义 closepoll 命令。

为达此目的,只需为应用添加一个 management/commands 目录。Django 会为每个名字不以下划线开头的 Python 模块注册一个 manage.py 命令。比如:

polls/
    __init__.py
    models.py
    management/
        commands/
            _private.py
            closepoll.py
    tests.py
    views.py

在本例中,closepoll 命令将对 INSTALLED_APPS 配置的所有包含 polls 应用的项目可用。

_private.py 模型不会以管理命令的形式提供。

closepoll.py 模块只有一个要求——必须定义 Command 类,继承自 BaseCommand 或其子类。

独立的脚本

自定义管理命令在运行独立脚本命令方面十分有用,也可用于 UNIX 的周期性 crontab 任务,或是 Windows 的定时任务。

为实现此命令,像这样编辑 polls/management/commands/closepoll.py:

from django.core.management.base import BaseCommand, CommandError
from polls.models import Question as Poll

class Command(BaseCommand):
    help = 'Closes the specified poll for voting'

    def add_arguments(self, parser):
        parser.add_argument('poll_ids', nargs='+', type=int)

    def handle(self, *args, **options):
        for poll_id in options['poll_ids']:
            try:
                poll = Poll.objects.get(pk=poll_id)
            except Poll.DoesNotExist:
                raise CommandError('Poll "%s" does not exist' % poll_id)

            poll.opened = False
            poll.save()

            self.stdout.write(self.style.SUCCESS('Successfully closed poll "%s"' % poll_id))

注解

当你使用管理命令时,期望在终端输出内容,你需要 write 至 self.stdoutself.stderr,而不是直接 print 至 stdoutstderr。利用这些代理,测试定义命令会更方便。也请注意下,你不需要为消息添加一个新行字符,它会自动添加,除非你指定了 ending 参数:

self.stdout.write("Unterminated line", ending='')

这个新的自定义命令能用 python manage.py closepoll <poll_id> 调用。

handle() 方法接受一个或多个 poll_ids,并将每个 poll.opened 设置为 False。若用户传入了不存在的 polls,将会抛出 CommandError教程 中不存在 poll.opened 属性,只是这里为了本例添加至 polls.models.Question 的。

接受可选参数P

closepoll 通过简单地添加几个命令选项,就能删除指定 poll,替代之前的关闭行为。这些自定义选项能被加入至 add_arguments() 方法,就像这样:

class Command(BaseCommand):
    def add_arguments(self, parser):
        # Positional arguments
        parser.add_argument('poll_ids', nargs='+', type=int)

        # Named (optional) arguments
        parser.add_argument(
            '--delete',
            action='store_true',
            help='Delete poll instead of closing it',
        )

    def handle(self, *args, **options):
        # ...
        if options['delete']:
            poll.delete()
        # ...

选项(本例中 delete)是由 handle 方法的 options 字典参数传入的。参见 Python 文档查询 argparse,获取更多 add_argument 的用法。

为了能添加自定义命令选项,所以的 管理命令 都能接受一些默认选项,例如 --verbosity--traceback

管理命令和地点(locales)P

默认情况下,管理命令以当前激活地点执行。

如果出于某些原因,自定义管理命令必须不以某个地点执行(比如,为了避免将已翻译的内容存入数据库),可在 handle() 方法中利用 @no_translations 装饰器禁用翻译行为:

from django.core.management.base import BaseCommand, no_translations

class Command(BaseCommand):
    ...

    @no_translations
    def handle(self, *args, **options):
        ...

由于禁用翻译需要使用配置文件,故装饰器不能用于那些不加载配置文件的命令。

Changed in Django 2.1:

@no_tanslations 装饰器是新的。在旧版本中,运行命令时,翻译行为默认是关闭的,除非命令的 leave_locale_alone 属性(现已移除)被置为 True

测试P

关于如何测试自定义管理命令的内容可在 测试文档 中找到。

覆盖命令P

Django 先注册内置命令,然后按相反的顺序在 INSTALLED_APPS 查找命令。在查找时,如果一个命令和已注册的命令重名,这个新发现的命令会覆盖第一个命令。

换句话说,为了覆盖一个命令,新命令必须有同样的名字并且它的 app 在 INSTALLED_APPS 中必须排在被覆盖命令 app 的前面。

第三方应用提供的管理命令若被不小心重写,能通过在你项目中的某个应用(在 INSTALLED_APPS 配置在第三方应用之前)创建新命令的方式为其取个别名,另其能被调用。这个应用需要导入被重写的 Command

命令对象P

class BaseCommand[源代码]P

所有管理命令最终派生的基类。

如果你想处理解析命令行参数,决定调用什么代码的过程,使用这个类。如果你不需要改变任何行为,考虑直接使用它的某个 子类

继承 BaseCommand 要求你重写 handle() 方法。

属性P

能被你派生的子类设置的,且能被 BaseCommand子类 使用的属性。

BaseCommand.helpP

命令简介,当用户运行命令 python manage.py help <command> 时包含在打印的帮助信息内。

BaseCommand.missing_args_messageP

如果你的命令定义了必须的位置参数,你可以在缺失参数时返回自定义错误消息。默认输出由 argparse 打印("too few arguments")。

BaseCommand.output_transactionP

一个布尔值,决定命令是否暑输出 SQL 语句;若为 True,输出内容会自动以 BEGIN;COMMIT; 包裹。默认值为 False

BaseCommand.requires_migrations_checksP

一个布尔值;若为 True,命令会在硬盘上存储的 migrations 与 数据库中的不一致时打印警告。警告不会阻止命令执行。默认值为 False

BaseCommand.requires_system_checksP

一个布尔值;若为 True,会在执行命令前完整地检验 Django 项目,确定是否有潜在的问题。默认值为 True

BaseCommand.styleP

一个实例属性,用于向 stdoutstderr 输出彩色内容。例如:

self.stdout.write(self.style.SUCCESS('...'))

参考 Syntax coloring 了解如何修改调色板与现成的样式(使用本节介绍的大写字母版本的 "roles")。

如果运行命令时传递了 --no-color 选项,所有的 self.style() 调用会返回未染色的原始字符串。

方法P

BaseCommand 有很多方法可供重写,不过仅有 handle() 是必须实现的。

子类中实现构造器

BaseCommand 的子类实现了 __init__ 方法,那么就必须调用 BaseCommand__init__:

class Command(BaseCommand):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # ...
BaseCommand.create_parser(prog_name, subcommand, **kwargs)[源代码]P

返回一个 CommandParser 实例,它是 ArgumentParser 的子类,包含一些针对 Django 的个性化设计。

你可以自定义这个实例,通过重写此方法,并为 super() 方法传入值为 ArgumentParserkwargs 参数。

Changed in Django 2.2:

kwargs 被添加了。

BaseCommand.add_arguments(parser)[源代码]P

添加命令行转换参数的入口,用于处理传给命令行的参数。自定义命令需要重写此方法,并同时添加命令接受的位置参数和可选参数。直接使用 BaseCommand 的子类时无需调用 super()

BaseCommand.get_version()[源代码]P

返回 Django 版本,内置 Django 命令必须正确返回。用户实现的命令可以重写此方法返回自己的版本。

BaseCommand.execute(*args, **options)[源代码]P

试着执行此命令,如果需要的话,进行系统检查(由 requires_system_checks 控制)。若命令抛出 CommandError,这会导致命令中断,并将错误输出至 stderr。

在你的代码中调用管理命令

执行命令时,不要从代码直接调用 execute() 方法。而是使用 call_command()

BaseCommand.handle(*args, **options)[源代码]P

命令的实际逻辑处理。子类必须实现此方法。

此方法可能会返回一个字符串,输出至 stdout (若 output_transactionTrue,则由 BEGIN;COMMIT 包裹)。

BaseCommand.check(app_configs=None, tags=None, display_num_errors=False)[源代码]P

利用系统检查框架校验 Django 项目是否存在隐含的问题。严重的问题会抛出 CommandError;警告会输出至 stderr;次要通知输出至 stdout。

app_configstags 均为 None,所以的系统检查项都会被运行。tags 能是一个检查标签的列表,例如 compatibilitymodels

BaseCommand 的子类P

class AppCommandP

管理命令接受一个或多个应用标签参数,并对每项应用做某些事。

子类必须实现 handle_app_config(),而不是 handle()。此方法会为每个应用调用一次。

AppCommand.handle_app_config(app_config, **options)P

app_config 运行命令,这会是 AppConfig 的实例,并指向命令行给定的应用标签。

class LabelCommandP

一个管理命令,从命令行接受一个或任意多个参数(labels),并针对每项做些事情。

子类必须实现 handle_label(),而不是 handle()。此方法会为每个应用调用一次。

LabelCommand.labelP

一个字符串,介绍了传递给命令的任意多个参数。此字符串用于命令的用法文本和错误消息。默认为 'label'

LabelCommand.handle_label(label, **options)P

运行 label 指定的命令动作,由命令行传入的字符串指定。

命令异常P

exception CommandError[源代码]P

异常类说明在运行管理命令时出错了。

如果异常是在命令行控制台运行管理命令时抛出的,它会被捕获,并转换为打印友好的错误信息,输出至合适的流(例如 stderr);作为结果,抛出异常(包含清晰的错误介绍)是一个不错的说明运行命令过程中出错的方式。

如果管理命令是由 call_command() 调用的,是否捕获异常取决于你。