☰
Django博客开发全流程:从ORM模型设计到部署避坑指南
2026/10/5 7:14:40 网站建设 项目流程

最近有个新入行的朋友问我:想学Django但不知道从哪个项目入手,看了一堆教程还是写不出来。我给他的建议始终是同一个——写一个博客系统。这不是敷衍,Django全栈开发最典型、最有完整闭环的项目就是博客:它有增删改查、有用户登录、有列表页和详情页、有表关联,甚至能直接推到线上。这篇文章就是我把从零搭博客的过程完整梳理了一遍,从设计表结构讲到登录权限、从ORM查询讲到部署前要避的坑。目标是让读者看完之后能照着敲出一个能跑、能写文章、能登录注册的博客系统。适合刚学完Python基础、想用Django做第一个完整项目的朋友,也适合已经写过一两遍但想理清全栈脉络的同行。

1. 项目定位与技术选型:为什么博客是Django入门的教科书式项目

1.1 博客系统恰好覆盖Django的核心能力

很多人选第一个项目时,要么选一个“待办事项列表”,要么选一个“图书管理系统”。这类项目不是不能练,而是练完之后你会发现自己只是学会了“抄代码”。Django的核心优势不在于“快”,而在于它把Web开发里大量重复、容易出错的部分做成了规范化的框架。博客系统恰恰能踩中Django绝大多数功能点。

文章是典型的CRUD资源。列表页、详情页、创建页、编辑页、删除确认页,这五种页面构成了Web应用最通用的骨架,任何系统几乎都逃不开这一套。博客之外,像CMS内容管理、企业官网的后台、甚至一些简单电商后台,都是这个模式。

更关键的是,博客天然需要多张表协同工作。文章有作者、有分类、有标签,这三个字段分别对应一对一、外键和多对多关系。很多新手一开始只写一张简单的表,以为ORM就是“给一个类加几个字段”,完全没体会到关系型数据库的设计逻辑。博客恰恰能让这三种关系在一个项目里全部出现,并且每个关系都有明确业务含义。这一点是“待办事项列表”给不了的。

另外一个容易被忽略的好处是:博客的前端不会太复杂。Bootstrap加模板继承就够用了,不需要提前学一个独立的前端框架。这意味着Django的模板系统、静态文件处理、表单渲染这些后端工程师必须懂的前后端协作知识,可以在博客项目里完整走通。博客作为入门项目,不是因为简单,而是因为它的复杂度刚好卡在“能练全流程”和“不会劝退”之间。

1.2 功能边界怎么划:先做MVP,别给自己加戏

我写这个博客的时候,第一步不是开项目,而是写了一个功能清单。刚开始我也犯过错,想加搜索、想加点赞、想加RSS订阅、想加暗色模式,最后全部忍住了。最终保留下来的规划是:

  • 用户认证:注册、登录、登出,以及基于登录状态的权限控制。
  • 文章管理:发表、编辑、删除自己的文章,未登录用户只能看。
  • 分类与标签:文章归类,列表页可以按分类筛选。
  • 评论功能:登录用户可以发表评论,并关联到具体文章。

这里尤其是“点赞”和“搜索”,我建议新手第一版别碰。点赞涉及反作弊、去重和数据统计,往深了说会牵扯Redis、消息队列甚至前端交互的状态管理,远超入门范畴。搜索更明显,如果刚上来就接Elasticsearch,光是环境搭建和索引同步就够喝一壶。Django自带的filter做简单包含搜索足以支撑个人博客的阶段。

在我后来的实际体会里,这四个核心模块做完,整个全栈开发的骨架已经非常完整。后续想加任何功能,都是往这个骨架上填肉,不需要推倒重来。项目边界划得清楚,开发过程中才不会三天两头改需求、改模型,把学习精力浪费在无效返工上。

2. 数据模型设计:博客的地基怎么打才稳

2.1 三张核心表的设计逻辑

动手写代码前,我会建议先在纸上画一下表关系。文章表、分类表、标签表是博客的数据核心,我逐个说说当时的设计思路。

先看文章表。常规字段无非是标题、正文、摘要、封面图、创建时间、更新时间。有两个字段值得单独讲,我建议每个新手都要认真理解:

第一个是status。我把它设成IntegerField,0代表草稿、1代表已发布。为什么不用BooleanField?因为状态这种东西以后大概率会扩展,例如增加“审核中”“已下线”“定时发布”等状态。整数字段从0开始递增,扩展性远强于布尔值。发布状态直接决定了前端查询时用不用filter过滤。写文章不可能一次写完,草稿功能非常实用。

第二个是slug。它用来做文章URL的优化。Django默认主键是自增id,如果URL是/blog/3/这样,能用但不好看,对SEO也不友好。用slug把标题转成hello-django这样的标识符,URL就变成/blog/hello-django/了。slug字段在创建文章时可以由标题自动生成,Django自带slugify()函数可以轻松实现。

分类表很简单:名字、slug。标签表同样。这里有一个关键点,需要认真理解关系方向。一篇文章只能属于一个分类,所以文章表里放的是ForeignKey;一个分类下可以有很多篇文章,反向查询就是分类下的文章列表。文章和标签之间是ManyToManyField,一篇文章可以有多个标签,一个标签也可以对应多篇文章。这也是博客系统跟普通学生管理系统的本质差异:它不是单表或多张孤立表,而是通过关系保证业务数据不冗余、不乱套。

至于ORM对多对多的处理方式,很多人会懵。你迁移后在数据库里找不到文章的tags字段,但能看到一张名叫类似blog_article_tags的中间表。这张表就是Django自动生成的关系表,里面只存文章ID和标签ID的对应关系。如果你想在新增文章时顺便把标签也创建好,那么就要注意多对多关系的add和create用法,一块儿处理。

2.2 用户权限与登录功能的底层思考

博客必须解决“谁能改谁的文章”这个问题。Django自带的User模型已经覆盖了用户名、密码、邮箱、权限组等绝大多数场景。我的原则是能用内置就别自定义。用第三方包当然快,但学习阶段亲手做一遍登录链路,比什么都强。

登录功能的实现逻辑是这样的:前端提交用户名密码,Django用authenticate()验证,验证成功后调用login()写入会话。后续请求带着session或者cookie去访问,框架里的request.user就能自动指向当前已登录用户。这段链路里最核心的词是“session”,本质上服务端存了一份记录了当前登录状态的钥匙,浏览器把钥匙存在cookie里,每次请求带上就行。理解了session机制,登录功能在你眼里就不再是一个黑盒。

权限控制分两层。第一层是登录要求,用@login_required装饰器包住发布、编辑、删除这几个视图,未登录用户访问这些URL会自动跳到登录页。第二层是对象级权限,文章模型上有author外键,编辑或删除前先判断request.user == article.author,不是作者就直接403。这里有个常见的坑:很多教程只做了登录判断,忘了判断“登录了不代表是文章作者”,结果就是任何注册用户都能改别人文章,这在实战里属于严重的数据安全问题。我在帮别人review代码时见过不少次,值得专门拿出来强调一遍。

3. 从空目录到能跑的项目:全流程实操记录

3.1 创建项目与应用的一些技巧

写代码之前先交代环境。我用的是Python 3.10和Django 4.x,两个版本都比较稳定。创建项目的过程我直接列命令:

mkdir blog_project && cd blog_project python3 -m venv venv source venv/bin/activate pip install django django-admin startproject config . python manage.py startapp blog python manage.py startapp accounts

这里有个小技巧,为什么项目用户目录叫config而不是blog?如果你直接startproject blog,那主目录名就是blog,再startapp blog就会撞名,后续import时容易出各种莫名其妙的问题。我把项目配置文件目录命名为config,应用命名为blog,职责就清晰了。config是拿着方向盘的那双手,blog是真正干活的发动机。

startapp之后立刻去settings.py里的INSTALLED_APPS把两个应用加进去。这一步漏了,后面models建了表也不会生效。这个坑可以说是新手高频问题榜首,我见过太多人问“为什么我定义了模型但迁移提示找不到”。你如果看到这句话,先回去看看INSTALLED_APPS,十有八九就能自己解决。

3.2 settings配置与数据库选择的取舍

项目创建完成后,默认语言是英文、时区是UTC,对中文博客来说肯定要改。settings.py里最关键的几行:

LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_TZ = True

USE_TZ这个配置值得多说几句。Django官方推荐True,意味着数据库里存的是UTC时间,页面展示时才转换成当地时间。很多新手贪图省事把USE_TZ改成False,结果在时间计算、按日期分组统计时遇到各种错乱。我的观点是保持True不动。模板里Django会自动适配时区,后端代码需要手动转换时用localtime方法,几乎没有额外负担。时间处理的坑是最隐蔽的,你踩过一次就会明白官方为什么默认推荐开启。

数据库方面,入门阶段用默认的SQLite完全够。不急着上PostgreSQL或者MySQL,因为你的核心任务是学Django本身,没必要把数据库的坑提前搬过来。数据库切换不需要改逻辑代码,以后要换时把ENGINE换成django.db.backends.postgresql再补几个连接参数就搞定。

3.3 迁移、ORM查询与删除对象的踩坑记录

定义好模型之后,迁移命令是每个Django项目都躲不开的门槛:

python manage.py makemigrations python manage.py migrate

makemigrations是把模型类翻译成数据库操作脚本,migrate才是真正执行脚本去建表。这条命令背后有个极具价值的小技巧:makemigrations之后用python manage.py migrate --plan可以提前查看将要执行的SQL操作;用python manage.py sqlmigrate blog 0001还能直接看到Django生成的原始SQL语句。面试官如果问“Django迁移是怎么管理的”,你能说出这三条命令的差异,就能证明你不是只会照抄命令跑流程。

ORM查询方面,我整理一份最常用的写法对照:

  • 查所有文章:Article.objects.all(),返回QuerySet
  • 按发布时间倒序:Article.objects.order_by('-created_at')
  • 只查已发布:Article.objects.filter(status=1)
  • 查单个:Article.objects.get(id=1),注意查不到或查重都会抛异常
  • 创建:Article.objects.create(title="...", content="...")
  • 更新:先取对象改字段再save(),或者批量update()
  • 删除:article.delete()

热词里提到“django执行查询-删除对象”,关键点正是delete()。这个方法有返回值,返回的是(总删除数, 各类型删除数)。这个返回值在外键关系存在时尤其值得注意,因为一对一、外键、多对多都会触发级联删除。多对多关系下的部分删除行为是新手最怕的。我曾经吃过一次亏:删一个分类时,把这个分类下所有文章都删了。后来才意识到,想要保留文章、仅仅删除分类,就该在ForeignKey上设置on_delete=models.SET_NULL,同时让该字段允许null=True。on_delete这个参数,建议每个新手花十分钟搞清楚五个选项的区别,后面是否发生“删一个边角料毁掉整棵树”的惨案,全看它。

3.4 登录路由与发表文章的完整链路

登录功能的完整链路我建议亲手写,别装一堆第三方认证库。以我的经验,核心视图写出来长了也就二三十行:

from django.contrib.auth import authenticate, login def user_login(request): if request.method == 'POST': form = LoginForm(request.POST) if form.is_valid(): username = form.cleaned_data['username'] password = form.cleaned_data['password'] user = authenticate(request, username=username, password=password) if user is not None: login(request, user) return redirect('blog:article_list') else: form = LoginForm() return render(request, 'accounts/login.html', {'form': form})

写完登录后最自然接着做的事情就是“创建文章”。创建文章视图基本逃不开request.method判分支的套路:GET返回空表单,POST校验表单、保存、跳转详情页。保存时有一个容易忽略的细节:文章需要自动关联到当前登录用户。所以新建时不能直接ModelForm.save(),要save(commit=False)先把表单数据变成模型对象,手动给author属性赋值,再二次save()落库。

我特意提一下commit=False这一步,是因为它代表了Django表单系统里一个非常核心的概念:把表单与模型解耦。理解了这个参数,再去看Django的ModelForm源码,整个表单工作流会豁然开朗。模板层面,建立一个base.html作为骨架,里面用{% block content %}{% endblock %}掏出内容区,所有页面extends它。再引入Bootstrap的CDN,几分钟就能从“纯文本页面”变成“有导航栏的网页”。

模板里判断登录状态用的是{% if user.is_authenticated %},已登录显示用户名和退出按钮,未登录显示登录链接。这些语法是新手最容易卡壳的小点,因为它们是模板语言而不是Python,调试的时候找半天可能只是少写了一个endblock。我想说的是,模板错误信息往往不像Python那样给出清晰的行号,所以写模板时务必把endblock这种闭合标签当作和中括号、引号一样重要的语法处理。

3.5 Django Admin:被低估的全栈利器

很多全栈教程忽视了Admin,但我认为它是新手理解Django威力的最佳窗口。只需在admin.py里写几行注册代码:

from django.contrib import admin from .models import Article, Category, Tag admin.site.register(Article) admin.site.register(Category) admin.site.register(Tag)

然后运行python manage.py createsuperuser创建管理员账号,访问/admin/就能直接在后台对文章做增删改查,管理分类标签,上传封面图,全程可视化。对个人博客来说,这个后台甚至可以完全当运营管理端使用,不需要再单独写一套后台界面。

Admin真正强大的地方不是registr,而是自定义。list_display控制列表显示哪些字段,list_filter提供侧边栏筛选,search_fields配置搜索框,fieldsets可以按区块组织编辑表单。这套自定义能力掌握之后,很多企业内部工具的管理界面都能靠它直接交付,省掉大量重复的前端工作。

4. 开发调试期绕不开的坑:问题排查与避坑指南

4.1 静态文件加载失败

这是Django新手遇到的第一个大坑,我把它放第一位。开发阶段settings.py里一般需要手动配好:

STATIC_URL = '/static/' STATICFILES_DIRS = [BASE_DIR / 'static']

配了还是不显示的话,第一步用浏览器DevTools看Network里静态资源的请求URL是多少,确认路径;第二步看模板里有没有写{% load static %}。常见错误无非两类:一类是模板里直接用相对路径写死,另一类是忘了在模板顶部加载static标签。这两个错误说起来都简单,但你如果没有排查经验,可能对着屏幕干瞪半小时。我后来养成的习惯是配完settings先写一个只有一张图片的测试页,确认静态文件通路正常后再往下写布局,能省非常多无效调试时间。

4.2 CSRF验证失败与登录跳转问题

Django的表单POST请求默认都要带CSRF token,模板里忘了写{% csrf_token %},提交时就会看到红色403错误页。这其实不是Bug而是安全机制。Django要求每个POST请求携带一个由服务端生成的随机token,用来防御跨站请求伪造攻击。理解了这一点,处理起403就不会烦躁。

至于登录跳转失效,十之八九和LOGIN_URL或next参数配置有关。在settings里显式设置LOGIN_URL = '/accounts/login/',凡是未登录的请求被驳回后,Django会记住原来要访问的地址,拼在next参数里,登录成功后再带用户回原页面。这个体验闭环对博客来说很重要。设想一下,读者看完文章想写评论,被要求登录,登录完又找不到刚才的文章了,这体验该多差。

4.3 查询性能与N+1问题

博客首页列表页要展示文章和分类,很多人直观的写法是循环里点属性取值,比如article.category.name。如果一页显示10篇文章,就会额外触发10条查询。这种就是典型的N+1问题,很多Django项目上线后变慢的元凶都在这里。

解决方式是用select_related和prefetch_related:

articles = Article.objects.filter(status=1).select_related('category').prefetch_related('tags')

select_related用于外键,它底层是SQL JOIN,把关联表数据一次取回来。prefetch_related用于多对多,底层是第二次批量查询后在内存里做关联。这个优化在开发环境数据量小的时候根本看不出来,等文章超过几百篇后,响应时间差异会非常显著。我建议Django新手从第一天就养成一个习惯:在列表页开django-debug-toolbar,盯着SQL查询次数看,这比任何性能教程都更直接。

4.4 迁移文件冲突与意外数据丢失

开发过程改模型是常态,改完一定记得重新makemigrations。我遇到最诡异的场景是不同环境下各自动过迁移,生成了编号重叠的文件,导致migrate时报“检测到多个叶子节点”或者直接提示冲突。这种情况下可以执行python manage.py migrate app_name --plan手动规划合并点,也可以把冲突迁移里的改动在模型里补齐后删掉多余迁移文件,用makemigrations重新生成。需要特别注意一点:线上已经迁移过的数据库,绝对不能直接删除迁移文件,否则Django会视为表不存在,处理起来相当痛苦。

我在本地开发时吃过一次苦头,为了“干净”把migrations目录全删了重新生成,结果数据库里的django_migrations记录对不上,最后只能重建库。现在我的习惯是迁移文件只增不改、不随意删除,这个原则能帮你避免大量数据层面的折腾。

5. 部署前收尾:让项目具备上线体质的几点建议

5.1 拆分开发与生产配置

开发阶段把一切配置写进一个settings.py没问题,但项目一旦准备部署,第一件要做的事是拆分配置。我推荐的做法是把settings.py改成settings/包,里面放base.py、dev.py、prod.py,通过环境变量控制加载哪套。理由很简单:你不可能一直开着DEBUG来记录异常,也不可能在线上暴露本地路径和相关密钥。虽然对一个入门项目来说这显得有点“工程化过度”,但如果你以后想靠Django吃饭,这个习惯越早养成越好。

5.2 用环境变量管理秘密信息

SECRET_KEY、数据库密码、第三方API key绝不写死在代码里。用django-environ或者干脆用os.environ读取。我的习惯是项目根目录放一个.env文件,里面存所有真实配置,同时把.env写进.gitignore防止意外提交。这事不是小题大做,我看到过太多直接把SECRET_KEY提交到公开仓库的案例,被爬虫扫到之后整个会话体系都不可信,只能紧急轮换密钥。

5.3 静态文件和Admin样式的托管

开发模式里Django能自己处理静态文件,但生产环境用Nginx加Gunicorn跑的时候,必须通过python manage.py collectstatic把散落在各app的静态文件收集到统一目录,交给Nginx托管。这个命令理解起来很简单,但有个前提:你需要安装whitenoise之类的库并把它加进中间件。否则Django在关闭DEBUG之后,默认不会帮你托管Admin后台的样式。Admin界面如果变成纯HTML“裸奔”,视觉效果就好像电脑中了病毒一样,搜索框、按钮全部错位。第一次部署时如果不提前了解这点,很容易以为自己哪里配错了。

写到这里,聊聊我个人的一些体会

这个博客项目走完一遍,你会发现它带给你的东西远远超出“会写一个能用的博客”本身。建模型时对关系型数据库的理解,写登录时对session机制的掌握,排查N+1时对查询性能的意识,这些都是能迁移到任何Web项目上的底层能力。

我有几个经验性的建议,算是送给走到这里的朋友。第一,自己动手把表的关联画一遍,不要直接照抄模型的代码。第二,登录链路别用第三方包,亲手写一次。第三,开发过程中坚持开django-debug-toolbar,把SQL查询次数当作健康指标看待。做到这三点,你再去接触更复杂的项目时,会发现Django世界里很多东西都是相通的。

如果下一步想继续发展这个博客,可以试着把评论改成异步通知,把文章搜索换成PostgreSQL的全文检索,或者给首页加上一个简单的Redis缓存。这些扩展都会让学习曲线平滑地继续向上延伸,而你现在已经站在一个足够稳固的起点了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询