自定义management命令

前面我们曾多次使用过形如python manage.py xxx的命令,例如runservermigrate等,这些都是Django框架内置的管理命令。实际上,Django也允许我们自定义管理命令,用于执行自定义系统维护、定时任务、批处理等操作。这篇笔记我们介绍自定义管理命令的用法。

创建自定义命令

自定义命令必须放在APP的management/commands/包下,假设我们要创建一个名为my_command的命令,它就应按约定如下放置。

app
├── __init__.py
├── management
│   └── __init__.py
│   └── commands/
│       ├── __init__.py
│       └── my_command.py
└── ...

注意:my_command.py必须位于合法的Python包中,这意味着其同级和上层都要有__init__.py,且该APP必须在INSTALLED_APPS中注册。

一个自定义命令的例子代码如下。

from django.core.management.base import BaseCommand

class Command(BaseCommand):
    help = '一个简单的示例命令'

    def handle(self, *args, **options):
        self.stdout.write('Hello, Django!')

自定义命令需要以类的形式定义,并继承BaseCommand,其中类属性help是帮助信息,用户使用命令时可用--help参数打印帮助信息,handle()方法具体定义了自定义命令的执行逻辑,我们这里只是打印Hello, Django!信息。

此时我们可以使用命令行测试。

python manage.py my_command

信息打印样式

Django管理命令在控制台上打印消息时应该使用self.stdout.write()方法而非print(),前者是Django提供的用于management命令的标准输出接口,它可以集成到Django的单元测试中,而且对颜色有直接支持。

def handle(self, *args, **options):
    # 添加各种样式
    self.stdout.write(self.style.SUCCESS('操作成功'))
    self.stdout.write(self.style.WARNING('这是警告'))
    self.stdout.write(self.style.ERROR('出错了'))

    # 也可以直接写到stderr
    self.stderr.write('错误输出')

参数定义与解析

Django底层使用Python标准库的argparse来解析参数,我们可以通过覆写BaseCommand的add_arguments()方法来定义参数,方法内的parse就来自argparse,我们可以按照argparse的使用说明添加位置参数和可选参数。

位置参数

from django.core.management.base import BaseCommand


class Command(BaseCommand):
    help = '删除指定ID的用户'

    def add_arguments(self, parser):
        # 接收一个或多个 user_id
        parser.add_argument(
            'user_ids',        # 参数名
            nargs='+',         # 至少一个
            type=int,          # 自动转为 int
            help='要删除的用户ID列表',
        )

    def handle(self, *args, **options):
        user_ids = options['user_ids']
        for uid in user_ids:
            self.stdout.write(f'删除用户: {uid}')

对应的测试命令如下。

python manage.py delete_users 1 2 3

nargs的常用取值如下表。

含义
N N个参数
? 0或1个
* 0或多个
+ 1或多个
省略 1个

可选参数

from django.core.management.base import BaseCommand


class Command(BaseCommand):
    help = "发送报告邮件"

    def add_arguments(self, parser):
        # --count:整数类型,有默认值
        parser.add_argument(
            "--count",
            type=int,
            default=10,
            help="发送的报告数量(默认 10)",
        )

        # --dry-run:布尔开关,它不需要具体指定参数,action="store_true"表示--dry-run出现即为True
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="演习模式,不实际发送",
        )

        # --email:接收多个值
        parser.add_argument(
            "--email",
            nargs="+",
            type=str,
            help="收件人邮箱,可传多个",
        )

    def handle(self, *args, **options):
        count = options["count"]
        dry_run = options["dry_run"]
        emails = options["email"] or []

        self.stdout.write(f"发送 {count} 份报告")
        self.stdout.write(f"收件人: {emails}")

        if dry_run:
            self.stdout.write(self.style.WARNING("演习模式,跳过实际发送"))
        else:
            self.stdout.write(self.style.SUCCESS("发送成功!"))

对应的测试命令如下。

python manage.py send_report --count 5 --email a@x.com b@x.com --dry-run

代码调用命令

Django中也支持我们直接通过代码调用封装的自定义management命令,不过它不是启动子进程,而是直接在同一个进程内调用,具体写法如下。

from django.core.management import call_command

call_command(
    "send_report",
    count=5,
    dry_run=True,
    email=["a@x.com", "b@x.com"],
)

call_command函数中,第1个参数是自定义命令名,后面的关键字参数都是命令的命名参数。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。