1. 为什么拿博客系统当Django全栈开发入门项目
很多新手问我,学Django怎么写第一个项目?我的答案永远是博客系统。全栈开发听起来虚,但博客系统恰好能把前后端串起来的每一个环节都踩一遍:建模型、做查询、配路由、写模板、弄后台、处理静态文件,往后还能继续加搜索、分页、部署。这个小项目不偏不倚卡在“简单到不会劝退”和“复杂到确实能练手”之间。
我第一次用Django做博客的时候,怀着“不就是CRUD嘛”的心态,结果被MVT架构和ORM折腾了一整天。但正是那一天让我把Django的执行路径完全摸清了。这里给准备入坑的朋友一句实话:重要不是把博客做得有多花哨,而是通过这个项目把Django的思维方式内化。以后你不管是写REST API、小程序后端,还是做数据分析平台,骨架都差不多。
1.1 一个博客系统到底涵盖了什么
博客的核心是内容生产和内容消费。从数据设计上看,它需要文章表、分类表,可能还有标签表;从界面交互上看,它需要文章列表页、详情页、搜索入口;从运营维护角度,它需要管理后台来发布文章、维护分类和标签。这些需求拆解下来,正好对应Django的三件套:Model负责和数据打交道,View负责业务逻辑,Template负责页面渲染。再加上Django内置的admin后台,数据维护这件事就几乎不用自己造轮子了。
很多人误以为admin只是给程序员用的调试工具,但实际上它就是一个真正可用的后台管理界面。模型建好后,只要在admin.py里用两行代码注册,大家都拥有一个基于Web的内容发布平台。整个开发周期里,省下的时间非常可观。
1.2 这套项目做完你能带走什么
做完博客项目,最值钱的东西不是代码,而是“请求到了页面返回”的完整想象。浏览器发来一个URL请求,Django拿urls.py去匹配对应的视图,视图调用models里的方法去数据库查询,把结果扔给template渲染,最终返回HTML。这个理解会在你以后的任意一次开发中反复复用。
如果你已经学过Python面向对象,Django模型正好把这些概念落地。模型本身就是一个类,字段对应类的属性,外键就是一种对象关联。你在写Post.objects.filter(category__name='Django')时,其实是在用面向对象的语法操作关系型数据库。这种思维一旦建立,后续学SQLAlchemy、Peewee这些ORM框架都会觉得似曾相识。
另外一个很多人忽视但有价值的点,是manage.py命令的熟练度。创建数据迁移、建超级用户、启动本地服务、收集静态文件、进入Django shell,这些命令往后每天都在敲,早一点形成肌肉记忆,能让你少走不少弯路。
2. 先把环境搞定:Python、虚拟环境与Django安装
2.1 Python版本选择与虚拟环境
要安装Django,先得有Python解释器。我建议直接用比较新的稳定版本,比如Python 3.10到3.12这个区间。旧版本Python虽然也能跑,但生态对旧版的兼容性只会越来越差,没必要在起点就和未来唱反调。
真正讲究的是虚拟环境。同一个电脑上可能同时存在多个项目,每个项目依赖的依赖包版本可能还不一样。一个项目在Django 4.x上正常,另一个可能还在Django 2.x的维护状态。不做隔离,早晚会因为版本冲突抓狂。所以第一步就是建虚拟环境,这个操作建议变成肌肉记忆。
在Windows的命令行里,我会这样操作:
# 先进入项目目录,比如 C:\projects\myblog mkdir myblog cd myblog python -m venv venv venv\Scripts\activateMac和Linux下几乎是同一个流程,只是激活命令不一样:
mkdir myblog cd myblog python3 -m venv venv source venv/bin/activate激活成功的标志,是命令行前面出现一个(venv)前缀。看到这个前缀,说明你当前shell用的Python解释器和pip都来自这个独立环境,装什么都不会污染系统。
2.2 pip安装Django与版本选择
激活虚拟环境后,安装Django就是一条命令的事:
pip install django这里默认会装最新稳定版。如果想指定版本,可以写成pip install django==5.0.6。我实际经验是,新项目直接跟最新稳定版就行,别去折腾4.x或2.x的老版本。除非你是在做一个已有项目,需要保持版本统一。
国内网络环境下,如果直接拉PyPI官方源感觉速度不稳定,可以换成国内镜像源。比如清华源:
pip install django -i https://pypi.tuna.tsinghua.edu.cn/simple装完之后,验证一下:
python -m django --version能输出版本号,说明环境就绪了。这一步建议无论多熟练都别跳,环境变量指错、虚拟环境没激活的情况时有发生,一行命令就能排查掉一半的问题。
3. 项目初始化:创建项目与app
3.1 django-admin startproject之后发生了什么
环境搞定后,用Django自带的管理命令创建项目:
django-admin startproject myblog .注意后面有个点。这个点代表在当前目录生成项目文件,不是再多套一层目录。如果省掉这个点,Django会创建一个myblog目录,里面还有个myblog目录,初学者经常在路径里绕晕。
执行后,项目根目录会多出一个manage.py文件和一个以项目名命名的包:
myblog/ manage.py myblog/ __init__.py settings.py urls.py asgi.py wsgi.pysettings.py是整个项目的配置中心,数据库、app注册、模板路径、静态文件、语言时区都在这里。urls.py负责入口路由,如果把Django比作一个餐厅,这个文件就是菜单,告诉你哪个菜该端到哪张桌子。wsgi.py和asgi.py是部署服务器时要用的接口文件,日常开发阶段基本不用碰。
初次接触时,头一个想改的就是settings.py里的几个配置:
# settings.py LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_TZ = True改成中文语言和上海时区,后台管理页面会变成中文,日期时间显示也会正常。
3.2 创建自己的博客app
项目建好后,再创建专门负责博客功能的app:
python manage.py startapp blog这一步会在项目根目录下生成一个blog文件夹,里面包含models.py、views.py、admin.py、migrations目录等文件。有人会和项目包混淆,理解成两个项目,其实是两回事。项目是整个网站的大框架,app是项目里的一个模块,一个项目下可以有多个app,比如blog、comment、user。这种模块化拆分是Django长期以来使用的一种结构,好处是能让你在代码层面的独立性上和功能职责上分配清晰,后面做大了也不用推倒重来。
3.3 注册app到settings
刚生成的app不会自动生效,必须手动告诉项目,让它在启动时加载。打开settings.py,找到INSTALLED_APPS这个列表,加一行:
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog', # 新加的 ]漏了这一步,后面做迁移、建表时都会提示找不到模型,报错会相当迷惑。我见过好几个新手在“为什么我创建的模型没生成表”这个问题上卡住,最后原因就是忘注册app,这一行代码虽然不起眼,却是救命的关键。
4. 数据模型与ORM操作:建模、查询、删除
4.1 博客系统的模型怎么设计
博客系统最少要有一张文章表。文章表通常会关联分类和标签。分类简单,直接用ForeignKey外键就可以;标签跟文章是多对多关系,用ManyToManyField。
在blog/models.py里写下这样的模型:
from django.db import models class Category(models.Model): name = models.CharField(max_length=50) def __str__(self): return self.name class Tag(models.Model): name = models.CharField(max_length=50) def __str__(self): return self.name class Post(models.Model): title = models.CharField(max_length=200) content = models.TextField() category = models.ForeignKey(Category, on_delete=models.CASCADE) tags = models.ManyToManyField(Tag) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True)设计要点说几个。title用CharField,记得给max_length;content是文章正文,用TextField,不限制长度;category是外键,指向Category表,on_delete参数决定删除分类时文章怎么处理,我用CASCADE表示级联删除,如果不想删掉文章,可以改成models.SET_NULL,前提是把外键字段设置null=True。
__str__方法很值得写。它决定对象在后台列表里显示成什么内容。如果不定义这个方法,admin后台里看到的永远是“Post object (1)”这种让人崩溃的字符串,根本不知道哪行是哪篇文章。这是新手最常忽略的细节之一。
4.2 生成迁移并同步到数据库
模型写完后,接下来是迁移流程:
python manage.py makemigrations python manage.py migrate第一条命令会检查模型的变化,生成迁移文件;第二条命令是把迁移应用到数据库,真正建表。这两条命令我会连在一起让大家形成一个固定习惯:改完模型就makemigrations,确认没有报错再migrate。
Django默认用的是SQLite数据库,这是一个文件型数据库,适合开发阶段。等做生产部署到时候可以切换成MySQL或PostgreSQL,只需要在settings.py里改DATABASES配置,代码不用大改,这也是Django对新手很友好的地方。
4.3 ORM查询与删除对象
数据模型跑起来之后,最常面对的需求就是查询和删除。
查询这件事,我建议到开发服务器里直接用shell练,Django提供了一个交互式命令行接口:
python manage.py shell进去之后,你会看到类似Python的交互提示符,但此时所有模型都已经准备就绪。可以试着跑:
from blog.models import Post, Category, Tag Post.objects.all()这是全表查询,返回一个QuerySet。接下来是过滤:
Post.objects.filter(category__name='Django')这里有两个连续的下划线(category__name)。双下划线的意思是“跨模型查字段”,翻译过来就是:找出所有“分类名称是Django”的文章。这是ORM查询里非常有代表性的写法,非常值得多练几次。
获取单个对象要用get:
post = Post.objects.get(pk=1)如果查不到或者查到多条,都会抛出异常。get方法返回的只有一个模型对象,而不是QuerySet,所以可以继续访问属性:
print(post.title) print(post.category.name)删除对象比想象中简单:
post.delete()这句话会在数据库里把对应记录删除,并且因为外键设置了CASCADE,Python会通过日志告诉你连带删除了哪些关联数据。这正好是我在项目实战中经常强调的一环:Django执行查询-删除对象不是凭空的SQL语句,而是ORM层的封装。
如果想批量删除,查询结果是个QuerySet,同样可以直接调用delete:
Post.objects.filter(created_at__year=2024).delete()控制台上会显示“删除了多少条记录”,验证起来很清楚。
4.4 在admin后台里操作数据
模型建好了,查询也通了,但还缺一个可视化管理界面。打开blog/admin.py,把模型注册进去:
from django.contrib import admin from .models import Post, Category, Tag admin.site.register(Post) admin.site.register(Category) admin.site.register(Tag)然后创建一个超级管理员账号:
python manage.py createsuperuser按提示输入用户名、邮箱和密码,用户名是必需的,邮箱可以留空。启动服务:
python manage.py runserver浏览器输入http://127.0.0.1:8000/admin,用刚创建的账号登录,就能看到全部注册的模型。点击Posts进去就能加文章、改内容、删记录。这个后台界面虽然默认样式朴素,但足以满足博客管理的基本需求。
5. 视图、URL与模板:数据到页面
5.1 视图函数怎么取数据
页面展现的业务逻辑,应放在blog/views.py里。我先写一个最典型的文章列表视图:
from django.shortcuts import render from .models import Post def post_list(request): posts = Post.objects.all().order_by('-created_at') return render(request, 'blog/post_list.html', {'posts': posts})这里用框架封装好的render函数,把QuerySet作为上下文传给模板。列表按时间倒序排序,最新文章显示在最前面,这是博客最常见的展示方式。
详情页视图相对会稍微复杂一些,因为要根据URL里的主键取对应对象:
from django.shortcuts import render, get_object_or_404 from .models import Post def post_detail(request, pk): post = get_object_or_404(Post, pk=pk) return render(request, 'blog/post_detail.html', {'post': post})用get_object_or_404代替直接get的好处是,当文章不存在时,不用自己写try except,它会自动返回404页面。这一点在实战中非常好用,省心且不容易出错。
5.2 URL路由配置
视图写好后,要把它和URL绑定起来。项目入口的urls.py已经被Django初始化过,我通常是先在里面include一下业务app的urls:
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('blog.urls')), ]然后在blog目录下新建一个urls.py:
from django.urls import path from . import views urlpatterns = [ path('', views.post_list, name='post_list'), path('post/<int:pk>/', views.post_detail, name='post_detail'), ]这里的<int:pk>是URL参数转换器,Django会把URL里这一段内容解析成整数放名叫pk的参数,然后自动传给post_detail视图函数。把app的路由拆到单独文件里管理,是官方推荐方式,模块越分越多时不会挤成一团。
5.3 模板继承与页面渲染
模板这块,新手最怕的是每个页面重复复制同一套HTML头部和导航。Django有模板继承机制可以直接化解。
我在blog/templates/目录下建一个base.html作为基础模板:
<!DOCTYPE html> <html lang="zh-hans"> <head> <meta charset="UTF-8"> <title>{% block title %}我的博客{% endblock %}</title> </head> <body> <header> <nav><a href="{% url 'post_list' %}">首页</a></nav> </header> <main> {% block content %}{% endblock %} </main> </body> </html>然后写post_list.html:
{% extends "base.html" %} {% block title %}最新文章{% endblock %} {% block content %} <h1>文章列表</h1> {% for post in posts %} <article> <h2><a href="{% url 'post_detail' pk=post.pk %}">{{ post.title }}</a></h2> <p>{{ post.created_at|date:"Y-m-d" }}</p> <div>{{ post.content|truncatechars:100 }}</div> </article> {% empty %} <p>还没有文章。</p> {% endfor %} {% endblock %}模板语言里{% %}是控制逻辑,{{ }}是输出数据。truncatechars过滤器可以把长文截断成摘要,这对列表页非常实用。
5.4 分页、静态文件与上下文处理
文章一多,列表页就得考虑分页。视图里用Django自带的分页器:
from django.core.paginator import Paginator def post_list(request): post_list = Post.objects.all().order_by('-created_at') paginator = Paginator(post_list, 5) page_number = request.GET.get('page') posts = paginator.get_page(page_number) return render(request, 'blog/post_list.html', {'posts': posts})模板中,分页对象自带posts.has_previous等方法,可以拿它们生成上一页下一页按钮。
静态文件同样要注意。Django的开发服务器默认能处理,但模板里必须先用{% load static %}加载标签,再通过{% static 'css/style.css' %}引用。很多初学者页面样式一直不显示,十有八九是这一环节出了问题。
6. 常见问题与排查技巧实录
6.1 TemplateDoesNotExist与模板路径问题
我见过最多的报错就是TemplateDoesNotExist。Django默认按app的templates目录去搜模板,前提是模板文件要放在blog/templates/下,且视图里引用blog/post_list.html时必须和实际路径对得上。很多时候路径名差一个斜杠、或者模板文件放错了层级,都会导致这个报错。
排查时可以在settings.py里打开调试信息,看Django提示的错误模板路径列表,从中基本能判断是哪个目录出了问题。如果自定义了模板目录,记得在settings.py中配置DIRS。模板这个坑,我自己也踩过,新建目录时少打一层,查了半小时。
6.2 迁移报错与数据库异常
改模型时最怕发生外键字段和原有数据冲突。比如你把某个外键字段改成了nullable,但数据库里已有历史数据不满足约束,migrate时会报错。这类问题建议尽量在开发早期解决,直接把数据库文件(默认是db.sqlite3)删掉再重新migrate,就能重置数据。但是提醒一下:如果已有生产数据,千万别用这个粗暴方式。
另一种常见情况是makemigrations后没有迁移,直接启动服务,页面就一直报“relation xx does not exist”之类的错误,换成人话就是:表还没建。遇到这个问题,先跑python manage.py makemigrations,再跑python manage.py migrate,不要跳过第一步。
6.3 页面样式突然失效
本地开发时页面样式正常,一部署就光了,这是很经典的问题。原因是生产环境需要手动收集所有静态文件,并且交给Web服务器托管,Django开发服务器不会在生产模式下帮你处理静态资源。
解决办法是:
python manage.py collectstatic并且在settings.py中配置好STATIC_ROOT。另外记得部署时设置ALLOWED_HOSTS,否则打开页面会收到Bad Request (400)的报错。我每次部署都把这两个设置写进检查清单,防止漏掉。
6.4 显示时间不对、评论乱码
时间不对八成是时区问题。TIME_ZONE里写Asia/Shanghai后,还要确认USE_TZ的值。如果数据库里存的是UTC时间,就要在模板层面用Django的timezone模板标签转换。建议直接在建表前就把TIME_ZONE配好,否则改动后旧数据的时间可能依旧错乱,还得专门写脚本处理,相当折腾。
乱码问题更多出现在数据库编码和Django默认客户端字符集不一致。MySQL部署时建议把库表的charset设成utf8mb4,很多中文内容连接数据库后出现问号的情况都能因此解决。做之前没设好,后面再来改编码,数据库表和索引都要重建,务必避免。
7. 用AI辅助Django开发:新工作方式
最近很多人喜欢用AI agent来开发Django项目,我实际试下来,确实能提速不少。把需求描述清楚,AI能在几秒内生成一份可用的模型和视图代码,比手敲快很多。
7.1 AI在Django开发里能做些什么
AI在Django开发里的优势是模板代码生成:创建app、注册模型、写view、配路由、做简单的分页和表单,这类套路感很强的代码,AI几乎不会出错。你甚至可以这样跟它说:“用Django写一个博客系统,包含文章列表、详情页、分类筛选。”一分钟内就能得到初版代码。
尤其在个人学习阶段,AI还能扮演陪练角色。做完模型设计后,让AI帮你检查字段类型是否合理、外键删除策略是否适当、查询语句能否优化。这种“代码评审”带来的成长速度远比自己闷头看文档快。
7.2 给AI提需求要注意什么
这里我特别想说一点:AI再好,也要你会判断它输出的代码是否正确。我踩过几次坑,它的结果大致形式都正确,但细节上容易出问题:比如忘注册app、urlpatterns写错位置、模板路径少了templates目录层级、settings.py配置被覆盖。
因此我给一个自己常用的格式提示:先让AI描述整体结构和需要的文件列表,再让它逐文件生成,最后自己手动快速审查关键文件。千万不要一次性让AI生成“完整项目”然后直接runserver,那样报错时你根本不知道问题出在哪个文件,查错成本比手写还高。
7.3 AI帮不上忙的时候
说实话,AI解决不了“你对该系统应该有哪些功能”这个需求分析问题,也解决不了业务上的取舍。比如文章删除了评论怎么办、用户权限怎么划分、标签要不要允许管理,这类模型设计决策需要你来定。AI生成的内容给你提供了选项,但选择仍然由你负责。这也是为什么我始终建议先把基础能力练扎实,再谈AI提效。基础不牢,AI越多,翻车越快且越隐蔽。
8. 后续扩展建议与个人体会
8.1 博客系统还能加点什么
文章、分类、标签、列表、详情页、后台,基础功能做齐了,博客系统就算立起来了。再往下的扩展方向,我可以给你几个明确选项:给文章加评论、加入搜索关键词功能、添加侧边栏最新文章和热门标签、把分类和标签页做成独立列表、写一个简单的RSS订阅、还可以把文章导出成Markdown格式。
如果想继续深入Django,还可以尝试在上面加一个REST API,利用DRF(Django REST Framework)把所有文章接口暴露出来,再配一个Vue或React前端,这才是真正意义上的全栈开发。到那时,Django的角色不再只是渲染页面,而是提供数据接口,前端再独立展示。
8.2 我自己的踩坑总结
再提醒几个要点。第一,虚拟环境务必认真建,我就见过同事直接在系统Python里装了一堆包,最后把生产环境搞崩。第二,改模型后及时makemigrations并顺手看一眼生成的sql,有助于反馈并理解ORM语法。第三,模板里忘记加{% load static %}这件事,发生的频率高得离谱,但看一次错误提示记一次就能记住。
做博客系统最大的收获,并不是“我写了个网站”,而是你开始掌握Django的执行脉络,以后任何功能需求出现,都能顺着Model-View-Template这条线去找该改哪里。搞懂这个骨架,Django全栈开发的大门基本就对大家敞开了。我个人建议初期多写几次基础流程,少依赖复制粘贴的轮子,等体会到了每一步的原因,再用AI加速并不迟。