有没有办法向现有的Django命令添加功能?

17 投票
3 回答
5667 浏览
提问于 2025-04-16 21:13

我想在一个 Django 命令开始之前,先运行一个命令。

举个例子:

$ python manage.py runserver
Validating models...

0 errors found
Django version 1.3, using settings 'creat1va.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
(started some command in the background)
[10/Jul/2011 21:50:26] "GET / HTTP/1.1" 200 1611
[10/Jul/2011 21:50:26] "GET /assets/css/master.css HTTP/1.1" 404 1783
[10/Jul/2011 21:50:26] "GET /assets/images/misc/logo.png HTTP/1.1" 404 1801
[10/Jul/2011 21:50:26] "GET /assets/images/icons/bo.gif HTTP/1.1" 404 1798
[10/Jul/2011 21:50:28] (My background process) "Some nice Feedback"

我的主要想法是启动一个后台进程,并输出日志信息。

有没有办法做到这一点,而不需要修改 Django 的源代码呢?

3 个回答

1

在你的应用里写一个管理命令,这个命令会先执行你自己定义的操作,然后再调用Django自带的功能。

7

为了进一步补充@Mario César的精彩回答,我想提供一个适用于Django 1.8及以上版本的现代代码版本,改编自他2011年的代码:

在Django 1.8之前,管理命令是基于optparse模块的[...] 现在管理命令使用argparse来解析参数,所有参数默认都是通过**options传递的[...]

来源

另外,我想指出,在问题中选择的特定命令runserver有一点复杂性,这使得它既是一个好例子也是一个坏例子。

坏例子是因为这个命令被Django本身也重写了。实际上,Django使用了Mario提出的相同方法:Django在staticfiles应用中重写了这个命令(见Django在github上的代码),以提供额外的静态文件选项。

因此,如果使用静态文件,最好重写staticfiles应用的命令,而不是核心命令。这也回答了@ts_pati的评论,说明了为什么会有问题。staticfiles的Django代码是如何重写的好例子,但这次需要导入staticfiles,以免失去那项功能:

from django.contrib.staticfiles.management.commands.runserver import Command as StaticfilesRunserverCommand


class Command(StaticfilesRunserverCommand):
    help = "Starts a lightweight Web server for development, serves static files and does some custom fancy stuff."

    def add_arguments(self, parser):
        super(Command, self).add_arguments(parser)
        parser.add_argument('--my-custom-argument', action="...", dest='my_custom_handler', default=True, help='do some stuff in fancy ways')

    def get_handler(self, *args, **options):
        """
        My fancy stuff function.
        """
        handler = super(Command, self).get_handler(*args, **options)
        my_custom_handler = options.get('my_custom_handler', True)
        # do stuff here
        return handler

编辑:我还想补充一下,INSTALLED_APPS中的顺序显然很重要,它必须在django.contrib.staticfiles之前。

20

你只需要知道,你可以像创建一个同名的应用程序一样,轻松地覆盖命令。

比如,我创建了一个应用,然后创建了一个和 runserver 同名的文件,接着在这个文件里扩展了 runserver 的基本类,添加了一个新功能,让它在运行之前执行。

举个例子,我想在 runserver 启动之前运行命令 $ compass watch,并且在 runserver 执行的过程中让它一直保持运行。

"""
Start $compass watch, command when you do $python manage.py runserver

file: main/management/commands/runserver.py

Add ´main´ app to the last of the installed apps
"""

from optparse import make_option
import os
import subprocess

from django.core.management.base import BaseCommand, CommandError
from django.core.management.commands.runserver import BaseRunserverCommand
from django.conf import settings

class Command(BaseRunserverCommand):
    option_list = BaseRunserverCommand.option_list + (
        make_option('--adminmedia', dest='admin_media_path', default='',
            help='Specifies the directory from which to serve admin media.'),
        make_option('--watch', dest='compass_project_path', default=settings.MEDIA_ROOT,
            help='Specifies the project directory for compass.'),
    )

    def inner_run(self, *args, **options):
        self.compass_project_path = options.get('compass_project_path', settings.MEDIA_ROOT)

        self.stdout.write("Starting the compass watch command for %r\n" % self.compass_project_path)
        self.compass_pid = subprocess.Popen(["compass watch %s" % self.compass_project_path],
            shell=True,
            stdin=subprocess.PIPE,
            stdout=self.stdout,
            stderr=self.stderr)
        self.stdout.write("Compas watch process on %r\n" % self.compass_pid.pid)

        super(Command, self).inner_run(*args, **options)

这样做效果很好。

想了解更多关于 Django 命令的细节,可以查看 https://docs.djangoproject.com/en/dev/howto/custom-management-commands/

希望这对某些人有帮助。

撰写回答