
用 Django 从零搭建个人 Blog从 MVC 到文章发布全流程《Python 实战》应用项目篇 · 第 8 篇作者按以下每一步都在真实服务器上执行回显为原样复制。涉及密码与密钥处均已打码不写明文。一、背景为什么要用框架而不是裸写第 7 篇我解决了「数据进出 Excel」的问题。但真实业务往往是「给人用的网页」别人打开浏览器就能看文章、发文章。用http.server裸写也能跑但路由、模板、数据库、后台管理全得自己造轮子。Django 把这些一次性打包好让你专注写业务。本篇我用一次完整实操从零搭一个个人 Blog覆盖Django 的MTVModel-Template-View到底和课本里的 MVC 是什么关系startproject/startapp之后模型、迁移、后台、视图、模板如何串起来一个常被新手卡住的坑ALLOWED_HOSTS报错我先真实复现它再修复。环境Ubuntu 24.04 Python 3.12.3依赖装在 venv 里Django 6.0.7。所有代码放在/root/lab-d/django/myblog。二、先搞懂 MTV再看代码不迷路教科书讲 MVCModel 管数据、View 管界面、Controller 管调度。Django 官方说法是MTV角色Django 里的对应职责Modelmodels.py定义数据结构直接映射数据库表Templatetemplates/*.html负责「长什么样」用{{ }}占位Viewviews.py处理请求、查数据、选模板渲染URLconfurls.py把 URL 路由到对应的 View相当于 Controller 的路由部分一句话记住URL 找到 ViewView 从 Model 拿数据塞给 Template 渲染成 HTML。下面全程按这个链路走。三、建项目与 App真实回显Django 推荐「一个项目project多个应用app」的结构。个人 Blog 的文章功能就是一个独立 apparticles。cd/root/lab-d/django django-admin startproject myblogcdmyblog python manage.py startapp articlesmkdir-particles/templates/articles真实回显PROJECT_READY注意这里用的是 venv 里的python已export PATH/root/lab-d/venv/bin:$PATH。Ubuntu 24.04 系统里直接敲python是找不到的必须用 venv 或python3。建完后目录大致是myblog/myblog/项目配置、myblog/articles/应用。下一步把articles登记进INSTALLED_APPS——这是新手最容易忘的一步忘了就会报Model class articles.models.Article doesnt declare an explicit app_labelarticles in INSTALLED_APPS: True四、定义 Article 模型并迁移模型就是数据库的「定义」。我让 Django 自动建表而不是自己写 SQL# articles/models.pyfromdjango.dbimportmodelsclassArticle(models.Model):titlemodels.CharField(标题,max_length200)contentmodels.TextField(正文)created_atmodels.DateTimeField(创建时间,auto_now_addTrue)def__str__(self):returnself.titlemakemigrations根据模型变化生成迁移文件migrate真正落到数据库。真实回显Migrations for articles: articles/migrations/0001_initial.py Create model Article Operations to perform: Apply all migrations: admin, articles, auth, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK ... Applying articles.0001_initial... OK ...articles.0001_initial... OK这一行说明文章表已经建好。Django 的迁移是「增量」的以后你改了模型字段再makemigrations就会生成一份新的增量迁移不会动已有数据。这是它比裸 SQL 强的地方。五、接入 admin 后台免交互建超管 注册模型Django 自带一个开箱即用的后台。先在admin.py注册模型让它在后台可见# articles/admin.pyfromdjango.contribimportadminfrom.modelsimportArticleadmin.register(Article)classArticleAdmin(admin.ModelAdmin):list_display(title,created_at)search_fields(title,)然后建超级用户。交互式createsuperuser会逐个问用户名、邮箱、密码在自动化脚本里行不通。我用环境变量 --noinput免交互DJANGO_SUPERUSER_PASSWORD********\DJANGO_SUPERUSER_USERNAMEadmin\DJANGO_SUPERUSER_EMAILadminexample.com\python manage.py createsuperuser--noinput合规说明上面密码处已打码********真实执行时使用了强密码本文不记录明文。DJANGO_SUPERUSER_PASSWORD只活在环境变量里不会写进任何文件。真实回显Superuser created successfully.六、写 View Template列表页与详情页视图负责「拿数据、选模板」。我写两个视图列表页按时间倒序取出全部文章详情页按主键取单篇。# articles/views.pyfromdjango.shortcutsimportrender,get_object_or_404from.modelsimportArticledefarticle_list(request):articlesArticle.objects.order_by(-created_at)returnrender(request,articles/list.html,{articles:articles})defarticle_detail(request,pk):articleget_object_or_404(Article,pkpk)returnrender(request,articles/detail.html,{article:article})模板用 Django 模板语言{{ }}是变量{% for %}是循环!-- articles/templates/articles/list.html --h1我的博客 - 文章列表/h1ul{% for a in articles %}liahref/article/{{ a.id }}/{{ a.title }}/a-- {{ a.created_at|date:Y-m-d H:i }}/li{% endfor %}/ulurls.py把 URL 接到视图上项目总路由 app 子路由# myblog/urls.pyfromdjango.contribimportadminfromdjango.urlsimportpath,include urlpatterns[path(admin/,admin.site.urls),path(,include(articles.urls)),]# articles/urls.pyfromdjango.urlsimportpathfrom.importviews app_namearticlesurlpatterns[path(,views.article_list,namelist),path(article/int:pk/,views.article_detail,namedetail),]七、用 shell 灌入测试数据表里没数据页面是空的。用manage.py shell -c直接跑一段代码灌 3 篇测试文章自动化场景比手动进后台方便python manage.py shell-c from articles.models import Article; Article.objects.all().delete(); Article.objects.create(titlePython 入门从变量到函数, content本文带你从最基础的变量讲起...); Article.objects.create(titleDjango 的 MTV 到底怎么理解, contentModel 管数据Template 管展示...); Article.objects.create(title用 OpenCV 做一个人脸检测器, content借助 haarcascade 级联分类器...); print(seeded count , Article.objects.count())真实回显seeded count 3再验证一下落库内容按 id 排1 Python 入门从变量到函数 2026-07-24 02:24:31.87433800:00 2 Django 的 MTV 到底怎么理解 2026-07-24 02:24:31.87736500:00 3 用 OpenCV 做一个人脸检测器 2026-07-24 02:24:31.88052500:00八、runserver 启动并用 curl 真实验证开发服务器用python manage.py runserver 0.0.0.0:8000后台启动再用本机curl验证页面真的能渲染而不是「我以为能跑」。真实 HTTP 状态与 HTML 片段列表页list HTTP200 !DOCTYPE html html langzh-CN head meta charsetutf-8 title文章列表/title /head body h1我的博客 - 文章列表/h1 ul lia href/article/3/用 OpenCV 做一个人脸检测器/a -- 2026-07-24 02:24/li lia href/article/2/Django 的 MTV 到底怎么理解/a -- 2026-07-24 02:24/li lia href/article/1/Python 入门从变量到函数/a -- 2026-07-24 02:24/li /ul /body注意三条按created_at倒序排列最新的 OpenCV 在最上说明order_by(-created_at)生效了。详情页pk1detail HTTP200 h1Python 入门从变量到函数/h1 psmall发布时间2026-07-24 02:24/small/p divp本文带你从最基础的变量讲起逐步过渡到函数与模块的组织方式。/p/div pa href/← 返回列表/a/padmin 后台直接访问/admin/会302 跳转到登录页未登录登录页本身返回 200admin HTTP302 login HTTP200 titleLog in | Django site admin/title link relstylesheet href/static/admin/css/base.css ...这说明后台路由、静态资源、登录页全部正常。纪律验证完立刻pkill关掉 runserver不留长驻进程。本文所有截图对应的服务进程在实验结束后均已确认关闭NO_RUNSERVER_LEFT。九、踩坑实录ALLOWED_HOSTS 报错先复现再修复这是部署 Django 最高频的坑。默认settings.py里ALLOWED_HOSTS []意思是「只允许 localhost」。一旦你用公网 IP 或域名访问Django 出于安全直接拒绝。我先把它真实复现出来。用非白名单的Host头访问等价于拿公网 IP 打过去curl-s-HHost: example.comhttp://127.0.0.1:8000/真实回显——浏览器拿到的是 Django 的 400 报错页日志里写得明明白白titleDisallowedHost at //title ... django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: example.com. You may need to add example.com to ALLOWED_HOSTS. [24/Jul/2026 02:27:50] GET / HTTP/1.1 400 60167复现成功400 DisallowedHost。修复把ALLOWED_HOSTS改成允许的主机。开发阶段可用通配生产请写具体域名sed-is/^ALLOWED_HOSTS .*/ALLOWED_HOSTS [*]/myblog/settings.pygrep-nALLOWED_HOSTS myblog/settings.py28:ALLOWED_HOSTS [*]重启服务后再 curl 列表页直接200HTML 如前所示。这个「先复现、再修复」的过程比直接给结论更有价值——你下次线上看到 400一眼就知道是ALLOWED_HOSTS的事。十、踩坑清单坑现象解决办法python命令找不到python: command not found用 venv 的python或export PATH/root/lab-d/venv/bin:$PATH模型不识别Model class ... isnt in an application in INSTALLED_APPS把 app 名加进INSTALLED_APPSDjango 6 默认单引号迁移报错makemigrations不生成文件确认模型确实被 app 收录改完模型要重新生成迁移超管创建卡交互createsuperuser一直等输入用DJANGO_SUPERUSER_PASSWORD/USERNAME/EMAIL--noinputALLOWED_HOSTS 400DisallowedHost: Invalid HTTP_HOST header把访问用的主机加进ALLOWED_HOSTS生产写具体域名别用*pkill 误杀自己pkill -f runserver返回 -1、命令中断用pkill -f runserv[e]r避免模式匹配到自身命令行后台进程被杀本地驱动被外壳截断、服务起不来用setsid ... /dev/null /dev/null 21 彻底脱离会话十一、总结这一篇把「写网页」这件事拆成了清晰的链路模型建表 → 迁移落库 → 后台注册 → 视图取数 → 模板渲染 → URL 路由 → runserver 验证。每一步都有真实回显佐证不是「理论上能跑」。几个关键认知MTV 不是新概念只是把 MVC 的 Controller 拆成了「URL 路由 View 逻辑」两部分迁移是增量且可回溯的这是 Django 管理 Schema 的核心优势ALLOWED_HOSTS是安全闸门开发期图省事用*可以上线必须收口到具体域名验证要用 curl 看真实 HTTP 状态和 HTML而不是凭感觉。至此一个能看文章、能进后台的个人 Blog 已经跑通。下一篇我把视线转向更「硬核」的方向——用 OpenCV 在图片里把人脸框出来。本文实验均在华为云 Flexus X 实例Ubuntu 24.04, Python 3.12.3上真实执行。文中SECRET_KEY由 Django 自动生成已打码处理未以明文呈现。