Django实战入门:从MTV架构到ORM与Admin后台的完整Web开发指南
做 Web 开发的人一定听过这句话Django 是 Python 世界里最能“开箱即用”的 Web 框架甚至有人直接叫它“瑞士军刀”。我第一次听到这个比喻时心里其实打了个问号——一套框架功能再多也不至于用“军刀”来形容吧。后来自己从零搭了一个带用户登录、数据管理后台、表单提交和简单接口的小站才真正理解了这句话Django 不是帮你把代码写得更快而是把每个 Web 项目都必须做的“地基工程”直接预制好了你不用每次从挖坑开始。这篇指南写给刚啃完 Python 基础、想立刻上手做真东西的人也写给那些被轻量框架的自由散漫折磨过、想要一套明确规范和成熟方案的人。我会按从“安装环境”到“项目跑通”再到“上线准备”的真实路线来讲尽量把关键原理和踩坑细节一次说透。1. 先把“为什么要用Django”这件事想清楚1.1 每个Web项目都要重复做的脏活Django替你包了开发一个网站业务逻辑可能只占三分之一剩下三分之二是绕不开的基础设施你要把 URL 对应到某个处理函数你要连接数据库把表结构和 Python 代码互相翻译你要处理表单提交防止用户乱传数据你要做用户登录、权限控制防止别人乱改数据你还要写一个后台方便运营人员不碰代码也能维护内容。这些事情任何一个项目都躲不掉而且每做一个新项目都要重新设计一遍风格还不一定统一。Django 的策略是把这些公共问题全部内置用官方推荐的方式一次性解决。路由、ORM、表单校验、用户认证、Admin 后台、国际化、安全防护这些在别的框架里要靠拼插件解决的模块在 Django 里是标准配置。这也解释了它为什么被叫“瑞士军刀”——不是因为它什么功能都有而是因为它把常用工具整合到了一个手柄里随拿随用不用到处翻箱子。初学者的一个常见误区是“先学轻量框架打基础再学 Django”。但实际很多人的路径是学了轻量框架感觉什么都要自己装不知道哪些该装、哪些不该装装了一堆插件之后踩了一堆兼容性的坑。你需要的恰恰是先有人告诉你“正规军通常怎么干”。Django 就像一个已经把行军路线画好的向导你跟着走就行至于为什么这条路最顺走完几遍自然就明白了。1.2 “瑞士军刀”背后其实是严格分工有人把 Django 理解成“功能全堆在一起”其实它的核心竞争力是分工明确官方把这套分工叫做 MTV 架构Model 管数据Template 管页面展示View 管业务逻辑URLconf 负责把请求分发给对应的 View。这套东西以前在桌面开发里叫 MVC但 Django 里的 View 和 MVC 里的 Controller 更像所以官方干脆用了 MTV 这个名字。很多新手都会在这里绕一下我当初也困惑过“View 到底管什么”。简单记就是浏览器请求进来View 决定要查什么数据、做什么处理、最后渲染哪个模板它才是整个请求流程里的调度中枢。分工明确带来的直接好处是项目一大了你不会迷路。模型改动去 models.py页面长什么样去 templates 文件夹接口行为去 views.pyURL 映射去 urls.py。哪怕三个月没碰这个项目重新捡起来也能快速定位问题。规范本身就是框架送给你的最大礼物。1.3 哪些项目其实不适合用它瑞士军刀也有不便之处工具多意味着体积大。Django 默认启动就带了一堆中间件和应用一个最小站点的初始化也会比轻量框架重不少。如果项目场景是一个纯展示型网页、一个高性能实时接口、一个只想写 50 行的内部小工具那确实没必要上 Django。轻量框架配合少量依赖会更轻快。但话说回来Django 的“重”主要重在学习路径和启动开销上真做复杂业务时反而是这些内置能力帮你省了成千上万行代码。所以我的建议是只要你的目标是做一个完整 Web 应用比如包含用户系统、数据管理、多个页面Django 就是性价比非常高的选择。尤其是新手规范化的约束会逼你养成好习惯少走很多弯路。2. 环境准备与第一个项目把启动流程完整跑通一次2.1 先选好 Python 和 Django 的版本组合我每次带新人入门第一件事永远是确认版本匹配。Django 对 Python 版本有明确要求装错版本会出现一些莫名其妙的报错比如某些语法不支持、某些库导入失败。实际操作时我建议先给 Django 一个大版本区间不要直接装最新正式版也不要用太老的版本。Python 版本推荐的 Django 大版本使用建议3.8 / 3.94.2老项目兼容性好长期支持版本适合接手旧代码3.10 / 3.115.0 / 5.1新特性更多适合刚入门直接学新版本3.12 及以上最新稳定版追求新特性但要留意第三方库是否跟上安装时建议用固定范围的方式方便以后升级pip install Django4.2,5.0装完验证一下python -c import django; print(django.get_version())能打印出版本号说明环境这关过了。2.2 虚拟环境最便宜的一道保险第一次接触 Django 的人最容易忽略虚拟环境直接在全局环境里pip install django就开干了。短时间没问题但一旦你同时开发两个项目一个用 Django 4.2一个要用 Django 5.x全局环境就会开始互相打架。创建虚拟环境很简单python -m venv venv # mac / Linux 激活 source venv/bin/activate # Windows 激活 venv\Scripts\activate激活之后命令行前面会多一个(venv)这时候再安装依赖就都装进这个独立环境里了。我自己的习惯是每个项目都建一个虚拟环境然后把依赖清单导出到 requirements.txt 里这样换机器、换服务器时一条命令就能恢复一模一样的运行环境。这一步看起来多花了一分钟实际上省掉的是后面几小时的排错时间属于入门必做的“最便宜的保险”。2.3 startproject 和 startapp先分清项目与应用的边界环境准备好之后创建项目django-admin startproject mysite cd mysite python manage.py startapp notes这里有一个非常容易混淆的概念项目和应用的边界。项目是一个站点的全局配置中心包含 settings.py、urls.py、wsgi.py 这些文件应用则是具体业务模块比如博客、论坛、留言板。一个项目可以包含很多应用每个应用各自有独立的 models.py、views.py、templates 目录。你会得到一个类似这样的目录结构mysite/ manage.py mysite/ settings.py urls.py wsgi.py notes/ models.py views.py admin.py migrations/记住一句话项目是配置的自留地应用是功能的组装车间。只要是新加一个功能模块就新建一个 app不要什么功能都往同一个 app 里塞否则项目很快就会变成一锅粥。还有一个新手必踩的坑新建完 app 之后一定要去 settings.py 的INSTALLED_APPS里把应用注册进去否则 Django 完全不知道这个 app 存在后面 makemigrations 会说找不到模型、模板也可能加载不上。INSTALLED_APPS [ # Django 自带应用 django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, # 你自己的应用 notes, ]2.4 第一次 runserver 的验证清单启动开发服务器python manage.py runserver看到这行提示就算成功Starting development server at http://127.0.0.1:8000/用浏览器访问http://127.0.0.1:8000/能看到一个火箭升空页代表项目整体跑通了。不过看到火箭页只是说明 Django 能响应请求了要确认环境真正没问题我建议做三件事执行python manage.py check看有没有配置层面的警告。执行python manage.py createsuperuser创建管理员账号。启动后访问/admin/能正常打开登录页并登录进去说明数据库和自带应用都正常。我见过不少新人栽在这一步runserver 能跑但一访问 /admin/ 就报错最后发现是数据库初始化没做执行一次 migrate 就好了。这也侧面说明Django 自带的东西确实多多到你刚启动就有一整套后台等着你。3. MTV核心机制拆解数据、逻辑和页面是怎么串起来的3.1 一次访问的完整路径理解 Django 最好的方式是跟着一个请求走完它的生命周期。以访问一个笔记列表为例浏览器请求http://127.0.0.1:8000/notes/。Django 根据 settings.py 里的ROOT_URLCONF找到根 urls.py。根 urls.py 里include(notes.urls)把请求继续转发给应用内的 urlconf。urlconf 匹配到path(, views.note_list)调用对应的视图函数。视图函数通过 ORM 向数据库查询笔记对象。查询结果传给模板模板渲染成 HTML 字符串。Django 把 HTML 封装成 HttpResponse 返回浏览器。整个过程看起来很复杂但逻辑线非常清晰URL 找视图视图查数据模板画页面。无论以后项目做多大都逃不出这条主干。3.2 ORM 模型不要把对象当SQL隐身衣Django 的 ORM 让你可以用 Python 类来描述数据库表。比如定义一个笔记模型from django.db import models class Note(models.Model): title models.CharField(max_length200) content models.TextField() created_at models.DateTimeField(auto_now_addTrue) def __str__(self): return self.titleCharField表示变长字符串TextField表示长文本DateTimeField(auto_now_addTrue)会在数据创建时自动写入当前时间。每个字段类型对应数据库里的一个列类型Django 会在迁移时自动处理转换。ORM 最大的价值是把“建表—增删改查”这些高频操作变成对象操作。你写Note.objects.filter(title__icontainsdjango)就能完成一条带模糊查询的 SQL 查询不用手拼字符串也就少了很多 SQL 注入风险。但我要提醒一句ORM 不是让你忘掉 SQL。遇到复杂查询、性能瓶颈、数据量大的报表时你仍然需要知道 SQL 在背后做了什么。我见过一些人把 ORM 用的风生水起但完全看不懂慢查询日志结果线上接口越跑越慢还找不到原因。正确的态度是用 ORM 写出干净的常规操作用 SQL 思维去分析优化异常情况。3.3 URLconf 和视图函数视图先行类视图是进阶在应用里配置 URL 映射# notes/urls.py from django.urls import path from . import views urlpatterns [ path(, views.note_list, namenote_list), path(int:pk/, views.note_detail, namenote_detail), ]int:pk是路径转换器Django 会把 URL 里的数字部分转成整数作为参数传给视图函数。对应的视图# notes/views.py from django.shortcuts import render, get_object_or_404 from .models import Note def note_list(request): notes Note.objects.all().order_by(-created_at) return render(request, notes/list.html, {notes: notes}) def note_detail(request, pk): note get_object_or_404(Note, pkpk) return render(request, notes/detail.html, {note: note})get_object_or_404是我很喜欢的一个工具函数查询到了就返回对象没查到就自动返回 404 页面。这样你不用写一堆 try/except。URL 匹配有一个特点从上往下逐个试第一个匹配成功的就生效。所以规则要按“从具体到模糊”排列避免前面的通用规则把后面的详情页拦截了。函数视图适合入门逻辑简单直观当你需要大量“列表页、详情页、创建页”这类重复模式时类视图能帮你省掉不少样板代码但那是进阶阶段的事入门先把函数视图吃透。3.4 模板语言的三个符号Django 模板是 HTML 加一些特殊标记。三个符号各有分工{% %}用于逻辑控制比如 for、if、block。{{ }}用于输出变量或表达式。{# #}用于注释模板渲染时会被忽略。一个典型的列表模板{% for note in notes %} h3{{ note.title }}/h3 p{{ note.created_at|date:Y-m-d }}/p {% empty %} p还没有笔记/p {% endfor %}{% empty %}是 for 循环的一个便捷分支当列表为空时显示它下面的内容。竖线是过滤器date:Y-m-d能把时间对象格式化成人类可读的日期。Django 内置了几十个过滤器常用的有truncatechars截断文字、default兜底显示、length计算长度。模板里只放展示逻辑业务逻辑一律放视图这是保持项目整洁的底线。3.5 Admin 后台被新手低估的“免费数据库管理面板”Django 自带的 Admin 后台是它最有吸引力的特性之一。创建超级用户后在notes/admin.py里注册模型from django.contrib import admin from .models import Note admin.site.register(Note)然后访问/admin/登录就能看到一个可以直接增删改查笔记数据的后台界面。搜索、筛选、分页都是内置的你一行前端代码都不用写。很多新手把这个后台当作“演示工具”看看就算了其实它是个调试利器。开发阶段通过后台手动造一些数据可以立刻验证前端页面是否按预期展示不用每次都去敲数据库命令。甚至可以配置每个字段在列表页是否可编辑、详情页以什么控件显示。我后来的不少个人项目运营后台干脆就直接基于 Admin 二次开发省了一大半时间。4. 别光看跟我做一个能增删查的笔记应用4.1 定需求全链路跑通比炫技重要先把需求定小一点一个笔记列表页、一个笔记详情页、一个新增表单、一个能管理数据的 Admin 后台。不涉及编辑、删除、用户系统但“增查”这条链路完整跑通已经能覆盖 Django 90% 的基础知识点。项目目录里已经建好notes应用并注册过了。接下来从模型开始一路把页面接上。4.2 从模型到数据库两条命令搞定建表模型代码写好后执行python manage.py makemigrations notes python manage.py migrate第一步是根据模型变化生成迁移文件第二步是把迁移落实到数据库。迁移文件相当于数据库结构的版本控制放在notes/migrations/目录里以后可以从零重建整个数据库结构。为什么分两步因为 makemigrations 生成的文件是可读的 Python 代码你可以 review 它到底要对数据库做什么操作避免误改。生产环境部署时通常只需要在服务器上执行 migrate因为迁移文件已经由开发者在代码库里写好。我见过很多新手每次改模型都只跑 migrate 不跑 makemigrations结果报错说没有变更或者每次改完直接删掉 migrations 文件夹后来数据库重建时全靠猜。记住这个节奏改模型 → makemigrations → 检查迁移文件 → migrate。4.3 写视图和模板三件套一次打通列表视图我们已经写好了新增一个创建页视图def note_create(request): if request.method POST: title request.POST.get(title, ) content request.POST.get(content, ) if not title: return render(request, notes/create.html, {error: 标题不能为空}) note Note.objects.create(titletitle, contentcontent) return redirect(note_detail, pknote.pk) return render(request, notes/create.html)这里有个重要习惯POST 处理结束后要redirect不要直接返回渲染结果。因为如果用户刷新页面浏览器会重新提交上一次的 POST 请求导致重复创建。重定向后浏览器会变成 GET 请求刷新只是重新获取页面不会重复提交。这是 Web 开发里经典的“POST 后重定向”模式。模板放在notes/templates/notes/目录下。Django 规定要在应用下建 templates 文件夹再建一层和应用同名的目录这是为了防止多个应用的模板文件重名冲突。列表页和详情页按上面说过的模板语法写新增表单页是form methodpost action/notes/new/ {% csrf_token %} input typetext nametitle placeholder标题 textarea namecontent placeholder内容/textarea button typesubmit保存/button /form4.4 表单数据和 CSRFPOST 里最常见的安全课注意看表单里那个{% csrf_token %}这不是摆设。CSRF 攻击是指恶意网站诱导用户向你的站点发起请求比如在别的页面藏一个自动提交的隐藏表单。没有防护的话用户无意间就可能在你这里创建数据甚至删数据。Django 的防护机制是渲染表单页面时生成一个一次性随机令牌随 Cookie 和隐藏字段一起返回浏览器提交时后端校验令牌是否有对应关系对不上就返回 403。所以你只要写 POST 表单就必须带这个标签这也是新手最容易遇到的 403 报错根源。另外request.POST.get(title)里这个title不是随便取的它对应表单里nametitle的属性。如果表单里没写 name后端就永远取不到值。这个低级问题排查起来特别费时间因为页面显示正常什么报错也没有就是提交完数据是空的。4.5 用 Admin 验证整条链路把模型注册进 Admin访问/admin/随便加几条笔记再回到列表页刷新应该能看到数据出现。此时你实际上已经跑通了完整的 MTV 链路数据存在数据库里Admin 能读写它列表视图能查它模板能展示它。我建议你在这个阶段故意做几个实验会事半功倍在 Admin 里删掉一条笔记然后访问它的详情页看看 404 长什么样提交一个空标题看看错误提示是否显示在模板里故意变量名写错看看 Django 是报错还是静默显示空白。这些操作能帮你建立对框架行为的直觉比单纯背文档有用得多。5. 入门期最容易翻车的四个坑附完整排查思路5.1 静态文件 404DEBUGTrue 的时候也可能翻车开发环境里Django 开发服务器会自动处理静态文件但前提是你的文件放对了位置。应用级静态文件要放在notes/static/notes/这样的目录里然后在模板里加载{% load static %} link relstylesheet href{% static notes/style.css %}不少新手把 CSS 直接放在项目根目录或者 templates 里然后发现样式就是不生效。一个典型的排查路径是先在浏览器里访问静态文件的 URL比如http://127.0.0.1:8000/static/notes/style.css如果 404说明文件路径和 Django 找静态文件的规则对不上如果 200说明路径没问题问题在模板引用写错。这里有个容易混淆的点开发服务器能直接服务静态文件但生产环境不行。上线时必须执行collectstaticDjango 会收集所有应用里的静态文件到一个统一目录再由 Web 服务器来服务。这个机制本身不复杂但很多项目都是开发环境一切正常一上线样式全挂就是这个环节没配合好。5.2 时区那 8 小时TIME_ZONE 与 USE_TZ 的分工Django 默认的TIME_ZONE UTC而很多本地用户在中国时区。如果不去改配置你在 Admin 里创建的数据时间看起来会比实际时间慢 8 个小时。这两个配置的关系是USE_TZ True表示数据库统一存 UTC 时间展示时再转换成本地时间。TIME_ZONE则指定你所在时区。所以正确做法是TIME_ZONE Asia/Shanghai USE_TZ True这样数据库里依然是标准的 UTC 时间但 Admin 和模板显示时会自动转为本地时间避免国际化和跨时区项目的时间错乱。这里还牵扯到 Python 的基础知识datetime 对象有带时区和不带时区两种状态Django 在高版本里对不带时区的时间写入数据库会直接报错。所以你在自定义代码里生成时间时建议用django.utils.timezone.now()而不是裸用datetime.now()它会自动返回带时区信息的时间对象。5.3 makemigrations、migrate 与“我改了模型但表没变”最常见的场景是改了模型的字段执行 makemigrations结果提示No changes detected。第一反应别慌先检查INSTALLED_APPS里有没有注册这个应用。我曾经在这个坑里折腾了一个小时最后发现是新建了 app 之后忘记注册Django 压根没把你的模型纳入监控。另一个场景是列表页突然报错no such column: notes_note.is_pinned但数据库文件里明明有这张表。原因是你改了模型加了字段makemigrations 和 migrate 没执行。这种错误有个特点报错信息会直接指出表名和列名非常明显。还有一个容易踩的坑误删了 migrations 目录后migrate 报错说迁移状态不一致。这时候不要想着手工去改数据库正确做法是生成一个初始迁移文件重新对齐。migrate --fake确实存在但它会把当前数据库结构标记为已迁移而不实际执行 SQL只适合在你非常清楚自己在干什么的时候用。新手下手前一定想清楚这个命令是把双刃剑。5.4 一次真实 500 的排查链路不要只盯着最后一行报错我举个例子大概是大部分人都遇过的场景。某天你在笔记模型的__str__方法里返回了 title 字段然后给列表页模板加了一个{{ note.title|upper }}结果页面突然 500 了。初学者看到页面白屏第一反应是回终端看最后一行报错但很多时候最后一行只是“异常被抛出”的结果真正的原因在上面几行。完整的排查链路应该是看终端的完整 traceback从最上面开始读通常第一二行里就有具体的异常类型和出错文件。执行python manage.py check排除配置层面的低级错误。执行python manage.py showmigrations notes确认所有迁移都已应用。如果异常涉及某个字段打开数据库或 Admin确认该条数据到底有哪些字段值。修复后用浏览器重新访问该页面验证。我刚入门时喜欢“看到报错就改一行代码再刷新”结果经常越改越乱。后来养成的习惯是报错来了先不动手花三分钟把 traceback 完整读一遍。其实大部分报错信息已经把解决方案写得很明白了比如no such column、Reverse for xxx not found你只需要沉住气读完。常见现象最可能的原因处理方式页面 404应用未注册或 URL 未 include检查 INSTALLED_APPS 和根 urls.py表单提交 403缺少 csrf_token模板里加上 {% csrf_token %}数据时间差 8 小时TIME_ZONE 仍为 UTC修改 settings.py 时区配置migrate 提示无变化应用没注册进 INSTALLED_APPS注册后重试新增字段后页面报错模型改了但没迁移依次执行 makemigrations、migrate模板变量为空变量名拼错或名字不匹配检查视图 context 字典和模板引用6. 从 runserver 到上线把 DEBUG 关掉才算真正学会部署6.1 DEBUGFalse 的第一分钟会遇到什么开发环境下你把DEBUG True开到地老天荒都没事但生产环境必须关闭。关闭的那一瞬间你会遇到三件事ALLOWED_HOSTS必须设置成你的域名或 IP否则 Django 直接拒绝访问并报 400。页面出错时不再显示详细报错和设置信息而是输出一个通用的 500 页面。开发服务器停止服务静态文件样式和图片全部失效。这几件事不亲身经历一遍很难理解 Django 为什么这么设计。DEBUG True时Django 会把你服务器的配置信息、环境变量、代码片段全部展示在报错页面上这在生产环境等于把家门钥匙送给攻击者。所以官方反复强调生产环境必须 DEBUGFalse并要求设置ALLOWED_HOSTS白名单。一个实用的配套习惯是把敏感配置放到环境变量里export DJANGO_DEBUGTrue export DJANGO_SECRET_KEYyour-secret-key然后 settings.py 里读取import os DEBUG os.environ.get(DJANGO_DEBUG, True) True SECRET_KEY os.environ[DJANGO_SECRET_KEY] ALLOWED_HOSTS os.environ.get(DJANGO_ALLOWED_HOSTS, ).split(,)这样代码仓库里不会出现真实的密钥换环境部署也只是改环境变量不用改代码。6.2 静态文件收集与服务器组合关闭 DEBUG 后第一步是配置静态文件。在 settings.py 里设置STATIC_URL /static/ STATIC_ROOT BASE_DIR / staticfiles然后执行python manage.py collectstatic这一步会把所有应用和第三方包的静态文件复制到STATIC_ROOT指定的目录。接下来需要一个 Web 服务器来处理 /static/ 下的文件请求同时用 WSGI 服务器运载 Django 应用本身。入门阶段不需要一步到位上微服务架构一台普通的云主机就能跑反向代理服务处理静态文件和外部请求转发WSGI 容器负责执行 Django 代码。跑这种组合最核心的验证方式是关闭 DEBUG、完成 collectstatic、用正式域名访问静态文件样式正常、管理后台能登录部署这关就算及格了。另外一个常见误区是有些人上线后用runserver --insecure硬扛这相当于给生产环境留了一个性能极差、行为不稳定的运行方式。runserver 只用于开发环境这是 Django 文档里写了无数次的话真的别在线上图省事。6.3 上线前的安全底线SECRET_KEY、数据库与 HTTPS部署前有几条底线踩了任何一条都可能出事绝对不要把 SECRET_KEY 提交到公开仓库。它被泄露后攻击者可以伪造会话、篡改数据。换新料的命令是python -c from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())。生产数据库不要用默认的 SQLite。SQLite 适合开发和小流量场景真实项目建议切换为主流关系型数据库。切换方法不复杂安装对应数据库驱动、把 settings.py 里数据库配置换成新的连接信息、重新执行迁移。整个访问链路要走 HTTPS。反代服务器配置好证书后Django 侧可以在 settings.py 里启用相应的安全头配置强制浏览器通过安全连接访问。不要在生产环境开着 Debug 工具栏、不要透露版本号信息、给 Admin 后台换一个不那么容易猜的路径。这些属于安全配置的入门清单做不到十全十美但底线必须先守住。6.4 入门之后下一步可以按这个顺序补课如果你能把上面的笔记应用完整跑通Django 入门这几关就算过了。接下来的学习路线我建议按依赖顺序来把 Django Forms 和 ModelForm 学熟替代手写字段解析表单校验会清爽很多。学习类视图尤其是 ListView、DetailView、CreateView它们能大幅压缩列表页和详情页的样板代码。研究用户认证与权限Django 自带的 User 模型配合登录、登出、权限装饰器能覆盖大部分业务需求。引入单元测试用 Django 的测试客户端模拟请求保证每次改完代码不把老功能弄坏。接触部署自动化把 collectstatic、迁移、重启服务这些步骤变成一条命令。如果后面要做前后端分离的接口服务再去补充接口序列化和身份认证方案。我见过太多人止步于“跟着教程跑通了”却从没独立从零写过一个项目。入门学习里最宝贵的一步就是关掉教程从一张白纸开始给自己设计一个小项目哪怕只是一本个人账本、一个收藏清单只要全链路自己做一遍你学到的会超过前面所有教程的总和。最后分享一个我自己的体会Django 入门最大的门槛不在语法而在“不要急着写代码”。先理解 MTV 之间怎么传数据先在 Admin 里把数据建出来再一点点把页面接上你会发现框架帮你省掉的远比你想的还多。还有一个保命习惯我一直在用每次改完模型立刻跑一遍 makemigrations再跑 migrate改动字段前先看开发环境的表现确认无误再上生产。这套习惯陪我避开了太多低级事故希望你也能尽早养成。