1. 这个demo真正值得学的是什么?先别急着敲代码
很多人第一次打开 Django 官网的“编写你的第一个 Django 应用”教程,下意识反应就是照着代码敲一遍,敲完却发现自己啥也没学会。我当年就是这个状态:项目跑起来了,页面也出来了,但让我独立改点东西就傻眼。后来回过头复盘才明白,官网这个投票应用 demo 从来不是让你“抄代码”的,它是用最小篇幅把 Django 核心的请求处理链路完整走了一遍。
这个项目的正式名称是 Polls(投票应用),顶层是一个 Django 项目(Project),里面挂了一个应用(App)。它要解决的事情很具体:用户能浏览一堆问题(Question),能进入某个问题去投票(Choice),投票结果能做后台管理。就这么点需求,Django 官方愣是用它串起了整个 Web 开发最要命的五个环节——模型定义、数据库迁移、后台管理、视图路由、模板渲染。
你会发现热词榜上那些问题的根源都在这个 demo 里。比如“django创建app”不会、static文件显示不出来、查询对象后怎么删除、项目实战不知道从哪下手……这些问题只要把官网 demo 真正吃透,至少能解决八成。所以这篇东西的定位很清楚:不是翻译官网教程,而是把官网教程里“没说明白”和“藏起来”的部分摊开揉碎了讲,同时把新手最常踩的坑帮你提前踩一遍。
如果你是第一天碰 Django,跟着这篇文章做完,你会有一个能跑的投票应用,并且知道自己每一步在干什么。如果你已经照着官网敲过一遍,这篇文章能帮你把脑子里的“一团代码”理顺成真正的知识框架。
2. 零基础起步:创建项目和第一个App
2.1 环境准备与项目骨架生成
动手之前先把环境确认好。Django 5.x 需要 Python 3.10 以上,我建议直接用 Python 3.12,现在大部分第三方库都跟上了,不会遇到兼容性折磨。安装 Django 用 pip 就行:
pip install django装完验证一下版本:
python3 -m django --version接下来就是在哪创建项目的问题。这里有个我见过无数新人踩的坑:官网教程会告诉你在任意目录执行django-admin startproject mysite,但很多人不知道这个命令生成的mysite是“外层项目容器目录”,里面还有一个mysite的包目录。如果你执行的时候不加点,后面manage.py跑起来之后路径嵌套很深,文件结构看着就头大。
我推荐的做法是创建一个专门的项目目录,然后在目录里带点创建:
mkdir mydemo cd mydemo django-admin startproject mysite .注意结尾那个点,它表示在当前目录直接生成manage.py,目录结构清爽不少。生成完你会看到这些内容:
mydemo/ manage.py mysite/ __init__.py settings.py urls.py asgi.py wsgi.py很多新手看到一堆 py 文件就发怵,其实你只需要关心前两个。settings.py是整个项目的总配置,数据库、应用注册、模板路径、语言时区都在这里改。urls.py是全局路由入口,浏览器敲什么网址,由它决定分发给哪个 App 处理。剩下的wsgi.py和asgi.py是部署时用的接口,开发阶段不用管。
注意:wsgi.py和asgi.py不是给初学者看的,但asgi.py你可以先瞄一眼,因为它就是后面热词里反复出现的一个知识点——我正在用asgi.py在开发阶段直接跑 WebSocket 服务,比如聊天室内的实时推送,或者后台有状态变化时把数据主动推给前端页面。而实现这一切,前提就是你已经把官网这个 demo 的请求处理模型搞懂了。
2.2 创建App与注册:migrate之前先想清楚这件事
项目是项目的骨架,App 才是业务模块的载体。官网第一步就让你创建 polls:
python3 manage.py startapp polls执行完你会发现多了个polls/目录,里面有models.py、views.py、admin.py这些文件。这里我想多说两句:startapp生成的views.py和models.py都是空文件,里面有注释但没逻辑,很多人看到空文件就不知道下一步干嘛了,这是正常心理,别慌,后面我们来填。
真正要命的是下一步仍然有一半人做了但不知道为什么做——注册 App。打开mysite/settings.py,找到INSTALLED_APPS,把polls加进去。
INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "polls", ]这一步不是走过场。Django 的 App 机制是“可插拔”的,如果你不注册,python3 manage.py migrate建表时不会扫到这个 App 的模型,后台管理也找不到它,挂载路由也会报错。我之前遇到过朋友把polls写在元组里但末尾忘加逗号,结果启动直接崩,就是这种细节。
注册完之后,照官网做第一次迁移:
python3 manage.py migrate我没打算只让你照做,我想解释一下这一步到底干了什么。Django 自带一堆内置应用(比如认证、会话、权限管理),它们也有自己的数据表。第一次migrate会把内置表全部建到你的数据库里。这就像搬进新房子,先把开发商配好的家具都归位,再摆你自己的东西。所以注册完 App 之后跑迁移是个好习惯,但这时候你的 polls 模型还没写,不会生成 polls 相关的表。
2.3 启动开发服务器:报错怎么读
跑起来看看效果:
python3 manage.py runserver正常会看到Starting development server at http://127.0.0.1:8000/。但很多人在这一步就出问题,最常见是端口被占用。我之前在一台老 Mac 上跑过,8000 端口被某个残留进程占着,报错长这样:
Error: That port is already in use.解决办法是换端口:
python3 manage.py runserver 8001或者直接指定成 0 端口让系统帮你随机挑一个:
python3 manage.py runserver 0还有一点新手容易忽略:runserver默认只监听本机。如果你想把服务暴露给局域网内手机测试,要执行:
python3 manage.py runserver 0.0.0.0:8000这个命令很实用,前端开发时经常用手机访问电脑上的开发服务器,但记得开发服务器不是生产服务器,别拿到公网环境长期跑。
注意:
runserver最大的价值是改了代码自动重载,大多数改完直接刷新页面就能看到效果。但改了settings.py里的数据库配置等核心内容时,部分情况不会自动重载,最好手动重启一次。
3. 真正动手:模型、数据库与后台管理
3.1 模型的写法与数据库迁移原理
投票应用最核心的数据是两个:问题和选项。官网在polls/models.py里写了两段代码,我换个方式给你拆解:
from django.db import models class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField("date published") def __str__(self): return self.question_text class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0) def __str__(self): return self.choice_text这段代码信息量挺大的。
CharField(max_length=200)是变长字符串,数据库里对应的是 varchar(200),200 是硬上限,超了就报错。这相当于给表单和数据库都加了一道校验。DateTimeField存日期和时间,因为官网要求一个“发布时间”,所以字段里给了个参数叫"date published",这是 Django 里的 verbose name,后台页面会把它作为标签显示。
最重要的是外键那行:question = models.ForeignKey(Question, on_delete=models.CASCADE)。它表示一个问题可以对应多个选项,每个选项都归属于一条问题记录。on_delete=models.CASCADE的意思是,如果某条问题被删除,那它名下所有选项也会自动删除,避免数据库里留孤儿数据。这属于数据库设计的“参照完整性”,Django 用一行配置帮你兜底了。
写完之后,生成了迁移文件:
python3 manage.py makemigrations polls这一步非常重要。makemigrations会扫一遍models.py,和你上一次迁移状态对比,把变化生成一个 Python 文件放到polls/migrations/里。它只是一个“施工图纸”,不会真的改数据库。
然后执行:
python3 manage.py migrate这时 Django 读取刚才生成的施工图纸,去数据库里真正建表、加字段。所以这两个命令的工作流程是:
| 命令 | 作用 | 类比 |
|---|---|---|
makemigrations | 生成迁移文件 | 出图纸 |
migrate | 执行迁移文件改变数据库 | 按图纸施工 |
大部分新手写模型改模型都很随意,后来发现数据库结构对不上才去看文档。我自己踩过的坑是:改完 models 之后忘了makemigrations,直接跑 migrate,结果数据库里永远没有新字段,一直报no such column。只要记住这对组合拳后面会少太多麻烦。
3.2 查询 API 和删除实操:不只面试才考
模型定义好之后,Django 给每个模型自动配了一套查询 API,这是整个框架最方便也最值得深挖的地方。官网用一个 Python shell 做演示:
python3 manage.py shell我建议各位不必刻意背 shell 命令,但下面几行值得自己亲手敲一遍:
from polls.models import Question, Choice q = Question(question_text="你最喜欢的编程语言?", pub_date="2025-01-01 08:00:00") q.save() Question.objects.all() # <QuerySet [<Question: 你最喜欢的编程语言?>]>新建对象后必须调用save()才会写进数据库,不调用它,对象只是内存里悬空的 Python 实例,退出 shell 就没了。Question.objects.all()返回的是一个 QuerySet,做过后端开发的人一看就明白,它就是“一堆记录”的集合。
这里我想把“删除对象”单独拎出来说,因为热搜词里有人专门搜“django执行查询-删除对象”。删除操作在 shell 里其实就是两行事:
q = Question.objects.get(id=1) q.delete()get()返回的是一个具体对象,不是 QuerySet。delete()会返回一个元组,告诉你删了多少条记录。比如:
(4, {'polls.Choice': 3, 'polls.Question': 1})这是我在实际项目里反复用到的排查手段。有时候用户在界面上误操作产生了脏数据,你不想写管理后台,直接进 shell 查出问题对象删掉,比写一堆清理脚本高效得多。
查询还有层我不能漏掉——外键关系查询。我见过好多新人在视图里为了拿某个问题的所有选项,自己写了 filter 循环:
choices = Choice.objects.filter(question_id=question.id)其实 Django 的 ORM 给你做了一个反向关联属性,官网没细说,但实际项目里很关键。假设question是一个 Question 实例,question.choice_set.all()就能拿到它的全部选项。这个_set的名称来源于外键关系里“多”的那一方,是 Django 默认生成的反向管理器。理解了它,你就不会在模板里为了显示选项列表而写一堆杂乱的查询了。
3.3 后台管理:不用写一句 HTML
Django admin 一直是让我觉得“框架级降维打击”的存在。一个投票应用如果要自己写管理界面,你需要设计列表页、编辑页、提交逻辑、权限控制,没个三五十个文件下不来。Django admin 只用两步:
# polls/admin.py from django.contrib import admin from .models import Question, Choice admin.site.register(Question) admin.site.register(Choice)然后创建一个管理员账号:
python3 manage.py createsuperuser输入用户名、邮箱、密码(密码输入时终端不显示字符,这是正常的),启动服务器后访问http://127.0.0.1:8000/admin/,登录就能看到问题表和选项表,可以在线增删改查。
很多人跑通 admin 就觉得完事了,但从新手进阶的角度,我建议你再去admin.py里加一段list_display:
class QuestionAdmin(admin.ModelAdmin): list_display = ["question_text", "pub_date"] list_filter = ["pub_date"] admin.site.register(Question, QuestionAdmin)加了这行之后,后台列表页会直接展示发布时间列,还能按时间过滤。官网教程其实留了这段进阶操作作为练习,我实测下来的体感是:Django 后台的可配置性远比想象的高,它不只是“能用”,还能模拟出轻量级运营后台的效果,撑起一个中小型项目的管理端完全没有问题。
这一步是整个 demo 的分水岭。如果你只是想“让程序能跑”,模型写完就够了。但如果你想真正理解 Django 的开发闭环,admin 是你第一次见识“配置驱动”这个设计哲学的地方——很多功能不需要写代码,声明配置即可。
4. 页面从无到有:视图、路由与模板
4.1 视图函数与路由映射:理解请求的一生
模型管数据,视图管“请求进来之后返回什么”。官网在第二步就写了一版最简单的视图:
from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. You're at the polls index.")与之配对的是一段路由:
from django.urls import path from . import views urlpatterns = [ path("", views.index, name="index"), ]然后把polls/urls.py挂到项目根路由mysite/urls.py上:
from django.urls import include, path urlpatterns = [ path("polls/", include("polls.urls")), path("admin/", admin.site.urls), ]这里有个概念必须想透:请求进来之后发生了什么?浏览器访问http://127.0.0.1:8000/polls/,Django 拿到 URL 路径是/polls/,先匹配项目根路由的polls/前缀,然后交给include引入的polls.urls继续匹配,最终匹配到views.index,执行视图函数,把返回的HttpResponse变成 HTTP 响应返回给浏览器。
所以include是一个“路由分包”机制,项目大了之后每个 App 都能维护自己的 urls,根路由只需要把各个 App 挂进来,互不干扰。官网 demo 代码不长,核心却在这里——Django 的学习曲线陡,最陡的地方就是路由分发和请求生命周期,想通了后面看啥都不怕。
4.2 模板渲染与数据传递:让页面动态起来
视图不能老返回纯文本,接下来要加载 HTML 模板。我在polls/views.py里改成:
from django.shortcuts import render from .models import Question def index(request): latest_question_list = Question.objects.order_by("-pub_date")[:5] context = {"latest_question_list": latest_question_list} return render(request, "polls/index.html", context)再建模板目录结构。这一步是新手最常见的卡顿点——模板应该放哪?我知道很多人第一次做会直接建一个template/文件夹,结果 Django 找不到。原因是 Django 习惯用App 下的templates目录自动发现模板,而且为了多个 App 之间避免模板名冲突,还要求在templates下面再加一层以App 名命名的目录:
polls/ templates/ polls/ index.html正确的目录结构必须长这样。render(request, "polls/index.html", context)里的路径polls/index.html是相对于polls/templates/的,会先去 polls App 的模板目录找,找不到再去全局模板目录找。这是 Django 模板加载器的默认搜索顺序,官网不会把这层挑明,但你不搞清楚,后面做项目会被“模板找不到”折磨到怀疑人生。
index.html的内容可以很简单:
<!DOCTYPE html> <html> <head> <title>Polls</title> </head> <body> <h1>最近的问题</h1> {% if latest_question_list %} <ul> {% for question in latest_question_list %} <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li> {% endfor %} </ul> {% else %} <p>还没有发布任何问题。</p> {% endif %} </body> </html>模板里的{% if %}和{% for %}是 Django 模板语言的控制流,{{ question.question_text }}是变量占位符。这里的latest_question_list就是视图里通过 context 字典传过来的任何变量。模板语言刻意保持简单,不允许写复杂逻辑,这是设计哲学——逼着你把业务逻辑放在视图里,模板只负责展示。
写到这里,页面已经不是“Hello World”,而是真正能读数据库并展示数据了。我第一次看到列表刷出来的时候,心里的感受是:原来“动态网站”就这么回事,数据库里的东西通过视图捞出来,再塞进 HTML 模板里展示。这个链路你建立了,后面加 API、加权限、加缓存,所有操作都在这条链路上做文章。
5. 高频翻车现场:静态文件、前端显示与排查思路
5.1 为什么 img 标签里的图片死活不显示
热搜词里有个很典型的问题:“vscode 写 img 标签在 django 的 static 文件中显示不了”。这个问题我起码被问过十次。先说结论:要在 Django 模板里引用静态文件(图片、CSS、JS),光把文件放在某个路径还不够,你必须在模板里用{% load static %}声明加载静态文件标签,再用{% static '路径' %}去引用。
官网到后面会教这个,但很多人在 demo 阶段就想放张 Logo,结果用 HTML 原生的<img src="static/image/xx.png">,打开页面全是 404 或者破图标。原因很简单:Django 开发服务器的 URL 不是直接对应磁盘路径的,/static/这个名字有一个特殊的静态文件处理器,需要你点击模板里的静态资源标签去触发它。
报错日志你大概率会看到这类信息:
"GET /static/images/logo.png HTTP/1.1" 404别急着怀疑文件路径,先检查模板开头有没有{% load static %},再检查 URL 里有没有包{% static %}。
5.2 静态文件目录到底应该建在哪
这里我把官网没细说的目录结构和配置一次讲透。Django 找静态文件分两种情况:
第一种是 App 内的静态文件。在每个 App 下建static目录,里面再按 App 名嵌套一层:
polls/ static/ polls/ style.css logo.png这样static标签会去所有已注册 App 的static/目录里找,做到了每个 App 内的资源自治。多个 App 都有style.css也不冲突。
第二种是全局静态文件。把资源放在项目根目录下的staticfiles/,然后在settings.py里配置:
STATICFILES_DIRS = [ BASE_DIR / "staticfiles", ]BASE_DIR是 Django 自动定义的项目根目录路径,这样你的全局图片、公共资源就放在项目根下的staticfiles/里,对所有 App 可见。
模板里正确的引用方式是这样的:
{% load static %} <img src="{% static 'polls/logo.png' %}" alt="Logo">注意static必须放在文件开头,否则 Django 模板解析器压根不认这个标签。路径里的polls/logo.png是相对polls/static/的路径。加上命名空间polls/作为前缀,能有效避免多个 App 文件重名时互相覆盖。
这里还有一个不小的坑:改完静态文件后浏览器还是旧的。因为浏览器缓存了 CSS/JS,很多新手改了文件但刷新看不到效果,就以为是 Django 没生效,其实是浏览器缓存在捣鬼。测试阶段按住Ctrl+Shift+R(Mac 上是Command+Shift+R)强制刷新能解决大半问题。
5.3 从 demo 到实战还要补哪些课
做完官网 demo 只是第一步,我把它叫做“Django 的地基”。地基打完之后,你会面临一个岔路口:
- 想做独立项目,下一步要学用户注册登录、权限管理。热搜词里的“django rbac”指的就是基于角色的权限控制,这其实是 Django 自带
auth应用 +admin后台的延伸。理解了 Admin 的权限模型,再看 RBAC 会轻松很多。 - 想做实时功能,就要学 WebSocket。热搜词里的“django websocket 实现后台有数据前端推送”,核心思路是:浏览器通过 WebSocket 和 Django 保持长连接,Django 后台有状态变化时直接通过这个通道把数据推给前端,而不是像常规请求那样等浏览器主动来拉。你先理解
asgi.py是干嘛的,再去看 Channels 或者 Django 5 的异步支持,就不容易迷失方向。 - 想做大项目,建议研究 Django 的类视图(Class-Based Views)和 DRF(Django REST Framework)。官网 demo 用的是函数视图,逻辑简单还行,但项目一复杂,列表页、详情页、表单页全是相似逻辑,类视图帮你做掉大半重复代码。
我一直觉得官网这个投票 demo 最了不起的地方,在于它没有任何一个多余的文件。每一个文件在真实项目里都会用到,每一段代码背后都牵连着 Django 最核心的设计概念。把它彻底吃透,你就拿到了理解所有 Django 知识点的“主干道地图”,后面不管学什么高级特性,本质上都是在这张地图的某个交叉路口扩展分支而已。
我个人实际操作中踩过最深的坑,是从 demo 直接跳到“自己捏一个复杂项目”,结果面对一堆概念直接懵掉。后来我学乖了,每次想学一个新功能,就回到 polls 这个 demo 上尝试加一点东西。比如给投票页面加个统计图表,加个登录后才能投票等,做着做着,Django 的知识网络就自然织起来了。今天写这篇,也是希望大家别只把官网 demo 当一个“课后练习”,而当它是一个可以反复折腾的实验田。