Django 命令行工具:基础用法与自定义扩展 | Django 系列教程 (1)
TL;DR
Django 默认为开发者集成了一系列的命令行,方便开发者去创建、开发以及维护 Django 项目及当中的不同子模块;如果你想要自定义也相当方便,只需要按对应约定去设定目录结构并编写命令的业务逻辑即可。Django 的项目结构通常都是一种非常固定的模式,可以让多人协作尽可能保持同样风格。
django-admin
和 manage.py
你可以利用 uv 这样的现代化工具来创建 Python 虚拟环境并安装依赖,再不济直接使用内置的 venv
模块并搭配 pip
命令安装依赖:
uv add django
# or use pippip install django
接着你就能在当前解释器路径中找到 django-admin
,我们可以用该命令来创建一个 Django 项目工程,比如我们创建一个 djangotutor
项目:
django-admin startproject djangotutor
startproject
通常命令用于帮我们在当前路径下创建一个 Django 工程项目,当中包含了许多模版代码文件。
所以运行上述命令后,在你当前路径下就会多了一个 djangotutor
的文件夹,打开长这样:
djangotutor├── djangotutor│ ├── __init__.py│ ├── asgi.py│ ├── settings.py│ ├── urls.py│ └── wsgi.py└── manage.py
2 directories, 6 files
django-admin
是一个全局命令,但在具体工程项目中我们主要使用的是 manange.py
管理并运行其他命令行,这样不同的 Django 项目就不会干扰。
所以当你切换到 djangotutor
路径下通过它来执行 runserver
命令就可以启动我们的 Django 项目了。
Django,启动!
uv run manange.py runserver
# or native python interpreterpython manange.py runserver
运行之后默认会在 8000 端口启动一个本地开发调试的服务器,你可以在浏览器中打开 http://localhost:8000/
来访问。
需要注意的是,
runserver
命令仅适用于本地开发调试的服务器,在生产环境下我们可能需要类似于 gunicorn 这样的 WSGI 或 ASGI 服务器才行。
自定义命令
除了内置的命令行之外,Django 还允许我们自定义命令,并通过 manange.py
来统一管理。但前提是:
- 我们需要自己创建一个应用,然后在应用目录下创建一个
management
文件夹和commands
文件夹; - 在 Django 根项目设置中添加应用。
现在先让我们直接基于 manage.py
来运行 startapp
命令,它会在当前的 Django 工程目录下新建一个应用模块:
uv run manage.py startapp myapp
# or native python interpreterpython manage.py startapp myapp
最后的文件目录树如下所示:
.├── djangotutor│ ├── __init__.py│ ├── asgi.py│ ├── settings.py│ ├── urls.py│ └── wsgi.py├── manage.py└── myapp ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py └── views.py
接着我们要在 myapp
应用中自定义命令,即根据约定式写法创建一个 myapp/management/commands/seed.py
文件作为模拟填充数据库示例数据的命令。
下面是一个很简单的伪代码示例,我们只需要基于 Django 为我们封装好的 BaseCommand
基类来实现我们的命令即可,其中 add_arguments
方法用于添加命令行参数,而 handle
方法用于执行命令:
from django.core.management.base import BaseCommand
class Command(BaseCommand): help = "Seed the database with initial data."
def add_arguments(self, parser): parser.add_argument( "-n", "--number", type=int, help="Number of people to seed. Defaults to 3.", default=3, )
def handle(self, *args, **options): data = [] self.stdout.write("Seeding the database...") for i in range(options["number"]): data.append(i) self.stdout.write("Done!") self.stdout.write(f"Seeding data: {data}")
现在我们到 djangotutor/settings.py
根项目设置文件中找到 INSTALL_APPS
配置项,然后将我们的应用名称添加进去即可:
...
INSTALLED_APPS = [ ... "myapp",]
...
现在当你再次调用 manage.py
时就能查找到 seed
命令了。
uv run manage.py --help
Type 'manage.py help <subcommand>' for help on a specific subcommand.
Available subcommands:
[auth] changepassword createsuperuser
[contenttypes] remove_stale_contenttypes
[django] check compilemessages createcachetable dbshell diffsettings dumpdata flush inspectdb loaddata makemessages makemigrations migrate optimizemigration sendtestemail shell showmigrations sqlflush sqlmigrate sqlsequencereset squashmigrations startapp startproject test testserver
[myapp] seed
[sessions] clearsessions
[staticfiles] collectstatic findstatic runserver
最后让我们运行并验证一下:
uv run manage.py seedSeeding the database...Done!Seeding data: [0, 1, 2]
uv run manage.py seed -n 10Seeding the database...Done!Seeding data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
嗒哒!成功运行。
当然了,如果你觉得用 OOP 的方式来写命令行有点繁琐,恰好又有用过 Click 这个库,那么你可以额外集成 django-click 或 django-typer 其中之一来提高编写命令行的效率。
这里以 django-click 为例,上述的命令可以改成这样:
import djclick as click
@click.command()@click.option( "-n", "--number", type=int, help="Number of people to seed. Defaults to 3.", default=3,)def seed(number): data = [] print("Seeding the database...") for i in range(number): data.append(i) print("Done!") print(f"Seeding data: {data}")
练习题
现在请你从头开始搭建一个名为
ailiaili
的 Django 项目,并成功将其运行在本地的 8080 端口上(如果端口被占用可以换一个);在
ailiaili
项目中创建一个名为articles
的应用,并为其添加一个create
命令,然后我们能通过 Django 命令行快速地创建一篇文章。这里我给出一个创建文章的基本逻辑代码:from django.conf import settingsARTICLES_DIR = settings.BASE_DIR / "articles"def create_article(title: str, content: str):"""Mock article creation.This function will write a new article to local file."""if not ARTICLES_DIR.exists():ARTICLES_DIR.mkdir()filepath = ARTICLES_DIR / f"{title}.md"filepath.write_text(content)print(f"Created article: {filepath.absolute()}")