1. 项目概述:为什么 Django 模板与静态文件管理总让人“卡在部署前五分钟”
你有没有过这样的经历:本地开发时页面样式完美、按钮点击流畅、图片加载飞快,可一到服务器上,CSS 全失效、JS 报 404、Logo 图片变成一个破碎的方框?打开浏览器开发者工具一看,Network 标签页里满屏红色的
GET /static/css/app.css 404
和
GET /templates/base.html 404
—— 这不是代码写错了,而是 Django 的模板与静态文件系统没被真正“驯服”。我带过二十多个 Django 项目从零上线,其中超过 60% 的首次部署失败,根源不在数据库迁移、不在 Nginx 配置,而就卡在这两个看似最基础的环节:
Templates(模板)怎么找、怎么渲染、怎么继承;Static Files(静态文件)怎么收集、怎么定位、怎么让 Web 服务器正确服务
。这不是“配个路径就能好”的小问题,它背后牵扯的是 Django 的请求生命周期、URL 解析机制、文件查找策略、生产环境与开发环境的根本性差异。比如
django.template.loader.get_template('home/index.html')
在本地能顺利加载,但线上却抛出
TemplateDoesNotExist
,原因可能不是路径写错,而是
TEMPLATES['DIRS']
没包含你的自定义模板根目录,或者
APP_DIRS=True
导致 Django 只在每个 app 的
templates/
子目录里找,而你把通用模板放到了项目根目录下的
templates/
;再比如
collectstatic
命令执行后,
/var/www/myproject/static/
下明明有
js/main.js
,Nginx 却始终返回 404,那大概率是
location /static/
的 root 指向了错误的物理路径,或是
alias
与
root
的语义混淆导致路径拼接错误。这篇文章不讲“Django 是什么”,也不堆砌官方文档的翻译,而是以一个十年 Django 实战者的真实视角,带你一层层剥开模板与静态文件这两套系统的底层逻辑、典型陷阱和可落地的解决方案。无论你是刚跑通
python manage.py runserver
的新手,还是正为生产环境 CSS 不生效焦头烂额的中级开发者,只要你还在用 Django 构建真实项目,这篇内容就是你绕不开的“通关地图”。
2. 核心设计思路拆解:Django 模板与静态文件为何要“两套独立系统”
2.1 模板系统不是“文件读取器”,而是“编译-缓存-渲染”引擎
很多人初学 Django,会下意识地把模板理解成“HTML 文件 + 变量替换”,这其实是最大的认知偏差。Django 的模板系统本质上是一个
编译型模板引擎
,它的核心流程是:
源模板字符串 → 编译为 Python 字节码 → 缓存字节码对象 → 执行渲染 → 返回 HTML 字符串
。这个过程决定了它和普通文件读取有本质区别。
首先,Django
绝不直接读取
.html
文件并做字符串替换
。当你调用
{% include 'header.html' %}
或
{% extends 'base.html' %}
时,Django 并不是每次请求都去磁盘打开
header.html
,而是先检查内存缓存中是否有该模板的已编译版本。如果没有,它才去
TEMPLATES['DIRS']
和各 app 的
templates/
目录中按顺序查找
header.html
,找到后将其内容读入内存,然后由
django.template.base.Parser
解析器进行词法分析和语法树构建,最终生成一个可执行的
Template
对象,并缓存起来。这意味着,
模板路径的查找顺序、缓存策略、编译错误的提示时机,都和你想象中的“文件路径”完全不同
。例如,如果你在
settings.py
中配置了:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'myproject/templates'],
'APP_DIRS': True,
'OPTIONS': {
'debug': DEBUG,
'context_processors': [...],
},
},
]
那么 Django 查找
header.html
的完整路径顺序是:
-
myproject/templates/header.html(来自DIRS) -
app1/templates/header.html(来自APP_DIRS=True,遍历所有 INSTALLED_APPS) -
app2/templates/header.html - ……直到所有 app 遍历完毕
这个顺序是硬编码在
django.template.loaders.filesystem.Loader
和
django.template.loaders.app_directories.Loader
中的,无法通过简单修改
include
标签来绕过。我曾在一个项目中遇到一个诡异问题:
base.html
在
app1/templates/
下,而
header.html
在
myproject/templates/
下,结果
base.html
里
{% include 'header.html' %}
总是报错。排查半天才发现,因为
APP_DIRS=True
且
app1
在
INSTALLED_APPS
列表中排在最前面,Django 在
app1/templates/
下没找到
header.html
后,就直接放弃了,根本没继续往下查
DIRS
。解决方案不是改
include
,而是把
app1
移到
INSTALLED_APPS
列表末尾,或者更稳妥地——
显式关闭
APP_DIRS=True
,只依赖
DIRS
统一管理所有模板路径
,这样路径逻辑完全可控,也符合大型项目的分层规范。
其次,模板的“继承”机制 (
{% extends %}
) 是一种
编译期行为
,而非运行时文件拼接。
{% extends 'base.html' %}
这行代码在编译阶段就被解析为对
base.html
模板对象的引用,Django 会递归编译
base.html
及其所有
include
的子模板,最终生成一个单一的、可执行的渲染函数。这就解释了为什么你不能在
{% extends %}
里使用变量:
{% extends template_name %}
是非法的,因为
template_name
是运行时上下文变量,而
extends
必须在编译期就确定目标模板。这个限制常被新手误认为是“Django 不够灵活”,实则是为了保证模板编译的确定性和性能。我见过太多人试图用
if
标签动态切换 base 模板,最后不得不改用
include
+ 大量重复代码,反而降低了可维护性。正确的做法是:
将不同布局抽象为不同的 base 模板(如
base_mobile.html
,
base_admin.html
),并在视图中根据条件选择渲染哪个
,这才是符合 Django 设计哲学的解法。
2.2 静态文件系统不是“文件夹映射”,而是“开发-构建-部署”三阶段流水线
如果说模板系统是“编译-缓存-渲染”,那么静态文件系统就是一套完整的“开发-构建-部署”流水线。很多开发者把它简化为“把 CSS/JS 放进
static/
文件夹就行”,这直接导致了生产环境的灾难。Django 的静态文件管理分为三个明确阶段:
第一阶段:开发阶段(
DEBUG=True
)
此时,Django 内置的
runserver
命令会启动一个轻量级的静态文件服务中间件
django.contrib.staticfiles.views.serve
。它的工作原理是:当 URL 匹配
/static/
前缀时,中间件会遍历
STATICFILES_DIRS
和各 app 的
static/
目录,按顺序查找请求的文件(如
/static/css/app.css
)。这个过程是
实时的、无缓存的、纯 Python 的
。好处是开发时改完 CSS 立刻生效,坏处是性能极差,绝对不能用于生产。关键点在于:
STATICFILES_DIRS
是一个
列表
,它定义了“额外的静态文件搜索路径”,而不仅仅是“存放静态文件的地方”。例如:
STATICFILES_DIRS = [
BASE_DIR / "myproject/static",
BASE_DIR / "node_modules/bootstrap/dist",
]
这表示 Django 会在
myproject/static/
下找
css/app.css
,如果找不到,会继续去
node_modules/bootstrap/dist/
下找
css/app.css
。这种设计允许你将第三方库(如 Bootstrap、jQuery)的静态文件直接纳入 Django 的统一管理,无需手动复制。我习惯把所有前端依赖都放在
node_modules/
,然后通过
STATICFILES_DIRS
引入,这样
npm update
后,Django 自动就能用上新版本,省去了手动同步的麻烦。
第二阶段:构建阶段(
collectstatic
)
这是连接开发与生产的桥梁,也是最容易出错的环节。
python manage.py collectstatic
命令的作用,是将所有
STATICFILES_DIRS
和各 app
static/
目录下的文件,“收集”到一个统一的、供生产 Web 服务器(Nginx/Apache)服务的物理目录中,即
STATIC_ROOT
。注意,
STATIC_ROOT
必须是一个空目录
,
collectstatic
会清空它并重新填充。这个命令的执行逻辑是:
-
清空
STATIC_ROOT目录; -
遍历
STATICFILES_DIRS列表,将每个目录下的所有文件(递归)复制到STATIC_ROOT; -
遍历
INSTALLED_APPS,对每个 app,检查其static/子目录,将其中所有文件复制到STATIC_ROOT; -
如果
STATICFILES_STORAGE配置了非默认的存储后端(如ManifestStaticFilesStorage),还会在复制后生成staticfiles.json映射文件,并重命名文件以加入哈希值(如app.a1b2c3d4.css),实现长期缓存。
这里的关键陷阱是:
collectstatic
不会检查文件是否重复或冲突
。如果
app1/static/css/style.css
和
app2/static/css/style.css
同名,
collectstatic
会按
INSTALLED_APPS
的顺序,后出现的 app 覆盖先出现的 app 的文件。我曾在一次紧急上线中,发现线上 CSS 突然变丑,排查数小时才发现是
django-allauth
的
static/css/allauth.css
覆盖了我们自己写的
allauth.css
,因为
django-allauth
在
INSTALLED_APPS
中排在我们自定义 app 后面。解决方案很简单:
永远把你的自定义 app 放在
INSTALLED_APPS
列表的最前面
,确保它们的静态文件优先被收集。
第三阶段:部署阶段(Web 服务器直连
STATIC_ROOT
)
生产环境中,Django
绝不负责提供静态文件
。
runserver
的静态服务中间件会被禁用(
DEBUG=False
时自动禁用),所有
/static/
请求必须由 Nginx 或 Apache 直接处理。这意味着,
STATIC_ROOT
的物理路径,必须精确匹配 Nginx 的
location /static/
配置。例如,如果
STATIC_ROOT = '/var/www/myproject/static/'
,那么 Nginx 配置必须是:
location /static/ {
alias /var/www/myproject/static/;
}
注意是
alias
,不是
root
。
alias
表示将
/static/
这个 URL 前缀
完全替换
为后面的路径,所以
/static/css/app.css
会被映射到
/var/www/myproject/static/css/app.css
。而
root
是
追加
路径,
root /var/www/myproject/static;
会导致
/static/css/app.css
被映射到
/var/www/myproject/static/static/css/app.css
,多了一个
static
,必然 404。这个细节,我见过至少五十个线上故障报告,全是
root
和
alias
混淆导致的。
2.3 为什么必须“两套系统”?—— 分离关注点与安全边界的硬性要求
模板与静态文件之所以被设计为两套完全独立的系统,根本原因在于它们在 Web 应用架构中扮演着截然不同的角色,有着不可妥协的安全边界。
模板的本质是
服务端逻辑
。它包含了
{% if %}
,
{% for %}
,
{% url %}
等控制结构,这些结构在服务端执行,可以访问数据库、调用业务逻辑、生成动态 URL。因此,模板文件必须由 Django 的模板引擎严格解析和沙箱化,防止任意代码执行。如果模板和静态文件混在一起,攻击者上传一个恶意的
.html
文件到
static/
目录,再通过某种方式诱导 Django 去“渲染”它,就可能绕过模板沙箱,执行任意 Python 代码。Django 通过强制分离
templates/
(服务端渲染)和
static/
(纯客户端资源)两个目录,从文件系统层面建立了第一道防线。
静态文件的本质是
客户端资源
。CSS、JS、图片、字体等,全部由浏览器直接下载并执行/渲染。它们不应该、也不能包含任何服务端逻辑。如果把 JS 文件放在
templates/
目录下,Django 会尝试用模板引擎去“渲染”它,这不仅毫无意义,还可能导致 JS 代码被错误地转义(如
<
变成
<
),彻底破坏功能。反之,如果把 HTML 模板放在
static/
目录下,Web 服务器会直接以
text/plain
类型返回其源码,暴露所有业务逻辑和敏感信息。
这种分离,是现代 Web 框架的共识。Flask 有
templates/
和
static/
,Rails 有
app/views/
和
app/assets/
,Express 有
views/
和
public/
。Django 的设计并非标新立异,而是对 Web 安全与工程实践的深刻理解。忽视这一点,强行“合并”或“简化”,无异于拆掉防火墙去修水管。
3. 核心细节与实操要点:从
settings.py
到
collectstatic
的每一个关键参数
3.1
settings.py
中的四大静态文件配置项:
STATIC_URL
,
STATIC_ROOT
,
STATICFILES_DIRS
,
STATICFILES_STORAGE
Django 的静态文件行为,几乎完全由
settings.py
中的四个配置项驱动。它们之间的关系,就像一个精密的齿轮组,任何一个齿磨损,整个系统就会卡顿。
STATIC_URL
:前端世界的“协议+域名+路径”
STATIC_URL = '/static/'
这个配置,
不是告诉 Django “静态文件存在哪里”,而是告诉前端模板“你该去哪里请求静态文件”
。它是一个纯粹的 URL 前缀,用于在模板中生成静态资源的绝对 URL。例如,在模板中写:
<link rel="stylesheet" href="{% static 'css/app.css' %}">
Django 会将其编译为:
<link rel="stylesheet" href="/static/css/app.css">
注意,
STATIC_URL
的值
必须以
/
开头,且通常以
/
结尾
。如果设为
STATIC_URL = 'https://cdn.example.com/static'
,那么
{% static 'css/app.css' %}
就会生成
https://cdn.example.com/static/css/app.css
,这正是对接 CDN 的标准做法。我强烈建议,在生产环境的
settings_prod.py
中,将
STATIC_URL
直接指向你的 CDN 域名,而不是
/static/
。这样,
collectstatic
收集到的文件,会被 Nginx 从本地
STATIC_ROOT
读取,但浏览器看到的 URL 却是 CDN 地址,实现了无缝的动静分离。
STATIC_URL
的值
绝不能
是本地文件系统的路径(如
/var/www/static
),那是
STATIC_ROOT
的职责。
STATIC_ROOT
:生产环境的“唯一真相源”
STATIC_ROOT = BASE_DIR / 'staticfiles'
是
collectstatic
命令的
唯一输出目录
。它必须是一个
绝对路径
,且在
collectstatic
执行前,该目录下
不能有任何与静态文件同名的其他文件或目录
,否则会被清空。这个目录的物理位置,就是 Nginx/Apache 的
location /static/
必须指向的地址。一个常见的反模式是:开发者为了“方便”,把
STATIC_ROOT
设为
BASE_DIR / 'static'
,和
STATICFILES_DIRS
中的某个路径重合。这会导致
collectstatic
在清空
STATIC_ROOT
时,一并删掉了你辛苦写的源文件,造成灾难性后果。我的经验是:
永远为
STATIC_ROOT
创建一个独立的、名字明确的目录,如
staticfiles
或
collected_static
,并确保它在 Git 仓库中被
.gitignore
排除
。这样,
collectstatic
的输出是干净的、可预测的,不会污染你的源码树。
STATICFILES_DIRS
:开发阶段的“多源搜索路径”
这是一个
列表
,定义了 Django 在开发阶段(
DEBUG=True
)查找静态文件的
所有额外路径
。它和
STATIC_ROOT
完全无关,只服务于
runserver
的开发服务器。你可以把它理解为“开发时的静态文件联盟”。例如:
STATICFILES_DIRS = [
BASE_DIR / "myproject/static", # 项目级静态文件
BASE_DIR / "node_modules/bootstrap/dist", # 第三方库
BASE_DIR / "frontend/dist", # Vue/React 构建产物
]
这个列表的顺序至关重要。Django 会按列表索引从 0 到 n 的顺序,依次在每个目录下查找请求的文件。一旦找到,立即返回,不再继续查找。因此,
把你自己的、最高优先级的静态文件目录放在列表最前面
。另外,
STATICFILES_DIRS
中的路径
必须是绝对路径
,相对路径会导致
collectstatic
报错。
BASE_DIR / 'xxx'
是推荐的写法,因为它基于项目根目录,跨平台兼容。
STATICFILES_STORAGE
:构建阶段的“文件处理器”
这个配置项决定了
collectstatic
如何处理和存储文件。默认值是
'django.contrib.staticfiles.storage.StaticFilesStorage'
,它只是简单地复制文件。但在生产环境中,你应该使用
'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
。它的核心能力是:
-
在复制文件时,为每个文件名添加一个基于文件内容的 MD5 哈希值(如
app.css→app.a1b2c3d4.css); -
生成一个
staticfiles.json文件,记录原始文件名与哈希文件名的映射; -
在模板中使用
{% static 'app.css' %}时,Django 会查询staticfiles.json,返回app.a1b2c3d4.css这个带哈希的 URL。
这带来的好处是
永久缓存
。浏览器可以放心地为
app.a1b2c3d4.css
设置
Cache-Control: max-age=31536000
(一年),因为只要文件内容不变,哈希值就不变;一旦你修改了
app.css
并重新
collectstatic
,生成的将是
app.x9y8z7w6.css
,URL 完全不同,浏览器自然会拉取新文件。这彻底解决了“用户浏览器缓存了旧 CSS,页面样式错乱”的经典问题。启用它只需两步:
-
在
settings.py中设置:STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' -
确保
STATIC_ROOT已正确配置,并执行python manage.py collectstatic --noinput
提示:
ManifestStaticFilesStorage会校验staticfiles.json中记录的所有文件是否真实存在于STATIC_ROOT。如果collectstatic执行后,你手动删除了STATIC_ROOT下的某个文件,下次collectstatic会报错,提示ValueError: The file 'xxx' could not be found with staticfiles.。这是因为staticfiles.json还记录着它,但物理文件已不存在。解决方法是先清空STATIC_ROOT,再重新运行collectstatic。
3.2 模板配置的三大核心:
TEMPLATES['DIRS']
,
TEMPLATES['APP_DIRS']
,
TEMPLATES['OPTIONS']['debug']
Django 的模板配置同样集中在
settings.py
的
TEMPLATES
列表中。一个典型的配置如下:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'myproject/templates'],
'APP_DIRS': True,
'OPTIONS': {
'debug': DEBUG,
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
DIRS
:模板的“主战场”
DIRS
是一个
列表
,定义了 Django 查找模板的
最高优先级路径
。它和
STATICFILES_DIRS
一样,遵循“先到先得”的原则。
BASE_DIR / 'myproject/templates'
是一个非常推荐的实践,它将所有项目级的通用模板(如
base.html
,
404.html
,
500.html
)集中管理,避免散落在各个 app 中。
DIRS
中的路径
必须是绝对路径
,且
BASE_DIR / 'myproject/templates'
这种写法比
os.path.join(BASE_DIR, 'myproject', 'templates')
更简洁、更 Pythonic。
APP_DIRS
:模板的“诸侯国”
APP_DIRS = True
表示 Django 应该自动在每个
INSTALLED_APPS
中的 app 目录下,查找
templates/
子目录。这是 Django 的约定俗成,意味着每个 app 都可以拥有自己专属的、与业务强耦合的模板(如
blog/templates/blog/post_list.html
)。
APP_DIRS
的查找顺序,严格遵循
INSTALLED_APPS
列表的顺序。因此,
如果你有一个通用的
blog/templates/base.html
,而另一个
core/templates/base.html
,那么
core
必须在
INSTALLED_APPS
中排在
blog
前面,才能确保
core/base.html
被优先选用
。这个顺序规则,是理解 Django 模板继承链的关键。
OPTIONS['debug']
:模板错误的“放大镜”
'debug': DEBUG
这个配置,直接影响模板错误的显示方式。当
DEBUG=True
时,Django 会显示极其详尽的错误页面,包括:
- 错误发生的精确行号和列号;
- 出错模板的完整源码高亮;
- 上下文变量的完整 dump;
-
所有已加载的模板继承链(
base.html←post_list.html←index.html)。
这极大地加速了本地调试。但当
DEBUG=False
时,Django 会隐藏所有技术细节,只显示一个通用的 500 页面,这是出于安全考虑,防止攻击者通过错误信息窥探你的服务器结构。我见过太多团队在生产环境将
DEBUG=True
,结果
TemplateDoesNotExist
错误页面里直接暴露了服务器的完整文件路径(如
/var/www/myproject/myproject/templates/
),这等于把家门钥匙交给了黑客。
永远、永远、永远在生产环境将
DEBUG
设为
False
,这是 Django 安全的底线。
3.3
urls.py
中的静态文件服务:
static()
与
serve()
的适用场景
在 Django 的 URL 路由中,如何让
runserver
服务静态文件?答案是
django.conf.urls.static.static()
函数。它通常出现在
urls.py
的末尾:
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('myapp.urls')),
]
# 仅在 DEBUG=True 时添加静态文件路由
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
这段代码的含义是:
如果
DEBUG=True
,则为
STATIC_URL
(如
/static/
)这个 URL 前缀,注册一个由
django.contrib.staticfiles.views.serve
视图处理的路由,该视图会从
STATIC_ROOT
目录下查找并返回文件
。
这里有两个关键点需要澄清:
第一,
static()
函数
只在
DEBUG=True
时才应该被调用
。这是 Django 官方的硬性规定。
DEBUG=False
时,
static()
生成的路由是无效的,Django 会忽略它,或者在某些版本中直接报错。生产环境的静态文件,必须由 Nginx/Apache 提供,这是性能和安全的双重保障。
第二,
document_root=settings.STATIC_ROOT
这个参数,
在开发阶段通常是错误的
。因为在开发阶段,
STATIC_ROOT
往往是空的(你还没运行
collectstatic
),而
runserver
应该从
STATICFILES_DIRS
和各 app 的
static/
目录中查找文件。所以,上面的代码其实有一个隐含的前提:
在开发阶段,你并不需要
STATIC_ROOT
有内容,
runserver
会自动忽略
STATIC_ROOT
,转而使用
STATICFILES_DIRS
。
static()
函数在这里的作用,仅仅是“告诉
runserver
,当 URL 是
/static/xxx
时,请用
serve
视图去处理”,而
serve
视图内部的逻辑,会根据
DEBUG
状态,自动选择
STATICFILES_DIRS
或
STATIC_ROOT
作为源。因此,
document_root
参数在开发时形同虚设,但它在
DEBUG=False
时又变得至关重要,因为那时
serve
视图会真的去
document_root
下找文件。这是一个精妙的设计,但也容易让人困惑。
注意:
django.contrib.staticfiles.urls模块提供了一个更“官方”的方式,即path('__debug__/', include('debug_toolbar.urls')),但这主要用于 Django Debug Toolbar,与静态文件服务无关。static()是唯一正确的方式。
4. 实操过程与核心环节实现:从零开始搭建一个可部署的模板与静态文件系统
4.1 项目初始化:创建符合最佳实践的目录结构
让我们从零开始,搭建一个结构清晰、易于维护、可直接部署的 Django 项目。第一步,永远是规划目录结构。一个经过实战检验的、符合 Django 社区共识的结构如下:
myproject/
├── manage.py
├── myproject/ # 项目包 (Project Package)
│ ├── __init__.py
│ ├── settings/ # settings 模块化
│ │ ├── __init__.py
│ │ ├── base.py # 公共配置
│ │ ├── development.py # 开发环境
│ │ └── production.py # 生产环境
│ ├── urls.py
│ └── wsgi.py
├── myapp/ # 应用 (App)
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── views.py
│ └── templates/ # App 级模板,路径为 myapp/templates/myapp/
│ └── myapp/
│ ├── base.html
│ └── index.html
├── static/ # 项目级静态文件源 (STATICFILES_DIRS)
│ ├── css/
│ │ └── app.css
│ └── js/
│ └── app.js
├── staticfiles/ # collectstatic 输出目录 (STATIC_ROOT)
├── templates/ # 项目级模板源 (TEMPLATES['DIRS'])
│ ├── base.html
│ └── 404.html
└── requirements.txt
这个结构的核心思想是 分层与隔离 :
-
myproject/settings/模块化,将公共配置、开发配置、生产配置分离,避免settings.py变成一个臃肿的“上帝文件”。 -
myapp/templates/myapp/是 Django 的标准约定,myapp/子目录是为了防止不同 app 的模板文件名冲突(如blog/templates/blog/post.html和shop/templates/shop/post.html)。 -
static/目录是STATICFILES_DIRS的一部分,存放所有项目级的、手写的 CSS/JS。 -
templates/目录是TEMPLATES['DIRS']的一部分,存放所有项目级的、通用的模板(base.html是所有页面的父模板)。 -
staticfiles/目录是STATIC_ROOT,它 永远为空 ,只在collectstatic时被填充,且 不应被 Git 跟踪 (添加到.gitignore)。
初始化步骤:
-
创建虚拟环境并激活:
python -m venv venv && source venv/bin/activate(Linux/Mac) 或venv\Scripts\activate(Windows) -
安装 Django:
pip install django -
创建项目:
django-admin startproject myproject . -
创建应用:
python manage.py startapp myapp -
创建目录:
mkdir static templates staticfiles -
创建
myproject/templates/base.html:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My Site{% endblock %}</title>
<link rel="stylesheet" href="{% static 'css/app.css' %}">
</head>
<body>
<header>
<h1>My Site</h1>
</header>
<main>
{% block content %}{% endblock %}
</main>
<script src="{% static 'js/app.js' %}"></script>
</body>
</html>
-
创建
myapp/templates/myapp/index.html:
{% extends 'base.html' %}
{% block title %}Home Page{% endblock %}
{% block content %}
<h2>Welcome to the Home Page!</h2>
<p>This is rendered from the <code>myapp</code> app.</p>
{% endblock %}
-
创建
static/css/app.css:
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 20px;
}
h1 {
color: #007bff;
}
-
创建
static/js/app.js:
console.log("App.js loaded successfully!");
4.2
settings.py
配置:模块化与环境隔离
接下来,我们将
settings.py
拆分为模块化配置。首先,创建
myproject/settings/base.py
,存放所有公共配置:
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent # 从 base.py 回溯到项目根目录
SECRET_KEY = 'your-secret-key-here'
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myapp',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'myproject.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # 项目级模板
'APP_DIRS': True, # 启用 app/templates 查找
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'myproject.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
然后,创建
myproject/settings/development.py
:
from .base import *
DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
# 静态文件配置 (开发)
STATIC_URL = '/static/'
STATICFILES_DIRS = [
BASE_DIR / 'static', # 项目级静态文件源
]
# STATIC_ROOT 不需要在开发中设置,runserver 会忽略它
创建
myproject/settings/production.py
:
from .base import *
import os
DEBUG = False
# 生产环境必须设置 ALLOWED_HOSTS
ALLOWED_HOSTS = ['myproject.com', 'www.myproject.com']
# 静态文件配置 (生产)
STATIC_URL = 'https://cdn.myproject.com/static/' # 指向 CDN
STATIC_ROOT = BASE_DIR / 'staticfiles' # collectstatic 输出目录
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
# 使用 Manifest 存储,启用长期缓存
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
# 安全相关设置
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
最后,修改
manage.py
和
myproject/wsgi.py
,让它们加载正确的 settings 模块:
# manage.py
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings.development')
# myproject/wsgi.py
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings.production')
现在,你的项目已经具备了清晰的环境隔离。开发时运行
python manage.py runserver
,生产时运行
gunicorn myproject.wsgi:application --settings=myproject.settings.production
。
4.3 模板继承与静态文件引用:编写第一个可工作的页面
现在,让我们编写视图,将模板和静态文件串联起来。编辑
myapp/views.py
:
from django.shortcuts import render
def index(request):
return render(request, 'myapp/index.html', {'message': 'Hello from Django!'})
编辑 `myapp
371

被折叠的 条评论
为什么被折叠?



