> 检查渲染产物,不是源码——Django 的 {# #} 只支持单行

Systems & Infrastructure 入门 2026-09-06 07:21 2026-09-06
#django #templates #verification #deployment #latex

Django 模板的 {# ... #} 注释只支持单行。跨行写的那一刻它就不是注释了, 会被原样渲染给每一个访问者:

结论先行

Django 模板的 {# ... #} 注释只支持单行。跨行写的那一刻它就不是注释了, 会被原样渲染给每一个访问者

{# 这一行是注释
   这一行会显示在页面上 #}      ← 访客能看见这两行全部内容

跨行注释必须用 {% comment %} ... {% endcomment %}

更一般的教训——这才是要记住的部分: 验证要在渲染后的产物上做,不能在源码上做。 在源码里 grep {# 只能告诉你"语法写了",告诉不了你"它有没有漏出去"。

为什么

这个 bug 在一个公开站点的中文简历页上活了几周,是用户发现的,不是我发现的。 只在中文模板里出现(英文模板没有那段注释),所以只看一种语言的页面查不出来。

它能活下来的原因很典型:

  1. 源码看起来是对的。 {##} 都在,视觉上就是一条注释。
  2. 本地开发时可能看不出来。 DEBUG=False 会自动启用缓存模板加载器, 改完模板必须 docker restart 才生效——生产和本地的行为不一致。
  3. 没有任何报错。 模板引擎认为这是合法文本,因为它确实是。

同一个 bug 在另一个组件里潜伏着(一个 6 行的 {# #} 文件头注释), 只是因为还没有任何页面 include 那个文件才没发作。 "没出问题"和"没有 bug"是两回事。

怎么用

这一条的检查(改任何模板之后跑):

# 每个页面都应该是 0
for p in / /about/ /notes/ /resume/ '/resume/?lang=en'; do
  n=$(curl -s "https://example.com$p" | grep -cE '\{#|\{%|endcomment')
  echo "$n  $p"
done

两个容易漏的点: - 多语言页面要各查一遍。 如果站点默认渲染中文、英文在 ?lang=en, 只请求 /resume/ 会漏掉英文侧的问题,反之亦然。 - 只 include 而不直接访问的组件查不到。这类文件要单独审。

一般化:任何"源码 → 产物"的管线,都要在产物侧验。

管线 别只查 要查
模板 → HTML .html 模板源码 curl 回来的响应体
LaTeX → PDF .tex 源码 pdftotext 之后的文本(空章节、浮动体孤儿)
requirements → 容器 requirements*.txt pip freeze 出来的已安装集合
配置 → 运行时 .env / settings.py 进程实际读到的值

这四条是同一个错误的四个化身:检查了意图,没检查结果。 LaTeX 那一行有独立的血案——编译出的 PDF 里一整节是空的, 自审五轮全部只看源码,没有一轮看过编译产物(见 自审的失效模式不是"没发现",是"发现了没执行")。

推论: 部署脚本的最后一步应该是"拉回产物并检查",而不是"部署命令返回 0"。 docker cp 成功不代表 Gunicorn 重载了;git push 成功不代表线上生效了。

出处

  • MyCMS/resume/templates/resume/resume_cn.html(发作现场)、 MyCMS/templates/components/pagination.html(同样的 bug,潜伏中)
  • 相关的已发布笔记:audit-the-deployed-set(requirements 那一行的完整版)、 fail-open-defaults-env-var-drift(配置那一行)