资讯动态

Django二次作业复盘:模型、视图、模板与CRUD实战指南

发布时间:2026/10/14 21:55:10 来源:尧图企业网站定制
拿到260316 Django 第二次作业这个题目时我一开始还有点轻视——想着第一次作业都熬过来了第二次无非是照着教程再敲一遍代码。真正动工之后我才发现这次作业的跨度比想象中大多了第一次作业还停留在视图返回一段字符串、模板渲染一个静态页面的阶段第二次就直接要求跟数据库打交道了模型、表单、增删改查、页面跳转全部要串起来。这篇文章就是我完整做完这次作业后的复盘把从拆解题目、搭建环境、编写模型到最终提交前自测的整个链路都过一遍重点聊聊那些文档里不会写、但实际做起来一定会踩的坑。如果你也正在做类似的Django作业或者刚学完Django基础想找一个完整的练手项目这篇应该能帮你省下不少弯路。1. 先拆作业这次到底要我们掌握什么拿到作业题目之后我做的第一件事不是开终端建项目而是把题目要求从头到尾读了三遍把所有动词圈出来。你会发现所谓的第二次作业其实是一个从会写页面到会做系统的过渡考核表面上的功能要求就那么几条但背后隐藏的考点非常多。1.1 从作业的显性需求看隐性考点我这次拿到的题目要求大致是实现一个信息管理的小系统包含数据录入页面、列表展示页面、详情页面还要支持数据的修改和删除。第一次作业如果是把页面做出来第二次作业就是把数据玩转。这里最关键的隐性考点有三个我觉得值得单独说一下第一是路由设计。不再是简单的path(index/)而是要考虑URL的命名、参数传递比如详情页的int:pk、以及多个页面之间怎么互相跳转。很多同学在这里会犯一个错误——直接把路径写死在模板里比如a href/detail/1/一旦路由改了所有页面全部失效。第二是CSRF校验机制。这是一个让新手崩溃的坎——明明表单填得好好的一提交就报403然后一脸茫然地到处搜403 Forbidden怎么办。其实这就是Django在保护你它要求所有POST请求必须携带一个由服务器生成的随机token防止跨站伪造请求。在模板表单里加一行{% csrf_token %}就能解决但如果不理解原理出错了就只能瞎猜。第三是QuerySet的求值特性。models.Book.objects.all()这一行代码执行的时候其实并没有真正去数据库里查——Django使用的是懒加载机制真正查库的时机是你开始遍历这个结果集或者对它做判断的时候。这个特性平时没感觉但等你后面做条件筛选、分页、排序的时候就会体会到设计API的讲究。1.2 把作业需求翻译成功能清单拆完考点以后我习惯把作业要求翻译成一张功能清单每一行都是可以验收的具体行为。比如在首页可以看到所有数据记录的列表每条记录显示标题和发布时间点击某条记录的标题可以进入详情页看到完整内容在录入页填写表单并提交数据能保存进SQLite数据库提交之后页面跳转到列表页并且新数据出现在列表里列表页每条记录后面有编辑和删除按钮编辑页面能回填原有数据保存后更新数据库删除操作需要二次确认避免误删把需求拆成这样之后你会发现这本质上就是一个对某个实体的CRUD增删改查系统只是规模比较小。这样想后面写代码的时候就有方向了——写模型的字段、写视图的各个函数、写模板的各个页面全都是围绕这张清单展开的不容易漏功能。2. 项目初始化五分钟建项目半小时调环境很多同学这次作业最容易翻车的地方反而不是写业务代码而是最开始的初始化环节。我这次用Django从头建项目的时候就连续踩了好几个环境相关的坑这里集中复盘一下。2.1 用虚拟环境隔离依赖别直接装全局第一次做Django作业的时候我贪图省事直接pip install django装在系统全局环境里。后来要装其他包的时候莫名其妙就把某个依赖版本搞坏了Django项目跑起来各种报错。所以这一次我老老实实建了虚拟环境python -m venv venv # Windows激活方式 venv\Scripts\activate # mac/Linux激活方式 source venv/bin/activate激活之后命令行前面会多一个(venv)前缀这是最直观的提示。虚拟环境的作用就是给这个项目一个独立的Python解释器和独立的包目录你在里面装什么版本都不会影响到机器上其他项目。装Django的时候我建议指定版本不要装最新的就走pip install django4.2.*这里解释一下为什么选4.2而不是5.x——4.2属于LST版本长期支持版官方维护时间更长教程和网上的解决方案也最多。对写作业来说稳定性和资料丰富度比尝鲜重要得多。2.2 settings.py 里三处最容易漏改的配置项目建好以后拿到手的第一件事是改settings.py。默认配置对中文环境极不友好有三处我每次都会先改掉LANGUAGE_CODE zh-hans # 默认是 en-us不改成这个的话Django自带页面全英文 TIME_ZONE Asia/Shanghai # 默认是 UTC不改成这个的话时间显示会差8小时 USE_TZ True # 数据库存UTC时间展示时转本地时间LANGUAGE_CODE和TIME_ZONE比较好理解我想多聊两句USE_TZ。Django默认开启了时区支持这意味着当你往DateTimeField字段里存数据时如果存的是一个不带时区信息的朴素时间Django会按当前TIME_ZONE先转成UTC再存进数据库。等你展示的时候又需要再多做一步转换。听起来很绕实际操作中观测到的现象就是你在表单里选了2024-01-01 12:00存到数据库里变成2024-01-01 04:00页面上再显示出来又变回12:00。里外里好像时间没变但如果你在代码里直接读数据库里的datetime对象去比较或者格式化就会看到那8小时的偏移。这个小坑一般在作业里不太容易暴露但做进阶功能时一定会碰到。另外如果你的作业涉及自定义APP记得把它加到INSTALLED_APPS里INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, # ... myapp, # 你的应用名 ]我当时忘了注册结果makemigrations的时候系统提示No changes detected我还以为是模型写错了查了半天才发现是这个原因。2.3 迁移命令执行顺序别搞反数据库相关的两条命令顺序是从makemigrations到migrate。makemigrations是根据你写的模型代码生成迁移文件相当于一份数据库改动计划migrate才真正去数据库执行这些改动。特别提醒刚建好项目的时候先不要动任何模型直接跑一次migrate。因为Django内置的auth、sessions等应用自带一堆数据表需要先执行这条命令把它们建出来。如果不做这一步后面只要代码里用到用户认证、登录状态就会报no such table: auth_permission之类的错。命令本身很简单python manage.py makemigrations python manage.py migrate我见过一个同学的操作顺序彻底反了先改模型、再makemigrations、然后发现迁移文件老是跟预期不符就把migrations目录里的文件删掉重来。这样反复几次后数据库和迁移文件的版本对不上最后只能删掉数据库重新建。所以我的建议是迁移文件是项目的资产不是垃圾文件。每次修改模型之后生成新的迁移文件即可不需要删旧文件。万一真到了需要从头来过的地步宁可删掉.sqlite3数据库文件再重新migrate也不要乱动迁移文件目录。3. 建模与ORM数据库不是Excel第二次作业的核心就是模型也就是MTV架构里的M层。刚开始我很不理解如果数据就是个表格为什么不用Excel等真上手用ORM建了模型才发现模型设计的本质是定义业务规则比单纯的数据格子复杂得多。3.1 字段类型的选择背后是对业务的理解我这次建了一个Article模型对我做的是一个简单的文章管理系统算是比较典型的信息管理结构。字段类型的选择直接决定了后面所有操作能用什么姿势字段类型适用场景注意事项CharField(max_length200)标题、名称等短文本max_length必填数据库层面会限制长度TextField()正文、描述等长文本没有长度限制适合放大量内容DateTimeField(auto_now_addTrue)记录创建时间auto_now_add只在新建时写一次不可手动改DateTimeField(auto_nowTrue)记录最后修改时间auto_now每次保存都会自动更新IntegerField()数字类数据可以用default0给默认值ForeignKey()一对多关系必须指定on_delete行为CharField的max_length很多人不太理解为什么不填就报错——这其实是数据库层面的约束。比如SQLite里的VARCHAR类型需要有个长度上限Django为了在不同数据库之间保持行为一致就强制你在模型里把长度定了。而且这个长度会从前端Validation一直影响到数据库建表语句是一个硬约束。DateTimeField那两个参数auto_now_add和auto_now经常被搞混。我自己的记忆方法是auto_now_add是添加时生效适合做created_atauto_now是现在自动更新适合做updated_at。两个都别手填值全交给Django处理。3.2 写对__str__和Meta后台管理直接起飞学校作业通常都要求能用admin后台查看数据这就是Django自带的后台管理系统。启动开发服务器后访问/admin/用createsuperuser创建的管理员账号就能登录。为了让后台列表清晰每个模型类里都会重写__str__方法class Article(models.Model): title models.CharField(max_length200) content models.TextField() created_at models.DateTimeField(auto_now_addTrue) def __str__(self): return self.title不写这个的话后台列表里显示的记录名全是Article object (1)根本分不清哪条是哪条。Meta类里常用的两个配置是verbose_name和orderingclass Meta: verbose_name 文章 verbose_name_plural 文章 # 默认会显示为文章s注意复数形式 ordering [-created_at] # 按创建时间倒序新文章在前ordering这个非常实用列表页展示顺序直接在这里定好模板里就不用每次写order_by了。3.3 外键关系与on_delete理解删除行为才能不丢数据文章系统一般都有个分类的概念这就是典型的一对多关系。一个分类下可以有多篇文章一篇文章属于一个分类。模型里这么写class Category(models.Model): name models.CharField(max_length50) class Article(models.Model): # ... category models.ForeignKey(Category, on_deletemodels.CASCADE, related_namearticles)on_delete之前一直被我当成必须填的参数而忽略了它的意义直到在一次作业里遇到一个真实场景才明白。我用个生活化的类比解释一下一个分类就像是文件柜的一个抽屉文章就是抽屉里放的文件。当你把这个抽屉丢掉的时候里面的文件怎么处理就是on_delete在回答的问题。CASCADE抽屉丢了文件跟着一起丢。做作业最常见。SET_NULL抽屉丢了文件还在只是归属字段变成空。前提是该字段设置了nullTrue, blankTrue。PROTECT抽屉里有文件的话不许丢必须先手动处理文件才能删。作业里一般用CASCADE没问题但如果你做的东西涉及用户相关资料、订单记录这类不该批量消失的数据就要谨慎考虑其他选项了。related_namearticles这个参数也很重要。它定义了从Category反查到所有相关Article的入口名。有了它在模板或视图里就能直接用category.articles.all()拿到这个分类下的所有文章。不指定的话Django默认用article_set也能用但语义不够清楚。4. 视图函数从能跑到会思考模型写完下一步就是写视图。第二次作业的核心视图一般围绕增删改查展开。这一部分我不太想逐行贴代码毕竟网上教程一大把我想聊几个更值得动脑子的地方。4.1 用render撑起整个响应逻辑一个最基础的列表页视图长这样from django.shortcuts import render, redirect, get_object_or_404 from .models import Article def article_list(request): articles Article.objects.all() return render(request, article/list.html, {articles: articles})render(request, template, context)的三个参数就是请求对象、模板路径、要传给模板的上下文数据。这个函数做的事情你可以理解为——把模板文件读进来把字典里的变量填充进去然后返回一个完整的HTML响应。这里有一个细节新手容易忽略视图里传给模板的字典key 是字符串articles模板里就由{% for article in articles %}来使用这个名字必须一致。别小看这个问题我遇到过一个同学在视图里传的是article_list模板里却写articles结果页面一片空白找了一下午才发现是名字对不上。对于详情页、编辑页这种需要从URL拿参数的视图我强烈建议用get_object_or_404替代getdef article_detail(request, pk): article get_object_or_404(Article, pkpk) return render(request, article/detail.html, {article: article})这个函数就是查不到就返回404页面的包装省得写try/except。更重要的是它避免了直接访问一个不存在的记录时页面崩溃。4.2 处理POST请求时的三个安全检查录入数据和编辑数据都涉及POST请求这一段我认为是第二次作业里最有工程感的部分。一个完整的POST处理流程长这样def article_create(request): if request.method POST: title request.POST.get(title) content request.POST.get(content) category_id request.POST.get(category) # 数据校验 if not title or not content: return render(request, article/form.html, {error: 标题和内容不能为空}) # 创建记录 Article.objects.create( titletitle, contentcontent, category_idcategory_id ) return redirect(article:list) return render(request, article/form.html)这里我总结三个每次都必须过一遍的检查点第一个CSRF token是否在模板里。Django的CsrfViewMiddleware默认开启所有POST请求都会被校验。你只在视图里写POST逻辑但忘了在表单模板里加{% csrf_token %}那提交时100%报403错误。加在form标签内即可Django会自动在生成的HTML里嵌一个隐藏字段。第二个请求方法是否判断。如果你的视图函数没有区分GET和POST那么用户直接访问这个URL时也会执行创建逻辑。不巧的话一个简单的页面刷新可能就会插入一条空数据。用if request.method POST:把写操作包起来是底线操作。第三个数据校验不能省。request.POST.get(title)拿到的是用户传上来的字符串你不能假设它一定有内容、长度一定合法。最简单的一层校验就是判断是否为空、长度是否超限不符合就返回错误信息。所有从客户端拿到的数据都是不可信的这个意识越早建立越好。4.3 POST后为什么要重定向而不是直接返回页面看上面的代码你可能注意到提交成功后我用的是redirect(article:list)而不是render(request, article/list.html, ...)。这两种写法的区别特别值得理解。如果不做重定向用户提交表单后浏览器地址栏仍然停留在/article/create/这个URL当前页面就是刚才POST请求的响应结果。如果这时候用户按一下F5刷新浏览器会弹窗提示确认重新提交表单点确认之后你以为只是刷新一下结果数据又重复插入了一条。而用了redirect之后浏览器的地址会变成列表页的URLF5只是重新请求一个普通的GET页面不会重复提交。这个模式叫PRG模式Post/Redirect/Get是Web开发里处理表单提交的标准做法。配合命名空间使用的话路由文件长这样app_name article urlpatterns [ path(, views.article_list, namelist), path(create/, views.article_create, namecreate), path(int:pk/, views.article_detail, namedetail), path(int:pk/edit/, views.article_edit, nameedit), path(int:pk/delete/, views.article_delete, namedelete), ]app_name article把这一组路由命名为一个命名空间配合namelist在视图或模板里就能用article:list来指代这个URL。以后就算路由路径改了只要name不变所有引用方都不用改这个设计的重要性用过一次就懂。5. 模板层继承与组件化是最值得花时间的模板是MTV里的T层也是很多人的噩梦。我见过不少同学第一次作业就把html、head、body在每个页面里各写一遍当时页面少没问题但第二次作业页面一多问题就爆炸了。5.1 为所有页面抽一个base模板比如导航栏、CSS样式引入、页脚这些内容几乎是每个页面都有的。写5个页面就意味着同一段导航代码复制粘贴5遍。某个链接改一次5个页面都要跟着改。这时候就该用模板继承了。先建一个templates/base.html!DOCTYPE html html langzh-hans head meta charsetUTF-8 title{% block title %}默认标题{% endblock %}/title {% load static %} link relstylesheet href{% static css/style.css %} /head body nav a href{% url article:list %}首页/a a href{% url article:create %}发布文章/a /nav div classcontainer {% block content %} {% endblock %} /div /body /html子模板里第一行就写{% extends base.html %}然后通过{% block content %}往指定位置填内容{% extends base.html %} {% block title %}文章列表{% endblock %} {% block content %} h1文章列表/h1 {% for article in articles %} p a href{% url article:detail article.pk %}{{ article.title }}/a span{{ article.created_at|date:Y-m-d H:i }}/span /p {% endfor %} {% endblock %}block的命名是宁短勿乱前几次我都是直接用content和title简单明了。只要子模板里写的block名字在base模板里存在替换就能生效。如果名字打错了Django不会报错页面会静默地保留base里的默认内容——这个静默失败坑了我好一阵。5.2 模板里访问数据的方式和一个小技巧模板语言里的{{ article.title }}和{{ article.created_at }}看着像Python属性访问但底层实现其实是先尝试字典key、再尝试属性、再尝试列表索引的解析顺序。也就是说你在模板里写{{ article.title }}无论article是对象、是字典还是后面带obj.attr都能取出对应的内容。平时不用深究但遇到模板里取值不对的时候知道这个查找顺序能帮上定位。过滤器是另一个提升体验的利器。比如长文本截断用{{ article.content|truncatechars:50 }}格式化时间用{{ article.created_at|date:Y-m-d H:i }}默认值用{{ some_var|default:暂无 }}。这些都是一行就能搞定的事情放在环境里多写几次就熟了。还有一点循环里顺便处理没有数据的情况在{% for %}循环后面加{% empty %}{% for article in articles %} ... {% empty %} p还没有任何文章快去发布第一篇吧。/p {% endfor %}这个细节虽然简单但老师打开页面看到空白列表和看到友好提示的观感完全不同。5.3 静态文件的配置不只是丢一个文件夹CSS、JS、图片都叫静态文件Django默认是不主动为你提供这些文件的服务的。开发阶段最简单的方式是在应用目录下建static/文件夹把style.css放进去在settings.py里确认STATIC_URL /static/没有改掉然后模板里先{% load static %}再用{% static css/style.css %}。如果写了路径却发现样式没生效先用浏览器开发者工具看一下CSS文件能不能正常打开检查是不是出现了403。这个403常见的原因是在settings.py的末尾漏写了STATICFILES_DIRS的配置。注意STATIC_URL和STATICFILES_DIRS是两个不同的变量前者是URL前缀后者是一个列表指定静态文件的物理位置开发阶段这一项可以不设置Django有默认的查找路径但一旦你用了额外的静态文件目录就必须配好。6. 提交之前我在本机跑通了一个完整闭环写代码一时爽但写完和能提交完全是两码事。这次作业我给自己定了一个规矩提交之前必须从头到尾完整走一遍用户操作流程一个环节断了都不能交。6.1 一套可以照抄的自测顺序我在浏览器里按下面这个顺序过了一遍建议你也试试启动服务python manage.py runserver确认控制台没有任何报错访问首页列表页能看到数据库里已有的数据如果刚建库可能是空的点新建按钮进入录入页填一部分数据留一个空字段测试校验逻辑提交表单观察是否跳转回列表页新数据是否出现进入详情页确认字段展示正确没有TemplateDoesNotExist之类的异常点编辑确认原来填写的数据能回填到表单里修改某些字段保存回列表页看是否更新点删除确认删除成功列表里数据消失F5刷新列表页确认没有重复提交打开/admin/登录后台看记录是否能正常展示这十步看起来简单但每跑一步都可能炸出新问题。我这次在删除功能上就翻车了一次删除完成后我用redirect(article:list)跳转但URL里还带着原来那条记录的pk详情页逻辑再一访问直接5xx。排查了半天才发现是视图里读取pk的方式不对用错了一个取参的写法。6.2 三大高频故障与排查链路如果你想提前知道哪些地方最容易出问题我整理一下这次和之前同学交流时发现的高频故障故障一迁移后数据库表没建出来表现访问页面报no such table: article_article。排查思路先看migrations目录里是否有对应应用的迁移文件如果没有跑python manage.py makemigrations 你的应用名如果文件在再确认是否真正执行了migrate。此外注意你当前在哪个虚拟环境里执行命令切错环境是最常见的尴尬。故障二模板继承后内容不显示表现页面能加载导航栏有但正文区域空白。排查思路大概率是子模板里{% block %}的名字跟base模板里的对不上或者子模板里忘了写{% extends %}。Django对这类错误经常静默处理不会报错。你可以先直接在base模板的block里放一行醒目的中文占位符比如!-- block content start --刷新页面看看是否出现在HTML源码里从而定位问题。故障三表单提交后403表现页面报CSRF verification failed. Request aborted.排查思路检查模板表单里有没有{% csrf_token %}如果用了类视图确认装饰器或csrf_protect没有被错误覆盖如果POST请求是通过纯JavaScript的fetch发出的还需要在请求头里带上CSRF token。6.3 用admin后台快速验证数据状态有时候页面功能看着正常但你不确定数据到底是真存进去了还是页面做了假数据。我的办法是直接去admin后台看。先用python manage.py createsuperuser创建管理员账号然后登录/admin/在对应模型的管理页面里查看记录列表。如果后台显示中文名、能按时间倒序排列、能点进详情看到字段值说明模型定义基本没问题。后台还能顺手测试一下删除操作、查询和编辑相当于Django免费送了一个数据管理工具。这个步骤还有一个额外收获如果模型里Meta的verbose_name、ordering配置正确admin后台的展示也会整洁很多这在作业评分环节是个加分项。7. 一些写在最后的个人体会第一次做这种带数据库的项目时我总觉得代码跑通就是万事大吉。这次作业做完我才意识到跑通只是第一步更重要的是理解每一行代码为什么存在。比如on_deletemodels.CASCADE不是随手粘上去的它代表了一套数据删除策略{% csrf_token %}不是麻烦事它是Web安全的基石。这些认知都是在踩过坑之后才真正长在自己身上的。如果让我总结做第二次作业最值得记住的一点我会说永远不要信任任何来自浏览器的数据也永远不要跳过自测流程。前者让你写出更健壮的代码后者让你交出一份真正能运行的作业。我知道很多同学到了截止日期前才开始赶工草草提交了事。但Django第二次作业其实是一个很好的分水岭——跨过去你对Web框架的理解会进入一个全新的层面跨不过去后面学DRF、学前端框架对接都会觉得吃力。最后分享一个今天我刚发现的小技巧如果sqlite3数据库文件因为反复测试变得又大又乱可以先把数据导出备份成JSON再用python manage.py flush清空数据库并重置序列最后用loaddata恢复。这个流程在下次做项目初期清理测试数据时很实用省得为了一个干净库把整个项目删了重来。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑