如何创建自定义 django-admin 命令

应用可以向 manage.py 注册自己的操作。例如,你可能希望给要发布的 Django 应用添加一个 manage.py 操作。本文为教程中的 polls 应用创建一个自定义 closepoll 命令。

在应用中添加 management/commands 目录。Django 会为这个目录内每个名称不以下划线开头的 Python 模块注册一个 manage.py 命令。例如:

polls/ 目录结构

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

在这个例子中,任何在 INSTALLED_APPS 中包含 polls 应用的项目,都可以使用 closepoll 命令。_private.py 模块不会成为管理命令。

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

独立脚本

自定义管理命令特别适合运行独立脚本,或者通过 UNIX crontab、Windows 计划任务控制面板定期执行脚本。

要实现命令,将 polls/management/commands/closepoll.py 编辑为:

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)
            )

使用管理命令并希望提供控制台输出时,应写入 self.stdout 和 self.stderr,而不是直接打印到 stdout 和 stderr。通过这些代理输出,自定义命令会更容易测试。消息末尾不需要添加换行符,系统会自动补上,除非指定 ending 参数:

控制台输出片段

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

可以通过 python manage.py closepoll <poll_ids> 调用这个新命令。

handle() 方法接受一个或多个 poll_ids,并将各投票对象的 poll.opened 设为 False。如果用户引用了不存在的投票,则抛出 CommandError。教程中的模型没有 poll.opened 属性;本示例为 polls.models.Question 添加了这个属性。

接受可选参数

通过接受额外命令行选项,可以轻松将同一个 closepoll 命令改成删除指定投票,而不是关闭投票。可以在 add_arguments() 中添加这些自定义选项:

closepoll.py:可选参数片段

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 字典参数访问。有关 add_argument 的更多用法,参见 Python 的 argparse 文档。

除了自定义命令行选项,所有管理命令也能接受一些默认选项,例如 --verbosity 和 --traceback。

管理命令与语言区域

管理命令默认使用当前激活的语言区域执行。

如果自定义管理命令必须在没有激活语言区域的情况下执行,例如为了防止将翻译后的内容写入数据库,可以在 handle() 方法上使用 @no_translations 装饰器来停用翻译:

命令类:停用翻译片段

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


class Command(BaseCommand):
    ...

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

停用翻译需要访问已配置的 settings,因此这个装饰器不能用于无需配置 settings 就能工作的命令。

测试

有关测试自定义管理命令的方法,参见测试文档。

覆盖命令

Django 先注册内置命令,然后按照 INSTALLED_APPS 的逆序查找命令。查找时,如果命令名与已注册的命令重复,新发现的命令会覆盖先前的命令。

换句话说,要覆盖一个命令,新命令必须与它同名,而且新命令所属应用必须在 INSTALLED_APPS 中排在被覆盖命令所属应用之前。

如果第三方应用的管理命令被意外覆盖,可以在项目的某个应用中创建一个新名称的命令,让它导入被覆盖命令的 Command 类。这个项目应用在 INSTALLED_APPS 中也必须位于第三方应用之前。

命令对象

class BaseCommand

所有管理命令最终都继承这个基类。

如果需要访问解析命令行参数并决定响应时调用哪些代码的全部机制,可以使用这个类。如果不需要改变这些行为,可以考虑使用它的某个子类。

继承 BaseCommand 必须实现 handle() 方法。

属性

所有属性都可以在派生类中设置,也可以用于 BaseCommand 的各子类。

BaseCommand.help

命令的简短描述。用户运行 python manage.py help <command> 时,它会显示在帮助信息中。

BaseCommand.missing_args_message

如果命令定义了必需的位置参数,可以自定义缺少参数时返回的错误消息。默认消息由 argparse 输出(“too few arguments”)。

BaseCommand.output_transaction

布尔值,表示命令是否输出 SQL 语句。为 True 时,输出自动由 BEGIN; 和 COMMIT; 包裹。默认值为 False。

BaseCommand.requires_migrations_checks

布尔值。为 True 时,如果磁盘上的迁移集合与数据库中的迁移不一致,命令会打印警告。警告不会阻止命令执行。默认值为 False。

BaseCommand.requires_system_checks

标签列表或元组,例如 [Tags.staticfiles, Tags.models]。命令执行前会检查指定标签下注册的系统检查是否有错误。值 '__all__' 表示执行全部系统检查;默认值为 '__all__'。

BaseCommand.style

这个实例属性用于在写入 stdout 或 stderr 时生成带颜色的输出。例如:

命令输出样式片段

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

有关修改调色板和可用样式,参见语法着色,并使用该节中各“角色”名称的大写形式。

执行命令时,如果传入 --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)

返回一个 CommandParser 实例。它是 ArgumentParser 的子类,带有 Django 的一些定制行为。

可以覆盖这个方法,并使用 ArgumentParser 的参数作为 kwargs 调用 super(),从而定制该实例。

BaseCommand.add_arguments(parser)

添加解析器参数的入口,用于处理传入命令行参数。自定义命令应覆盖这个方法,添加命令接受的位置参数和可选参数。直接继承 BaseCommand 时,不需要调用 super()。

BaseCommand.get_version()

返回 Django 版本;对于所有内置 Django 命令,它应当是正确版本。用户提供的命令可以覆盖这个方法,返回自己的版本。

BaseCommand.execute(*args, **options)

尝试执行命令,必要时执行由 requires_system_checks 属性控制的系统检查。如果命令抛出 CommandError,该异常会被捕获并打印到 stderr。

不要在代码中直接调用 execute() 执行命令,应使用 call_command()。

BaseCommand.handle(*args, **options)

命令的实际逻辑。子类必须实现这个方法。

它可以返回一个字符串,该字符串会打印到 stdout;如果 output_transaction 为 True,输出会由 BEGIN; 和 COMMIT; 包裹。

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

使用系统检查框架,检查整个 Django 项目中的潜在问题。严重问题作为 CommandError 抛出;警告输出到 stderr;轻微通知输出到 stdout。

如果 app_configs 和 tags 都为 None,会执行所有系统检查,但不包含部署检查和数据库相关检查。tags 可以是检查标签列表,例如 compatibility 或 models。

可以传入 include_deployment_checks=True,同时执行部署检查;也可以通过 databases 指定数据库别名列表,在这些数据库上执行数据库相关检查。

BaseCommand.get_check_kwargs(options)

为 check() 调用提供关键字参数,包括将 requires_system_checks 的值转换为 tags 关键字参数。

覆盖这个方法,可以改变提供给 check() 的参数。例如,要启用数据库相关检查,可以这样覆盖:

命令类:get_check_kwargs() 片段

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

BaseCommand 子类

class AppCommand

接受一个或多个已安装应用标签作为参数,并对每个应用执行操作的管理命令。

子类不实现 handle(),而必须实现 handle_app_config();每个应用都会触发一次调用。

AppCommand.handle_app_config(app_config, **options)

对 app_config 执行命令操作。app_config 是与命令行提供的应用标签对应的 AppConfig 实例。

class LabelCommand

接受一个或多个任意命令行参数(标签),并对每个参数执行操作的管理命令。

子类不实现 handle(),而必须实现 handle_label();每个标签都会触发一次调用。

LabelCommand.label

描述传入命令的任意参数的字符串,用于用法文本和错误消息。默认值为 'label'。

LabelCommand.handle_label(label, **options)

对 label 执行命令操作。label 就是命令行提供的字符串。

命令异常

exception CommandError(returncode=1)

表示执行管理命令时出现问题的异常类。

从命令行控制台执行管理命令时,如果抛出这个异常,它会被捕获,并转换成格式良好的错误消息,写入适当的输出流(即 stderr)。因此,抛出这个异常并提供合理的错误描述,是表示命令执行出错的推荐方式。

它接受可选的 returncode 参数,用于定制管理命令通过 sys.exit() 退出时的退出状态。

如果在代码中通过 call_command() 调用管理命令,需要自行在必要时捕获异常。


原文:How to create custom django-admin commands,Django 6.1 文档。本文为中文翻译。版权归 Django Software Foundation 和各贡献者所有。原文 get_check_kwargs() 说明中的关键字参数依同页 check() 签名写作 tags。

许可:Django BSD 三条款许可证。Copyright (c) Django Software Foundation and individual contributors. All rights reserved.

允许以源代码或二进制形式再分发和使用,无论是否修改,但须满足:源代码再分发保留上述版权声明、条件与下列免责声明;二进制再分发在文档和/或其他随附材料中重现上述版权声明、条件与下列免责声明;未经事先书面许可,不得使用 Django 或贡献者的名称为衍生产品背书或推广。

本软件由版权持有人与贡献者按原样提供,不作任何明示或默示保证,包括但不限于适销性及特定用途适用性。无论依据合同、严格责任或侵权(包括过失或其他原因),版权持有人与贡献者均不对因使用本软件产生的任何直接、间接、附带、特殊、惩罚性或后果性损失负责,包括替代商品或服务采购、使用损失、数据损失、利润损失或业务中断,即使已被告知可能发生此类损失。

原始代码许可证全文
Copyright (c) Django Software Foundation and individual contributors.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3. Neither the name of Django nor the names of its contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容