这篇文章我们主要来介绍一下关于 Django 的命令模块,我们经常会使用到,比如以下几个常用的命令,都属于 Django 的命令模块:
python manage.py makemigrations
python manage.py migrate
python manage.py startapp
python manage.py runserver
management
模块是 Django 框架中用于管理命令行操作的核心组件。它提供了一种简单而强大的方式,让开发人员可以通过命令行执行各种任务,如数据库迁移、创建超级用户、运行开发服务器等。
目录结构
本质上,在 Django 中,这些都是使用 management
模块来进行管理的,我们可以在 Django 源码目录以及通过 python manage.py startapp 创建的 app 目录中查看到所有支持的命令,在 Django 源码中,主要位于以下路径:django.core.management
,目录结构如下:
├── management
│ ├── __init__.py
│ ├── __pycache__
│ ├── base.py
│ ├── color.py
│ ├── commands
│ ├── sql.py
│ ├── templates.py
│ └── utils.py
核心的内容都位于 commands 目录下,我们再来看一下 commands 目录下都有什么文件:
├── commands
│ ├── __init__.py
│ ├── __pycache__
│ ├── check.py
│ ├── compilemessages.py
│ ├── createcachetable.py
│ ├── dbshell.py
│ ├── diffsettings.py
│ ├── dumpdata.py
│ ├── flush.py
│ ├── inspectdb.py
│ ├── loaddata.py
│ ├── makemessages.py
│ ├── makemigrations.py
│ ├── migrate.py
│ ├── optimizemigration.py
│ ├── runserver.py
│ ├── sendtestemail.py
│ ├── shell.py
│ ├── showmigrations.py
│ ├── sqlflush.py
│ ├── sqlmigrate.py
│ ├── sqlsequencereset.py
│ ├── squashmigrations.py
│ ├── startapp.py
│ ├── startproject.py
│ ├── test.py
│ └── testserver.py
是不是非常熟悉,我们平常执行 python manage.py help
所展示的命令,几乎都位于这里。
对于 Django 来说,它定义了关于命令模块的一些规则,我们只需要遵循即可找到相关的内置命令及实现自己的命令,后面会通过查看源码来解析原因:
- management和commands每个目录下都必须有个__init__.py空文件,表明这是一个python包。另外以下划线开头的文件名不能用作管理命令脚本。
- management/commands目录可以位于任何一个app的目录下,Django都能找到它。
- 一般建议每个python脚本文件对应一条管理命令。
Django是如何定义命令的
management
模块的实现涉及多个关键类和函数,我们来深入了解其中的一些重要概念和源码。
Django启动入口文件
我们从最熟悉的 Django 启动方式开始。我们都知道启动 Django的方式:python manage.py runserver
,查看 manage.py 文件的核心部分:
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
发现其调用了management
模块的 execute_from_command_line:
def execute_from_command_line(argv=None):
"""Run a ManagementUtility."""
utility = ManagementUtility(argv)
utility.execute()
我们从 ManagementUtility 这个核心类开始入手解析我们的命令模块。
ManagementUtility类
ManagementUtility
类是 Django management
模块中的核心类,它位于 django.core.management
包中。这个类负责解析命令行参数,并根据参数执行相应的命令。
简化后的 ManagementUtility
的核心代码如下,并对重要的部分做了注释:
class ManagementUtility:
def __init__(self, argv=None):
# 初始化操作
pass
def main_help_text(self, commands_only=False):
# 打印帮助文档
pass
def fetch_command(self, subcommand):
# 根据传入的子命令名称 subcommand 返回该命令的类
commands = get_commands()
try:
app_name = commands[subcommand]
except KeyError:
# 主要处理当输入命令错误或不存在时的错误信息
pass
if isinstance(app_name, BaseCommand):
klass = app_name
else:
klass = load_command_class(app_name, subcommand)
return klass
def autocomplete(self):
# 用于自动补全命令的。它是一个便利方法,可以帮助开发人员在命令行中输入管理命令时提供自动补全的功能。
pass
# 核心方法,整个命令入口执行的关键
def execute(self):
# 当不传任何命令是,默认执行 help 命令,即执行 python manage.py 等同于 python manage.py help
try:
subcommand = self.argv[1]
except IndexError:
subcommand = "help" # Display help if no arguments were given.
# 解析传入的命令行参数
parser = CommandParser(
prog=self.prog_name,
usage="%(prog)s subcommand [options] [args]",
add_help=False,
allow_abbrev=False,
)
parser.add_argument("--settings")
parser.add_argument("--pythonpath")
parser.add_argument("args", nargs="*") # catch-all
try:
options, args = parser.parse_known_args(self.argv[2:])
handle_default_options(options)
except CommandError:
pass # Ignore any option errors at this point.
# 确保所有的 INSTALLED_APPS 已经正确加载
try:
settings.INSTALLED_APPS
except ImproperlyConfigured as exc:
self.settings_exception = exc
except ImportError as exc:
self.settings_exception = exc
if settings.configured:
# 当命令为runserver并且不是 --noreload 模式时执行
if subcommand == "runserver" and "--noreload" not in self.argv:
try:
autoreload.check_errors(django.setup)()
except Exception:
# The exception will be raised later in the child process
# started by the autoreloader. Pretend it didn't happen by
# loading an empty list of applications.
apps.all_models = defaultdict(dict)
apps.app_configs = {}
apps.apps_ready = apps.models_ready = apps.ready = True
# Remove options not compatible with the built-in runserver
# (e.g. options for the contrib.staticfiles' runserver).
# Changes here require manually testing as described in
# #27522.
_parser = self.fetch_command("runserver").create_parser(
"django", "runserver"
)
_options, _args = _parser.parse_known_args(self.argv[2:])
for _arg in _args:
self.argv.remove(_arg)
# 进行 django 初始化操作,这也是 django 启动必须执行的第一个初始化动作
else:
django.setup()
# 自动补全命令
self.autocomplete()
if subcommand == "help":
# 打印帮助信息
pass
elif subcommand == "version" or self.argv[1:] == ["--version"]:
# 打印 django 版本信息
sys.stdout.write(django.get_version() + "\n")
elif self.argv[1:] in (["--help"], ["-h"]):
# 同样是打印帮助信息
sys.stdout.write(self.main_help_text() + "\n")
else:
# 执行具体子命令的 run_from_argv 方法,子命令为继承了 BaseCommand 类的子类
self.fetch_command(subcommand).run_from_argv(self.argv)
ManagementUtility类中最重要的就是 execute
方法,主要包括了以下几步:
- 检查传入的参数:根据传入的参数判断执行何种子命令。
- 通过
CommandParser
对象来解析命令行参数,并调用parse_known_args()
方法解析参数 - django 初始化: django.setup 确保 django 初始化。
- 调用子命令:根据子命令获取相应的命令类,并创建该命令类的实例,执行该命令实例的
run_from_argv
方法执行具体的命令内容。
通过上面的步骤,我们发现最后会执行具体子命令的 run_from_argv 方法,子命令为继承了 BaseCommand 类的子类,接下来我们来看看 BaseCommand 类。
BaseCommand类
每一个自定义的管理命令本质是一个Command类, 它继承了Django的Basecommand或其子类, 主要通过重写handle()方法实现自己的业务逻辑代码,而add_arguments()则用于帮助处理命令行的参数,如果运行命令时不需要额外参数,可以不写这个方法,下面是 BaseCommand 类的主要代码:
class BaseCommand:
# 帮助文本, 一般备注命令的用途及如何使用。
help = 'Some help texts'
def add_arguments(self, parser):
# 当我们自定义的命令类需要添加额外的参数选项时,重写这个方法
pass
def run_from_argv(self, argv):
"""
Set up any environment changes requested (e.g., Python path
and Django settings), then run this command. If the
command raises a ``CommandError``, intercept it and print it sensibly
to stderr. If the ``--traceback`` option is present or the raised
``Exception`` is not ``CommandError``, raise it.
"""
self._called_from_command_line = True
parser = self.create_parser(argv[0], argv[1])
options = parser.parse_args(argv[2:])
cmd_options = vars(options)
# Move positional args out of options to mimic legacy optparse
args = cmd_options.pop("args", ())
handle_default_options(options)
try:
# 去掉异常处理后,这个方法最重要的逻辑就在这里,通过 self.execute 执行子命令的逻辑
self.execute(*args, **cmd_options)
except CommandError as e:
if options.traceback:
raise
# SystemCheckError takes care of its own formatting.
if isinstance(e, SystemCheckError):
self.stderr.write(str(e), lambda x: x)
else:
self.stderr.write("%s: %s" % (e.__class__.__name__, e))
sys.exit(e.returncode)
finally:
try:
connections.close_all()
except ImproperlyConfigured:
# Ignore if connections aren't setup at this point (e.g. no
# configured settings).
pass
def execute(self, *args, **options):
"""
Try to execute this command, performing system checks if needed (as
controlled by the ``requires_system_checks`` attribute, except if
force-skipped).
"""
if options["force_color"] and options["no_color"]:
raise CommandError(
"The --no-color and --force-color options can't be used together."
)
if options["force_color"]:
self.style = color_style(force_color=True)
elif options["no_color"]:
self.style = no_style()
self.stderr.style_func = None
if options.get("stdout"):
self.stdout = OutputWrapper(options["stdout"])
if options.get("stderr"):
self.stderr = OutputWrapper(options["stderr"])
if self.requires_system_checks and not options["skip_checks"]:
if self.requires_system_checks == ALL_CHECKS:
self.check()
else:
self.check(tags=self.requires_system_checks)
if self.requires_migrations_checks:
self.check_migrations()
output = self.handle(*args, **options)
if output:
if self.output_transaction:
connection = connections[options.get("database", DEFAULT_DB_ALIAS)]
output = "%s\n%s\n%s" % (
self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()),
output,
self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()),
)
self.stdout.write(output)
return output
def handle(self, *args, **options):
# 任何自定义的命令都需要实现 handle 方法,这里是具体的子命令执行逻辑
raise NotImplementedError(
"subclasses of BaseCommand must provide a handle() method"
)
BaseCommand
类中最重要的就是如下几个方法:
add_arguments
:这个方法用于添加自定义的命令行参数。handle
: 这是BaseCommand
类中最重要的方法,用于执行实际的命令逻辑。当管理命令被调用时,Django 将调用该方法。你需要在子类中重写这个方法,并在其中定义你的命令的具体逻辑。run_from_argv(argv)
: 这个方法用于从命令行参数列表argv
中解析命令并执行它。它是BaseCommand
类的入口点方法,被 Django 的管理脚本manage.py
调用。它会解析命令行参数,调用handle()
方法执行实际的命令逻辑。execute
:用于执行实际的命令逻辑的核心方法,execute
方法会调用handle
方法,处理解析参数,在执行handle
方法时,如果发生错误,execute
方法会捕获并处理这些错误。
自定义命令
要注册自定义命令,我们需要在 Django 项目的某个应用中创建一个名为 management
的子目录,并在该目录中创建一个名为 commands
的子目录。在 commands
目录中,我们可以创建一个 Python 模块文件,并定义一个继承自 BaseCommand
的命令类。
以下是一个简单的自定义命令的示例:
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'This is my custom command.'
def handle(self, *args, **options):
self.stdout.write('Hello from my custom command!')
在上述示例中,我们创建了一个名为 Command
的自定义命令类,从Django4开始,该类的名称必须为Command
,继承自 BaseCommand
。在 handle()
方法中,我们简单地输出了一条消息。
要执行自定义命令,我们可以在命令行中使用以下命令:
python manage.py hello_world
其中,hello_world
是我们自定义命令的名称。