作者:Julia Evans · 原文发表于 2026 年 1 月 27 日 · 中文翻译整理。原文链接
我很喜欢开始学习一种从来没用过、却已经存在二十多年的“老派、平淡”技术。想到自己将来可能碰到的问题,已经有人解决过上千次,而我只需要把事情做出来,这种感觉很棒。
我很早就想学 Rails、Django 或 Laravel 这样的流行 Web 框架,却一直没真正开始。几个月前,为了做一个网站,我终于开始学 Django。目前感觉不错,下面是一些简短的笔记。
编辑说明:下文保留作者的第一人称经验。这不是完整部署或安全指南。原文没有注明安装的 Django 版本;编辑核查固定使用 Django 6.0 官方文档,不反推作者实际使用的版本。代码仅做静态审查,未执行或连接服务。
比 Rails 更少一些“魔法”
2020 年,我曾花时间尝试学习 Rails。它很酷,我也真的希望自己会喜欢它,Ruby 社区也很棒。不过,如果把 Rails 项目搁置几个月,回来后我就很难记起该怎么做事。
比如,routes.rb 里写着 resources :topics,仅凭这一行,并不能直接知道 topics 路由在哪里配置。我还得记住约定,或者重新查资料。
能够把项目丢下几个月,甚至几年,再回来继续做,对我非常重要——我的项目基本都这样。Django 对我来说更容易接上,因为很多东西写得更明确。
在这个小型 Django 项目里,除了设置文件,主要就五个文件:urls.py、models.py、views.py、admin.py 和 tests.py。想找别的东西,比如某个 HTML 模板,通常可以在这些文件里看到明确引用。
自带的管理后台
这个项目需要一个后台,让我能手动查看或编辑数据库里的部分数据。Django 内置的管理界面很好用,少量代码就能定制。
下面是其中一个管理类的片段,指定列表显示哪些字段、按哪些字段搜索,以及默认的排序方式:
@admin.register(Zine)
class ZineAdmin(admin.ModelAdmin):
list_display = ["name", "publication_date", "free", "slug", "image_preview"]
search_fields = ["name", "slug"]
readonly_fields = ["image_preview"]
ordering = ["-publication_date"]
这里的 admin、Zine 和 image_preview 都依赖项目里的导入或定义。这是原项目的配置片段,不是独立可运行的完整文件。后台仍需按实际用途设置用户权限,尤其是可修改真实数据的操作。
用 ORM 还挺有意思
以前我的态度是:“ORM?谁需要它?我自己写 SQL 就好了!”但目前我很享受 Django 的 ORM。我尤其喜欢它用双下划线 __ 表达跨关系查询的方式:
(
Zine.objects
.exclude(product__order__email_hash=email_hash)
)
这条查询涉及五张表:zines、zine_products、products、order_products 和 orders。为了让它工作,我只需告诉 Django,订单与商品之间存在一个 ManyToManyField,杂志与商品之间还有另一个 ManyToManyField。Django 因而知道该如何连接这些数据。

当然,我也可以自己写这条查询。不过,product__order__email_hash 写起来短得多,看起来也更容易理解。诚实地说,如果手写 SQL,我可能得想一会儿才能构造好它,因为实际查询除了这些连接,还要做其他事情。
眼下我完全不担心这个项目里 ORM 生成的查询的性能,所以暂时对 ORM 很兴奋。当然,我想以后一定会碰到让我不满意的地方。
编辑注:这是作者对当前小项目的判断,不是 ORM 查询性能的普遍保证。关系名称取决于模型定义;正文给查询表达式补了一对括号,使原文换行片段在 Python 中能够作为表达式书写,没有改变查询意图。复杂排除条件的结果与 SQL 应针对实际模型核对。
自动生成迁移,很省心
ORM 的另一个好处是数据库迁移。
如果我在 models.py 里增加、删除或修改字段,Django 可以为这些变化生成迁移脚本,例如 migrations/0006_delete_imageblob.py。
我想,必要时也可以编辑这些脚本。不过到目前为止,我一直直接运行生成的脚本,没有修改,效果很好,简直像魔法一样。
我开始意识到,容易进行迁移对现在的我很重要:我还在摸索数据该怎样组织,因此经常修改数据模型。
编辑补充:“自动”指 Django 根据模型变化生成迁移文件,并不是保存文件后就自动修改生产数据库。Django 6.0 官方流程区分生成迁移与应用迁移;生成结果仍应审查。删除字段或模型的迁移可能丢失数据,应先备份,在测试环境验证并准备回退。本文没有执行迁移,也不把作者“直接运行”的经历当作生产操作建议。
我喜欢它的文档
我以前有个坏习惯:不读文档。但目前看过的 Django 文档,我都读得很开心。这并非偶然。Jacob Kaplan-Moss 在 PyCon 2011 有一场关于 Django 文档文化的演讲,叫作 Writing great documentation。
例如,模型入门文档列出了使用 ORM 时最重要、最常见的字段。
使用 SQLite
之前运维 Postgres 的一次体验不太顺利,我没能理解系统到底发生了什么。后来,我决定让自己所有的小网站改用 SQLite,情况好多了。我也很喜欢它的备份方式:执行 VACUUM INTO 生成一个文件,再复制这个文件。
我一直参考 这篇在生产环境中使用 Django 与 SQLite 的指南。
我觉得这个网站应该没问题,因为预计一天最多只有几百次写入,远少于 Mess with DNS。后者的写入多得多,一直运行得不错,不过它把写入分散在三个 SQLite 数据库里。
编辑注:上述规模与成功经验属于作者的具体项目,不能只凭“每天几百次写入”推断其他应用能否承受并发高峰。备份文件还应验证可恢复性,并按真实数据的敏感程度控制访问。这里保留命令名称以解释原文,没有提供或执行数据库操作脚本。
邮件等基础功能都带着
Django 很有“开箱即用”的感觉,这一点我很喜欢。需要 CSRF 防护、Content-Security-Policy,或者发邮件,都能在框架里找到相应支持。
例如,我想让开发环境把邮件保存到文件里,避免把真实邮件发给真实的人,结果只需要几行配置。在 settings/dev.py 中写:
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = BASE_DIR / "emails"
然后在 settings/production.py 中配置生产邮件:
import os
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.whatever.com"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = "xxxx"
EMAIL_HOST_PASSWORD = os.environ["EMAIL_API_KEY"]
编辑改动:原文最后一行使用 os.getenv('EMAIL_API_KEY'),这里改为 os.environ["EMAIL_API_KEY"] 并补上导入,让缺少凭据时明确报错,而不是把空值继续传给邮件后端。主机名和用户名保留为原文示例值,必须换成自己的服务配置;不要把真实密钥写入代码或版本库。开发邮件文件可能包含敏感内容,不应公开提供下载,生产环境也要确认加载的是正确的设置文件。
这让我觉得:如果还需要别的网站基础功能,Django 很可能已经内置了一种方便的做法。
版本说明:Content Security Policy 是本文版本核查中需要特别区分的一项。Django 6.0 文档提供内置 CSP 支持;不要把原文这句话直接套用到所有旧版本。具体策略仍须配置,内置支持不等于应用已经获得完整保护。
设置文件还是有点让人发怵
settings.py 仍然让我有点紧张。Django 的设置方式是在文件里定义一组全局变量。我会想:如果把变量名拼错了怎么办?我怎么发现它?假如把 WSGI_APPLICATION = "config.wsgi.application" 错写成 WSGI_APPLICATOIN = "config.wsgi.application" 呢?
我可能已经习惯了 Python 语言服务器提醒我拼写错误,所以在无法依赖这种支持时,会觉得有些不适应。
编辑注:原文的拼错名称是问题示例,不是可采用的配置。生产部署还需单独核对调试开关、允许的主机、HTTPS、密钥和权限等设置;本文没有涵盖这些配置的完整检查。
先记到这里
以前,我还没真正成功地用一个 Web 框架完成过项目。目前几乎所有网站,要么是一个 Go 二进制程序,要么是静态站点,所以我很想看看这次会怎么样。
还有很多东西要学。Django 的表单验证和认证系统,我都还没有深入了解。
感谢 Marco Rogers 说服我给 ORM 一个机会。原博客也仍在试验通过 Mastodon 收集评论的方式,邀请读者分享自己最喜欢的 Django 功能。
原文版权归 Julia Evans。编辑增补、版本界限和代码调整均已明确标注,不代表作者新增观点。












暂无评论内容