当前位置: 首页 > news >正文

用 Django 从零搭建个人 Blog:从 MVC 到文章发布全流程

用 Django 从零搭建个人 Blog:从 MVC 到文章发布全流程

《Python 实战》应用项目篇 · 第 8 篇
作者按:以下每一步都在真实服务器上执行,回显为原样复制。涉及密码与密钥处均已打码,不写明文。

一、背景:为什么要用框架而不是裸写

第 7 篇我解决了「数据进出 Excel」的问题。但真实业务往往是「给人用的网页」:别人打开浏览器就能看文章、发文章。用http.server裸写也能跑,但路由、模板、数据库、后台管理全得自己造轮子。Django 把这些一次性打包好,让你专注写业务。

本篇我用一次完整实操,从零搭一个个人 Blog,覆盖:

  • Django 的MTV(Model-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,再看代码不迷路

教科书讲 MVC:Model 管数据、View 管界面、Controller 管调度。Django 官方说法是MTV

角色Django 里的对应职责
Modelmodels.py定义数据结构,直接映射数据库表
Templatetemplates/*.html负责「长什么样」,用{{ }}占位
Viewviews.py处理请求、查数据、选模板渲染
URLconfurls.py把 URL 路由到对应的 View(相当于 Controller 的路由部分)

一句话记住:URL 找到 View,View 从 Model 拿数据,塞给 Template 渲染成 HTML。下面全程按这个链路走。

三、建项目与 App(真实回显)

Django 推荐「一个项目(project)多个应用(app)」的结构。个人 Blog 的文章功能就是一个独立 app:articles

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 doesn't declare an explicit app_label

articles in INSTALLED_APPS: True

四、定义 Article 模型并迁移

模型就是数据库的「定义」。我让 Django 自动建表,而不是自己写 SQL:

# articles/models.pyfromdjango.dbimportmodelsclassArticle(models.Model):title=models.CharField("标题",max_length=200)content=models.TextField("正文")created_at=models.DateTimeField("创建时间",auto_now_add=True)def__str__(self):returnself.title

makemigrations根据模型变化生成迁移文件,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.modelsimportArticle@admin.register(Article)classArticleAdmin(admin.ModelAdmin):list_display=("title","created_at")search_fields=("title",)

然后建超级用户。交互式createsuperuser会逐个问用户名、邮箱、密码,在自动化脚本里行不通。我用环境变量 +--noinput免交互:

DJANGO_SUPERUSER_PASSWORD='********'\DJANGO_SUPERUSER_USERNAME=admin\DJANGO_SUPERUSER_EMAIL=admin@example.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):articles=Article.objects.order_by("-created_at")returnrender(request,"articles/list.html",{"articles":articles})defarticle_detail(request,pk):article=get_object_or_404(Article,pk=pk)returnrender(request,"articles/detail.html",{"article":article})

模板用 Django 模板语言,{{ }}是变量,{% for %}是循环:

<!-- articles/templates/articles/list.html --><h1>我的博客 - 文章列表</h1><ul>{% for a in articles %}<li><ahref="/article/{{ a.id }}/">{{ a.title }}</a>-- {{ a.created_at|date:"Y-m-d H:i" }}</li>{% endfor %}</ul>

urls.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_name="articles"urlpatterns=[path("",views.article_list,name="list"),path("article/<int:pk>/",views.article_detail,name="detail"),]

七、用 shell 灌入测试数据

表里没数据,页面是空的。用manage.py shell -c直接跑一段代码灌 3 篇测试文章(自动化场景比手动进后台方便):

python manage.py shell-c' from articles.models import Article; Article.objects.all().delete(); Article.objects.create(title="Python 入门:从变量到函数", content="本文带你从最基础的变量讲起..."); Article.objects.create(title="Django 的 MTV 到底怎么理解", content="Model 管数据,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.874338+00:00 2 Django 的 MTV 到底怎么理解 2026-07-24 02:24:31.877365+00:00 3 用 OpenCV 做一个人脸检测器 2026-07-24 02:24:31.880525+00:00

八、runserver 启动并用 curl 真实验证

开发服务器用python manage.py runserver 0.0.0.0:8000后台启动,再用本机curl验证页面真的能渲染(而不是「我以为能跑」)。真实 HTTP 状态与 HTML 片段:

列表页

list HTTP=200 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>文章列表</title> </head> <body> <h1>我的博客 - 文章列表</h1> <ul> <li><a href="/article/3/">用 OpenCV 做一个人脸检测器</a> -- 2026-07-24 02:24</li> <li><a href="/article/2/">Django 的 MTV 到底怎么理解</a> -- 2026-07-24 02:24</li> <li><a href="/article/1/">Python 入门:从变量到函数</a> -- 2026-07-24 02:24</li> </ul> </body>

注意三条按created_at倒序排列(最新的 OpenCV 在最上),说明order_by("-created_at")生效了。

详情页(pk=1)

detail HTTP=200 <h1>Python 入门:从变量到函数</h1> <p><small>发布时间:2026-07-24 02:24</small></p> <div><p>本文带你从最基础的变量讲起,逐步过渡到函数与模块的组织方式。</p></div> <p><a href="/">← 返回列表</a></p>

admin 后台:直接访问/admin/302 跳转到登录页(未登录),登录页本身返回 200:

admin HTTP=302 login HTTP=200 <title>Log in | Django site admin</title> <link rel="stylesheet" href="/static/admin/css/base.css"> ...

这说明后台路由、静态资源、登录页全部正常。

纪律:验证完立刻pkill关掉 runserver,不留长驻进程。本文所有截图对应的服务进程在实验结束后均已确认关闭(NO_RUNSERVER_LEFT)。

九、踩坑实录:ALLOWED_HOSTS 报错(先复现再修复)

这是部署 Django 最高频的坑。默认settings.pyALLOWED_HOSTS = [],意思是「只允许 localhost」。一旦你用公网 IP 或域名访问,Django 出于安全直接拒绝。我先把它真实复现出来。

用非白名单的Host头访问(等价于拿公网 IP 打过去):

curl-s-H"Host: example.com"http://127.0.0.1:8000/

真实回显——浏览器拿到的是 Django 的 400 报错页,日志里写得明明白白:

<title>DisallowedHost 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-i's/^ALLOWED_HOSTS = .*/ALLOWED_HOSTS = ["*"]/'myblog/settings.pygrep-nALLOWED_HOSTS myblog/settings.py
28:ALLOWED_HOSTS = ["*"]

重启服务后再 curl 列表页,直接200,HTML 如前所示。这个「先复现、再修复」的过程,比直接给结论更有价值——你下次线上看到 400,一眼就知道是ALLOWED_HOSTS的事。

十、踩坑清单

现象解决办法
python命令找不到python: command not found用 venv 的python,或export PATH=/root/lab-d/venv/bin:$PATH
模型不识别Model class ... isn't in an application in INSTALLED_APPS把 app 名加进INSTALLED_APPS(Django 6 默认单引号)
迁移报错makemigrations不生成文件确认模型确实被 app 收录;改完模型要重新生成迁移
超管创建卡交互createsuperuser一直等输入DJANGO_SUPERUSER_PASSWORD/USERNAME/EMAIL+--noinput
ALLOWED_HOSTS 400DisallowedHost: Invalid HTTP_HOST header把访问用的主机加进ALLOWED_HOSTS(生产写具体域名,别用*
pkill 误杀自己pkill -f runserver返回 -1、命令中断pkill -f 'runserv[e]r'避免模式匹配到自身命令行
后台进程被杀本地驱动被外壳截断、服务起不来setsid ... < /dev/null > /dev/null 2>&1 &彻底脱离会话

十一、总结

这一篇把「写网页」这件事拆成了清晰的链路:模型建表 → 迁移落库 → 后台注册 → 视图取数 → 模板渲染 → 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 自动生成,已打码处理,未以明文呈现。

http://www.jsqmd.com/news/1270093/

相关文章:

  • REPA方法:扩散模型训练中的早期停止与整体对齐策略
  • Hacker Typer部署教程:3步快速搭建属于你的黑客打字网页
  • AI招聘系统技术解析与求职反制策略
  • 英雄联盟智能助手Seraphine:三步实现游戏数据管理的终极解决方案
  • DeepSeek++语音输入输出:解放双手的AI交互方式
  • Python 计算机视觉入门:OpenCV 人脸检测实战
  • 《Windows 11 从入门到精通》2.1.1:Windows Update 自动升级详解
  • 2026年广东自动化设备线束供应厂家:专业能力与市场格局深度解析 - 卓企推荐
  • AI编程助手时代程序员核心能力重构与实践
  • 2026 年 7 月新发布:双鸭山值得关注的阀控智能水表直销厂家怎么联系,水费不再心疼!这套系统如何彻底颠覆您的用水管理? - 领域鉴赏官
  • 2026回头才醒悟:树洞陪玩安全隐私不踩坑,安心倾诉才是真治愈 - 时时资讯
  • OpenCV与YOLOv8实战笔记:计算机视觉知识管理
  • 05-运算符和类型转换
  • 从0到1部署wasm-service:完整的Rust到WebAssembly构建流程
  • 从源码到部署:Ministral-3-8B-Base-2512-bf16技术白皮书级教程
  • OBS多平台直播终极指南:3步实现高效同步推流
  • Godot引擎2D游戏开发:从场景树架构到性能优化的实战指南
  • KiraAI 1.6.6部署实战:从环境搭建到性能调优
  • Unity Toggle状态控制:从UI组件到逻辑状态机的进阶指南
  • 力扣 373:有序数组最小K数对的暴力与优化双解
  • SSH批量密码修改:一条命令实现多服务器安全运维
  • 解决Linux普通用户Docker权限问题的最佳实践
  • Unity运行时节点编辑器实战:集成、性能优化与自定义序列化解决方案
  • 2026回头才醒悟:情感咨询树洞安全隐私不踩坑,咨询师说对话加密,可注销账号后记录还在系统里躺着 - 时时资讯
  • 2026 年更新:岐山优秀的称重数据统计软件制造企业有哪些,别再瞎猜!这套工具如何帮你省下百万成本? - 实业推荐官【官方】
  • 2026回头才醒悟:起床监督树洞安全隐私不踩坑 - 时时资讯
  • 2026免费去水印软件推荐:两款实用工具实测对比 - 爱上科技热点
  • 解决iOS异形按钮痛点:OBShapedButton高级特性与最佳实践
  • CSS css 中的 animation、transition、transform 有什么区别?
  • 【CTF-MISC-邮件附件】在eml邮件中传输xlsx,xlsx里面藏压缩包和密码