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

要做到这点,只需向该应用添加一个management/commands目录。Django将为该目录中名字没有以下划线开始的每个Python模块注册一个manage.py命令。例如:

  1. polls/
  2. __init__.py
  3. models.py
  4. management/
  5. __init__.py
  6. commands/
  7. __init__.py
  8. _private.py
  9. closepoll.py
  10. tests.py
  11. views.py

在Python 2上,请确保managementmanagement/commands两个目录都包含__init__.py 文件,否则将检测不到你的命令。

在这个例子中,closepoll命令对任何项目都可使用,只要它们在INSTALLED_APPS里包含polls应用。

_private.py将不可以作为一个管理命令使用。

closepoll.py模块只有一个要求 – 它必须定义一个Command类并扩展自BaseCommand或其 子类。

独立的脚本

自定义的管理命令主要用于运行独立的脚本或者UNIX crontab和Windows周期任务控制面板周期性执行的脚本。

要实现这个命令,需将polls/management/commands/closepoll.py编辑成这样:

  1. from django.core.management.base import BaseCommand, CommandError
  2. from polls.models import Poll
  3. class Command(BaseCommand):
  4. help = 'Closes the specified poll for voting'
  5. def add_arguments(self, parser):
  6. parser.add_argument('poll_id', nargs='+', type=int)
  7. def handle(self, *args, **options):
  8. for poll_id in options['poll_id']:
  9. try:
  10. poll = Poll.objects.get(pk=poll_id)
  11. except Poll.DoesNotExist:
  12. raise CommandError('Poll "%s" does not exist' % poll_id)
  13. poll.opened = False
  14. poll.save()
  15. self.stdout.write('Successfully closed poll "%s"' % poll_id)
  1. Changed in Django 1.8:
  2. Django 1.8之前,管理命令基于optparse模块,位置参数传递给*args,可选参数传递给**options。现在,管理命令使用argparse解析参数,默认所有的参数都传递给**options,除非你命名你的位置参数为args(兼容模式)。对于新的命令,鼓励你仅仅使用**options

当你使用管理命令并希望提供控制台输出时,你应该写到self.stdoutself.stderr,而不能直接打印到 stdoutstderr。通过使用这些代理方法,测试你自定义的命令将变得非常容易。还请注意,你不需要在消息的末尾加上一个换行符,它将被自动添加,除非你指定ending参数:

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

新的自定义命令可以使用python manage.py closepoll <poll_id>调用。

handle()接收一个或多个poll_ids并为他们中的每个设置 poll.openedFalse。如果用户访问任何不存在的polls,将引发一个CommandErrorpoll.opened属性在教程中并不存在,只是为了这个例子将它添加到polls.models.Poll中。

接收可选参数

通过接收额外的命令行选项,可以简单地修改closepoll来删除一个给定的poll而不是关闭它。这些自定义的选项可以像下面这样添加到 add_arguments()方法中:

  1. class Command(BaseCommand):
  2. def add_arguments(self, parser):
  3. # Positional arguments
  4. parser.add_argument('poll_id', nargs='+', type=int)
  5. # Named (optional) arguments
  6. parser.add_argument('--delete',
  7. action='store_true',
  8. dest='delete',
  9. default=False,
  10. help='Delete poll instead of closing it')
  11. def handle(self, *args, **options):
  12. # ...
  13. if options['delete']:
  14. poll.delete()
  15. # ...
  1. Changed in Django 1.8:
  2. 之前,只支持标准的optparse库,你必须利用optparse.make_option()扩展命令option_list变量。

选项(在我们的例子中为delete)在handle方法的options字典参数中可以访问到。更多关于add_argument用法的信息,请参考argparse的Python 文档。

除了可以添加自定义的命令行选项, 管理命令还可以接收一些默认的选项,例如--verbosity--traceback

管理命令和区域设置

默认情况下,BaseCommand.execute()方法使转换失效,因为某些与Django一起的命令完成的任务要求一个与项目无关的语言字符串(例如,面向用户的内容渲染和数据库填入)。

  1. Changed in Django 1.8:
  2. 在之前的版本中,Django强制使用"en-us"区域设置而不是使转换失效。

如果,出于某些原因,你的自定义的管理命令需要使用一个固定的区域设置,你需要在你的handle()方法中利用I18N支持代码提供的函数手工地启用和停用它:

  1. from django.core.management.base import BaseCommand, CommandError
  2. from django.utils import translation
  3. class Command(BaseCommand):
  4. ...
  5. can_import_settings = True
  6. def handle(self, *args, **options):
  7. # Activate a fixed locale, e.g. Russian
  8. translation.activate('ru')
  9. # Or you can activate the LANGUAGE_CODE # chosen in the settings:
  10. from django.conf import settings
  11. translation.activate(settings.LANGUAGE_CODE)
  12. # Your command logic here
  13. ...
  14. translation.deactivate()

另一个需要可能是你的命令只是简单地应该使用设置中设置的区域设置且Django应该保持不让它停用。你可以使用BaseCommand.leave_locale_alone选项实现这个功能。

虽然上面描述的场景可以工作,但是考虑到系统管理命令对于运行非统一的区域设置通常必须非常小心,所以你可能需要:

  • 确保运行命令时USE_I18N设置永远为True(this is a good example of the potential problems stemming from a dynamic runtime environment that Django commands avoid offhand by deactivating translations)。
  • Review the code of your command and the code it calls for behavioral differences when locales are changed and evaluate its impact on predictable behavior of your command.

测试

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

Command 对象

class BaseCommand

所有管理命令最终继承的基类。

如果你想获得解析命令行参数并在响应中如何调用代码的所有机制,可以使用这个类;如果你不需要改变这个行为,请考虑使用它的子类。

继承BaseCommand类要求你实现handle()方法。

属性

所有的属性都可以在你派生的类中设置,并在BaseCommand的子类中使用。

BaseCommand.args

一个字符串,列出命令接收的参数,适合用于帮助信息;例如,接收一个应用名称列表的命令可以设置它为‘<app_label app_label ...>’。

  1. Deprecated since version 1.8:
  2. 现在,应该在add_arguments()方法中完成,通过调用parser.add_argument()方法。参见上面的closepoll例子。

BaseCommand.can_import_settings

一个布尔值,指示该命令是否需要导入Django的设置的能力;如果为Trueexecute()将在继续之前验证这是否可能。默认值为True

BaseCommand.help

命令的简短描述,当用户运行python manage.py help <command>命令时将在帮助信息中打印出来。

BaseCommand.missing_args_message

  1. New in Django 1.8.

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

BaseCommand.option_list

这是optparse选项列表,将赋值给命令的OptionParser用于解析命令。

  1. Deprecated since version 1.8:
  2. 现在,你应该覆盖`add_arguments()`方法来添加命令行接收的自定义参数。参见上面的例子。

BaseCommand.output_transaction

一个布尔值,指示命令是否输出SQL语句;如果为True,输出将被自动用BEGIN;COMMIT;封装。默认为False

BaseCommand.requires_system_checks

  1. New in Django 1.7.

一个布尔值;如果为True,在执行该命令之前将检查整个Django项目是否有潜在的问题。如果requires_system_checks缺失,则使用requires_model_validation的值。如果后者的值也缺失,则使用默认值(True)。同时定义requires_system_checksrequires_model_validation将导致错误。

BaseCommand.requires_model_validation

  1. Deprecated since version 1.7:
  2. requires_system_checks代替

一个布尔值;如果为True,将在执行命令之前作安装的模型的验证。默认为True。若要验证一个单独应用的模型而不是全部应用的模型,可以调用在handle()中调用validate()

BaseCommand.leave_locale_alone

一个布尔值,指示设置中的区域设置在执行命令过程中是否应该保持而不是强制设成‘en-us’。

默认值为False

如果你决定在你自定义的命令中修改该选项的值,请确保你知道你正在做什么。 如果它创建对区域设置敏感的数据库内容,这种内容不应该包含任何转换(比如django.contrib.auth权限发生的情况),因为将区域设置变成与实际上默认的‘en-us’ 不同可能导致意外的效果。更进一步的细节参见上面的管理命令和区域设置一节。

can_import_settings选项设置为False时,该选项不可以也为False,因为尝试设置区域设置需要访问settings。这种情况将产生一个CommandError

方法

BaseCommand有几个方法可以被覆盖,但是只有handle()是必须实现的。

在子类中实现构造函数

如果你在BaseCommand的子类中实现__init__,你必须调用BaseCommand__init__

  1. class Command(BaseCommand):
  2. def __init__(self, *args, **kwargs):
  3. super(Command, self).__init__(*args, **kwargs)
  4. # ...

BaseCommand.add_arguments(parser)

  1. New in Django 1.8.

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

BaseCommand.get_version()

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

BaseCommand.execute(*args, **options)

执行这个命令,如果需要则作系统检查(通过 requires_system_checks属性控制)。如果该命令引发一个CommandError,它将被截断并打印到标准错误输出。

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

不应该在你的代码中直接调用execute()来执行一个命令。请使用call_command

BaseCommand.handle(*args, **options)

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

BaseCommand.check(app_configs=None, tags=None, display_num_errors=False)

  1. New in Django 1.7.

利用系统的检测框架检测全部Django项目的潜在问题。严重的问题将引发CommandError;警告会输出到标准错误输出;次要的通知会输出到标准输出。

如果app_configstags都为None,将进行所有的系统检查。tags可以是一个要检查的标签列表,比如compatibilitymodels

BaseCommand.validate(app=None, display_num_errors=False)

  1. Deprecated since version 1.7:
  2. check命令代替

如果appNone,那么将检查安装的所有应用的错误。

BaseCommand 的子类

class AppCommand

这个管理命令接收一个或多个安装的应用标签作为参数,并对它们每一个都做一些动作。

子类不用实现handle(),但必须实现handle_app_config(),它将会为每个应用调用一次。

AppCommand.handle_app_config(app_config, **options)

app_config完成命令行的动作,其中app_configAppConfig的实例,对应于在命令行上给出的应用标签。

Changed in Django 1.7:

以前,AppCommand子类必须实现handle_app(app, **options),其中app是一个模型模块。新的API可以不需要模型模块来处理应用。迁移的最快的方法如下:

  1. def handle_app_config(app_config, **options):
  2. if app_config.models_module is None:
  3. return # Or raise an exception.
  4. app = app_config.models_module
  5. # Copy the implementation of handle_app(app_config, **options) here.

然而,你可以通过直接使用app_config的属性来简化实现。

class LabelCommand

这个管理命令接收命令行上的一个或多个参数(标签),并对它们每一个都做一些动作。

子类不用实现handle(),但必须实现handle_label(),它将会为每个标签调用一次。

LabelCommand.handle_label(label, **options)

label完成命令行的动作,label是命令行给出的字符串。

class NoArgsCommand

  1. Deprecated since version 1.8:
  2. 使用BaseCommand代替,它默认也不需要参数。

这个命令不接收命令行上的参数。

子类不需要实现handle(),但必须实现handle_noargs()handle()本身已经被覆盖以保证不会有参数传递给命令。

NoArgsCommand.handle_noargs(**options)

完成这个命令的动作

Command 的异常

class CommandError

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

如果这个异常是在执行一个来自命令行控制台的管理命令时引发,它将被捕获并转换成一个友好的错误信息到合适的输出流(例如,标准错误输出);因此,引发这个异常(并带有一个合理的错误描述)是首选的方式来指示在执行一个命令时某些东西出现错误。

如果管理命令从代码中通过call_command调用,那么需要时捕获这个异常由你决定。

译者:Django 文档协作翻译小组,原文:Adding custom commands

本文以 CC BY-NC-SA 3.0 协议发布,转载请保留作者署名和文章出处。

Django 文档协作翻译小组人手紧缺,有兴趣的朋友可以加入我们,完全公益性质。交流群:467338606。