Django模板与静态文件系统原理与部署实战

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 的完整路径顺序是:

  1. myproject/templates/header.html (来自 DIRS
  2. app1/templates/header.html (来自 APP_DIRS=True ,遍历所有 INSTALLED_APPS)
  3. app2/templates/header.html
  4. ……直到所有 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 会清空它并重新填充。这个命令的执行逻辑是:

  1. 清空 STATIC_ROOT 目录;
  2. 遍历 STATICFILES_DIRS 列表,将每个目录下的所有文件(递归)复制到 STATIC_ROOT
  3. 遍历 INSTALLED_APPS ,对每个 app,检查其 static/ 子目录,将其中所有文件复制到 STATIC_ROOT
  4. 如果 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 代码被错误地转义(如 < 变成 &lt; ),彻底破坏功能。反之,如果把 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,页面样式错乱”的经典问题。启用它只需两步:

  1. settings.py 中设置: STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
  2. 确保 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 )。

初始化步骤:

  1. 创建虚拟环境并激活: python -m venv venv && source venv/bin/activate (Linux/Mac) 或 venv\Scripts\activate (Windows)
  2. 安装 Django: pip install django
  3. 创建项目: django-admin startproject myproject .
  4. 创建应用: python manage.py startapp myapp
  5. 创建目录: mkdir static templates staticfiles
  6. 创建 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>
  1. 创建 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 %}
  1. 创建 static/css/app.css
body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    margin: 0;
    padding: 20px;
}
h1 {
    color: #007bff;
}
  1. 创建 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值