项目结构
这一课你将能做到
- 用
startproject + startapp 创建标准的 Django 项目骨架,并解释每个文件的作用
- 正确区分「项目(project)」和「应用(app)」,并知道何时该拆 app
- 修改
settings.py 的关键配置(INSTALLED_APPS、DATABASES、TEMPLATES、静态文件),并理解配置加载顺序
为什么要学
Django 项目的目录结构和 Spring Boot 的 Maven 结构一样,是「约定优于配置」的体现。你打开任何一份生产级 Django 代码,目录骨架 95% 一致。看懂结构就拿到了读 Django 代码的钥匙。
和 Spring Boot 对照:Spring Boot 一个项目就是一个可执行 jar,用包(package)划分业务模块;Django 一个项目下挂多个 app,每个 app 是一个独立可复用的 Python 包,有自己的 models/views/urls/templates。理解这个差异,是理解 Django 模块化设计的关键。
先建立图像
标准 Django 项目目录(项目名 mysite,包含一个 app blog):
mysite/ ← 项目根(你随便起名)
├── manage.py ← 命令行入口
├── mysite/ ← 项目配置包(项目名同名)
│ ├── __init__.py
│ ├── settings.py ← 全局配置
│ ├── urls.py ← 根 URL 路由
│ ├── asgi.py ← ASGI 入口(异步部署用)
│ └── wsgi.py ← WSGI 入口(同步部署用)
├── blog/ ← 应用(可复用的业务模块)
│ ├── migrations/ ← 数据库迁移文件
│ ├── admin.py ← Admin 注册
│ ├── apps.py ← app 配置
│ ├── models.py ← 数据模型
│ ├── tests.py ← 测试
│ ├── urls.py ← app 自己的路由(可选)
│ └── views.py ← 视图
├── templates/ ← 项目级模板(可选)
├── static/ ← 项目级静态文件(可选)
└── db.sqlite3 ← 默认 SQLite 数据库
一次请求的路径:
浏览器 → WSGI → settings.ROOT_URLCONF(mysite/urls.py)
→ 匹配 path("blog/", include("blog.urls"))
→ blog/urls.py → blog/views.py::post_detail
→ blog/models.py (查数据库)
→ templates/blog/post_detail.html (渲染)
→ 返回 HTML
跟着做
步骤 1:创建项目
django-admin startproject mysite .
cd mysite
注意末尾的 .——让 Django 在当前目录生成项目,避免多套一层同名目录。
步骤 2:创建第一个 app
python manage.py startapp blog
执行后多了 blog/ 目录。app 的精髓是「可复用」——理论上你可以把这个 blog/ 拷到别的 Django 项目里直接用。
步骤 3:注册 app 到项目
打开 mysite/settings.py,在 INSTALLED_APPS 末尾加上:
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"blog", # ← 新增
]
这一步常被初学者漏掉。不注册,Django 不会扫描这个 app 的 models.py,迁移时找不到模型。
步骤 4:看一眼关键配置
# 安全相关
SECRET_KEY = "django-insecure-..." # 会话/CSRF 签名密钥,生产环境要从环境变量读
DEBUG = True # 开发模式;生产必须 False
ALLOWED_HOSTS = [] # 允许访问的域名/IP,DEBUG=False 时必须配置
# 应用与中间件
INSTALLED_APPS = [...]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", # CSRF 防护
"django.contrib.auth.middleware.AuthenticationMiddleware", # 注入 request.user
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
# URL 入口
ROOT_URLCONF = "mysite.urls"
# 模板
TEMPLATES = [{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"], # 项目级模板目录
"APP_DIRS": True, # 自动扫描每个 app 下的 templates/
...
}]
# 数据库
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# 静态文件
STATIC_URL = "static/"
步骤 5:用 app 拆 URL
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("blog/", include("blog.urls")), # 把 /blog/ 前缀转发给 blog app
]
from django.urls import path
from . import views
app_name = "blog" # 命名空间,反查 URL 时用 blog:post_list
urlpatterns = [
path("", views.post_list, name="post_list"),
path("<int:pk>/", views.post_detail, name="post_detail"),
]
from django.http import HttpResponse
def post_list(request):
return HttpResponse("文章列表")
def post_detail(request, pk):
return HttpResponse(f"文章详情 id={pk}")
启动后访问 /blog/ 和 /blog/42/ 都能命中对应视图。
步骤 6:理解 manage.py 干了什么
#!/usr/bin/env python
import os
import sys
def main():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError("...") from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
核心就一件事:设置 DJANGO_SETTINGS_MODULE 环境变量,告诉 Django 去哪读配置,然后把命令行交给 Django 内核。
讲清楚要点
- 项目 vs 应用:项目是网站整体配置+入口;app 是可复用功能模块。一个项目可挂 N 个 app。判断标准:能不能拷到别的项目直接用?能→app。
INSTALLED_APPS 是注册表:Django 启动时按顺序加载这些 app,扫描它们的 models、admin、signals。漏注册 = app 没接入。
BASE_DIR 是路径基准:settings.py 顶部定义的 Path(__file__).resolve().parent.parent,所有路径配置都基于它,避免硬编码绝对路径。
- 配置分层思想:生产项目常把 settings 拆成
base.py / dev.py / prod.py,或读环境变量。开发期单文件够用,但要知道有这套玩法。第 11 课会展开。
include() 是路由分发器:项目根 urls.py 不该写具体业务路由,只该 include 各 app 的 urls。这样 app 自治,可插拔。
wsgi.py vs asgi.py:wsgi 是同步部署入口(Gunicorn、uWSGI);asgi 是异步入口(Uvicorn、Daphne),支持 WebSocket。新项目两个文件都有,按部署方式选。
你可能会懵的地方
-
什么时候拆 app?
功能边界清晰就拆:用户/订单/文章/评论各一个 app。别一上来拆 10 个,也别全堆在一个 app 里。经验法则:一个 app 的 models.py 超过 500 行就该考虑拆。
-
__init__.py 能删吗?
不能。它把目录标记成 Python 包,删了 import 会失败。Python 3.3+ 支持「命名空间包」不带它也能跑,但 Django 默认约定还是带上。
-
mysite/mysite/ 同名目录是干嘛的?
外层是项目容器,内层是项目「配置包」。改名内层不影响,只要 settings.py 里的 ROOT_URLCONF 和 WSGI_APPLICATION 指向正确即可。
-
app 下的 urls.py 是必须的吗?
不是。你可以把所有路由都写在项目根 urls.py 里。但工程上推荐每个 app 自带 urls,根 urls 只做 include。
-
apps.py 里那个类有什么用?
是 app 的「配置类」。可以重写 ready() 方法在 app 加载完成时执行初始化代码(比如注册信号)。普通情况不用动。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 改了 models.py 没把 app 加进 INSTALLED_APPS | makemigrations 检测不到 | 创建 app 后立即注册 |
| 在根 urls.py 写一堆业务路由 | 根路由膨胀、app 无法复用 | 用 include() 分发到 app |
BASE_DIR 写死绝对路径 | 换机器就崩 | 始终用 BASE_DIR / "xxx" |
DEBUG = True 直接上生产 | 报错页泄露敏感信息 | 生产必须 DEBUG=False 并配 ALLOWED_HOSTS |
| 把密钥硬编码在 settings.py 并提交到 git | 安全事故 | os.environ.get("SECRET_KEY") 从环境变量读 |
卡点急救
ModuleNotFoundError: No module named 'blog':startapp 之后没把 blog 加入 INSTALLED_APPS,或者运行 manage.py 时工作目录不对(必须在项目根)。
- 访问
/blog/ 返回 404,但路由明明配了:根 urls.py 里忘了 include("blog.urls"),或 app_name 与 namespace 冲突。
ImproperlyConfigured: The SECRET_KEY setting must not be empty:环境变量没设或没读到。检查 .env 是否被加载,或临时在 settings.py 里写个默认值。
面试常问
-
Django 项目和 app 的区别?什么场景拆 app?
项目是站点级容器(配置、URL 入口、WSGI/ASGI);app 是可复用的业务模块(用户、订单、文章)。当一个功能模块可以独立打包复用,或团队多人协作需要明确边界时,应该拆 app。
-
Django 启动时 settings.py 的加载顺序是怎样的?
manage.py 设 DJANGO_SETTINGS_MODULE → Django 启动时 import 该模块 → 模块顶部定义 BASE_DIR 等基础常量 → INSTALLED_APPS 逐个加载,触发各 app 的 apps.py::AppConfig.ready() → 路由懒加载(首次请求时编译)。
-
生产环境 SECRET_KEY 怎么管?
不能进 git。常见做法:环境变量(os.environ.get)、密钥管理服务(Vault、AWS Secrets Manager)、CI/CD 注入。开发可用 django-insecure- 前缀的随机串,生产必须独立生成且保密。
自测三问
- 为什么 Django 要把项目「配置包」和应用「app」分开?
INSTALLED_APPS 漏写一个 app 会发生什么?
include() 在 urls.py 里的作用?为什么推荐根 urls 只做 include?
小结一句话
Django 项目 = 一份配置 + 一组可插拔 app;理解 settings.py 和 urls.py 的角色分工,是写大型 Django 应用的地基。
延伸