编写自定义 django-admin 命令

应用程序可以用 manage.py 注册自己的动作。例如,你可能想为你正在分发的 Django 应用添加一个 manage.py 动作。在本文档中,我们将为 教程 中的 polls 应用程序构建一个自定义的 closepoll 命令。

要做到这一点,将一个 management/commands 目录添加到应用程序中。Django 将为该目录中名称不以下划线开头的每个 Python 模块注册一个 manage.py 命令。例如:

polls/
    __init__.py
    models.py
    management/
        __init__.py
        commands/
            __init__.py
            _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)
            )

Note

当你使用管理命令并希望提供控制台输出时,你需要 write 至 self.stdoutself.stderr,而不是直接 print 至 stdoutstderr。利用这些代理,测试自定义命令会更方便。还需要注意的是,你不需要用换行符来结束消息,它会自动添加,除非你指定了 ending 参数:

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

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

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

接受可选参数

同样的 closepoll 可以很容易地修改,通过接受额外的命令行选项来删除一个给定的投票,而不是关闭它。这些自定义选项可以在 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()
        # ...

The option (delete in our example) is available in the options dict parameter of the handle method. See the argparse Python documentation for more about add_argument usage.

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

管理命令和本地化

默认情况下,管理命令以当前活动的 locale 执行。

如果由于某些原因,你的自定义管理命令必须在没有活动的 locale 的情况下运行(例如,为了防止翻译的内容被插入数据库),请在你的 handle() 方法上使用 @no_translations 装饰器停用翻译:

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


class Command(BaseCommand):
    ...

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

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

测试

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

覆盖命令

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

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

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

命令对象

class BaseCommand[source]

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

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

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

属性

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

BaseCommand.help

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

BaseCommand.missing_args_message

If your command defines mandatory positional arguments, you can customize the message error returned in the case of missing arguments. The default is output by argparse ("too few arguments").

BaseCommand.output_transaction

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

BaseCommand.requires_migrations_checks

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

BaseCommand.requires_system_checks

一个标签的列表或元组,例如 [Tags.staticfiles, Tags.models] 。系统会在执行命令 registered in the chosen tags 之前检查是否有错误。值 '__all__' 可用于指定应执行所有系统检查。 默认值为 '__all__'

BaseCommand.style

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

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

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

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

BaseCommand.suppressed_base_arguments

要在帮助输出中取消显示的默认命令选项。这应该是一个选项名称的集合(例如,'--verbosity')。取消显示的选项仍然传递其默认值。

方法

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)[source]

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

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

BaseCommand.add_arguments(parser)[source]

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

BaseCommand.get_version()[source]

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

BaseCommand.execute(*args, **options)[source]

尝试执行此命令,根据 requires_system_checks 属性控制是否需要执行系统检查。如果命令引发 CommandError,它将被拦截并打印到 stderr

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

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

BaseCommand.handle(*args, **options)[source]

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

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

BaseCommand.check(app_configs=None, tags=None, display_num_errors=False, include_deployment_checks=False, fail_level=checks.ERROR, databases=None)[source]

使用系统检查框架来检查整个 Django 项目中的潜在问题。严重的问题将作为 CommandError 引发;警告将输出到 stderr;较小的通知将输出到 stdout

如果 app_configstags 都是 None,则执行除了部署和数据库相关检查之外的所有系统检查。tags 可以是一个检查标签的列表,比如 compatibilitymodels

你可以传递 include_deployment_checks=True 以执行部署检查,并在 databases 中传递数据库别名列表以对其运行数据库相关检查。

BaseCommand.get_check_kwargs(options)[source]
New in Django 5.2.

Supplies kwargs for the call to check(), including transforming the value of requires_system_checks to the tag kwarg.

Override this method to change the values supplied to check(). For example, to opt into database related checks you can override get_check_kwargs() as follows:

def get_check_kwargs(self, options):
    kwargs = super().get_check_kwargs(options)
    return {**kwargs, "databases": [options["database"]]}

BaseCommand 的子类

class AppCommand

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

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

AppCommand.handle_app_config(app_config, **options)

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

class LabelCommand

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

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

LabelCommand.label

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

LabelCommand.handle_label(label, **options)

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

命令异常

exception CommandError(returncode=1)[source]

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

如果在从命令行控制台执行管理命令期间引发此异常,它将被捕获并转换为适当输出流(即 stderr)上的精美打印的错误消息;因此,引发此异常(附带一个合理的错误描述)是指示命令执行出现问题的首选方式。它接受可选的 returncode 参数,以自定义管理命令退出时使用的退出状态,使用 sys.exit()

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