第一次接触Django的人,几乎都绕不开官网那个投票demo。问题、选项、投票、结果统计,功能简单得不像一个框架的官方教程,但它恰好把Django最核心的一整条链路串起来了:模型定义、数据库迁移、后台管理、URL路由、视图函数、模板渲染、表单交互、自动化测试。我带了几年新人,每次被问“Django怎么入门最快”,我的答案都是同一句:别急着买课,先把官方demo完整搭一遍,不是“跟着抄一遍”,而是要理解每一步在干什么。这篇就按我自己的习惯,把整个搭建过程从头拆到尾,包括那些文档里不会专门提醒你的坑。
1. 为什么我坚持让新手先把官方demo完整搭一遍
如果你去搜“Django教程”,大概率会看到两类东西:一类是培训机构包装过的“十天学会Django”,另一类是各类技术社区里的实战项目分享。我的看法一直很明确:与其跟着那些又长又绕的视频走一遍,不如先把官网的投票应用(polls)从头到尾敲一遍,真正把它跑通、改过、删掉重搭一次,你得到的不是“一个能交差的案例”,而是对Django全貌的骨架式认知。
官方demo在Django语境里通常特指官方文档第一部分的投票应用:一张Question表存问题,一张Choice表存选项,用户进首页看到最近的问题列表,点进去投票,最后看到统计结果。功能确实简单,但它把Django里最常被使用的模块——模型、迁移、后台管理、URL路由、视图、模板、表单、测试——全部串联了一遍。这个组合拳打下来,你对“一个Web请求是怎么在Django里跑通的”会有非常具体的体感,而不是停留在背概念。
1.1 投票demo解决的不是“投票”,是完整的Django体验
有人会问:做个投票demo,值得吗?我每次的回答都一样——你搭的不是投票功能,而是把“Django处理一个HTTP请求的完整链路”亲手走一遍。用户访问你的网址,Django从urls.py里找到匹配的路由,调用对应的视图函数,视图再通过models操作数据库,最后把数据丢给模板渲染成HTML返回浏览器。这整条链在官方demo里是一次性走通的。
说白了,理解了这条链路,之后再遇到任何Django项目,你都能顺着它排查问题:页面404了先看路由,数据不对先看模型,样式丢了先看静态文件,POST报403先检查csrf_token。demo阶段积累的这套排查顺序,会直接变成你以后的工作习惯。很多人在真实项目里手忙脚乱,不是因为不会写代码,而是脑子里没有这张“请求地图”。
1.2 它和“看视频跟着写电商项目”的真正区别
视频教程常见的节奏是“我敲一行,你跟着敲一行”,最后确实得到了一堆代码,但换台电脑、换个需求,就不知道怎么从头来。官方demo最不同的地方在于:文档的读者不是“某一个跟随者”,而是“任何一个人”。它要求你理解每个文件存在的原因,而不是把每个文件默写出来。
这也是为什么我建议新手把官方demo完整搭一遍之后,还要自己删掉项目重搭一次。第二次全程不查文档,你对startproject、startapp、makemigrations、migrate这些命令的肌肉记忆,比任何笔记都管用。至于那些“企业级项目”,它们更适合等你有了基础之后拿来扩展视野:比如看看人家的用户认证怎么拆分、目录结构为什么那样分。但第一口饭,还是要啃官方demo,因为只有它能把基础知识压缩到最小、最标准、最没有歧义的范围里。
2. 环境准备:把Python版本和虚拟环境的坑留在这一步
很多人搭官方demo翻车,并不是代码抄错了,而是卡在环境上。你在Windows命令提示符里敲Linux命令,肯定报错;电脑里Python还是3.8,新版Django往上装的时候白色屏报一堆依赖错误;更常见的,是pip直接装了最新版Django,结果发现文档里的一些配置项和自己的版本对不上。这些都是在教学里见过无数次的开场白。所以环境准备这步,我建议直接当成正式环节来做,不要以为“装个Python而已”就跳过。
2.1 先确认Python版本
Django版本和Python版本是一一绑定的。以常见的Django 5.x为例,官方要求Python 3.10以上;你用Python 3.8硬装,pip会告诉你“Django 5.0 requires Python >=3.10”,然后拒绝安装。所以第一步永远是确认当前Python的版本:
python3 --version # Windows 上则是 python --version如果没有Python,就去官网下载3.11或3.12的稳定版,安装时记得勾选“Add Python to PATH”。这个勾选特别容易被忽略,漏掉之后命令行里死活找不到python命令,反而是新手最常见的事故。装完后关掉终端重新打开一次,确认版本号能正常输出,再进入下一步。
2.2 venv的正确使用姿势
接下来必须在虚拟环境里装Django。虚拟环境说白了就是一个独立的小房间,你在这个房间里安装的包只影响当前项目,不会污染全局环境,也不会被别的项目干扰。如果你装了太多不同项目以后,就会发现这个隔离有多重要——A项目用的是Django 4.2,B项目要Django 5.0,全局装的版本肯定打架。
Python自带的venv模块已经足够用了,不需要额外装virtualenv或者poetry:
mkdir mysite cd mysite python3 -m venv venv source venv/bin/activate # Linux/macOS venv\Scripts\activate # Windows PowerShell激活之后,命令行提示符前面会出现(venv),说明你已经进入虚拟环境。之后所有python命令和pip命令,用的都是这个环境里的解释器和包。退出项目时敲deactivate即可,过几天回来,重新source或者activate一次就能继续干活。
2.3 用pip装Django,注意版本锁定
激活虚拟环境后,安装Django就一行命令:
pip install django不指定版本时,pip会装最新版。对官方demo来说这不是问题,但我建议你顺手确认一下版本号:
python -m django --version如果官方文档是针对Django 5.x写的,你最好也装同一个大版本,避免小差异导致配置项对不上。更稳妥的做法是直接限一个范围:
pip install "django>=5.0,<6.0"装完之后,别急着startproject。先看一眼当前目录里有没有和目标项目同名的文件,避免后面生成目录时纠缠不清。干净目录永远是Django项目最好的起点。
3. 项目骨架:startproject和startapp生成了什么,为什么要知道
环境准备好之后,第一次敲django-admin startproject的感觉很微妙:屏幕上刷出一串文件,你以为自己在创建项目,其实Django只是按照约定替你铺好了地基。很多人跳过这一节,觉得“反正命令生成了就行”,但如果不理解这些文件各自干嘛,后面连“项目”和“应用”的区别都理不清。
3.1 startproject之后,settings.py里的秘密
先看经典做法——把项目创建在当前目录中,而不是多套一层目录:
django-admin startproject mysite .加不加最后的点,结果差别非常大。加了这个点,manage.py会在当前目录直接生成;不加点,Django会创建mysite/mysite两层目录,后面所有命令都要多进一层,对新手来说纯属自找麻烦。
生成的核心文件都在mysite/子目录下:
settings.py:项目配置,Django的大脑urls.py:顶层路由wsgi.py/asgi.py:服务器和项目的对接入口manage.py:项目管理的命令行工具
我强烈建议你在跑任何页面之前,先打开settings.py把几个变量认一遍。它们是后续所有行为的基石:
| 配置项 | 作用 | demo阶段你的态度 |
|---|---|---|
DEBUG | 调试模式开关 | 保持True,部署再改 |
ALLOWED_HOSTS | 允许访问的主机列表 | 开发阶段留空 |
INSTALLED_APPS | 已启用应用的清单 | 稍后手动加polls |
DATABASES | 数据库连接配置 | 默认SQLite,零配置 |
LANGUAGE_CODE/TIME_ZONE | 语言和时区 | 可改成zh-hans、Asia/Shanghai |
这里特别提醒一句:默认的INSTALLED_APPS里已经包含了admin、auth、sessions等六个内置应用,它们都依赖数据库。所以后面第一次执行migrate时,Django会顺便把用户管理、session管理等十几张系统表都建好。很多人看到migrate刷出一大堆记录会慌,其实这是正常流程,不是报错。
3.2 startapp生成的应用目录与INSTALLED_APPS注册
项目地基有了,接下来创建应用。Django里的“项目”和“应用”是两层概念,项目是容器,应用是实现具体功能的模块:
python manage.py startapp polls执行之后,目录里多出一个polls/文件夹,里面有models.py、views.py、admin.py、tests.py等文件。这个文件夹就是投票应用的本体。
注意,startapp命令只是把文件夹创建出来,Django本身并不知道它的存在。得手动把应用名加进settings.py的INSTALLED_APPS里:
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'polls', ]漏掉这一步,后面执行makemigrations时会得到“没有检测到模型变化”的提示,或者在admin后台里看不到polls相关的注册信息。这个坑几乎每个人都会踩一次,踩完就记住了:Django不会自动发现你新建的应用,一切都要显式注册。
3.3 第一次runserver的意义
配置完成后运行:
python manage.py runserver默认监听8000端口,浏览器打开http://127.0.0.1:8000/,会看到Django的火箭升空页,写着“It worked!”。很多教程把这当作“环境OK”的证明就结束了,但我想多说一句:这个页面本身就是Django在提醒你,项目已经完整地活着了。
从这一刻开始,你再改动models、views、templates,开发服务器都会自动热重载(除了新增迁移文件等少数情况),不用手动重启。这个开发体验是Django特别招人喜欢的原因之一。我用的命令是python manage.py runserver,而不是django-admin runserver,原因是manage.py会读取当前项目的settings配置,确保在正确环境下启动。这个习惯从第一天就要养成。
4. 模型定义与迁移:Question和Choice这两张表是怎么诞生的
接下来是官方demo的核心板块——定义数据模型。投票应用的数据结构其实很简单:一个问题(Question)下挂多个选项(Choice),每个选项有一个票数字段。简单归简单,它要讲清楚的却是整个Django ORM里最重要的概念:模型是Python类,而数据库表是从模型“算”出来的,不是你手写SQL生成的。
4.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是变长字符串,DateTimeField是时间戳,IntegerField是整数,ForeignKey是外键。每个字段类型都有自己参数,最典型的例子是CharField必须指定max_length,因为数据库层级的长度限制需要明确,不是随便填的。
__str__方法一定要写。它决定对象被打印出来时的样子,也决定后台管理页里每一行记录显示什么。你不写,后台里看到的全是Question object (1)这种看不懂的记录,排查数据时非常痛苦。
Choice里的外键多说一句:on_delete=models.CASCADE表示当Question被删除时,属于它的所有Choice一起被删除。这是“一个问题下有多个选项,选项依赖问题而存在”的模型化表达,也是一对多关系里最正常的语义。官方demo特意把外键放在这里,就是想让你在第一次接触Django时就能看到一对多关系该怎么建模。
4.2 makemigrations、sqlmigrate、migrate三个命令各干一件事
模型写好后,数据库不会自动生成表。Django的迁移体系分两步走,这是官方demo里最容易被新手误解的地方。先执行:
python manage.py makemigrations polls这条命令只做一件事:比较当前模型定义和已有迁移文件,生成一个记录“从上一版到这一版数据库应该怎么变”的迁移文件。注意,它此时并没有修改数据库,只是在polls/migrations/目录下产生一个类似0001_initial.py的文件。
如果你好奇这个迁移文件到底翻译成了什么SQL,可以执行:
python manage.py sqlmigrate polls 0001输出结果里会看到CREATE TABLE语句,也会看到外键约束。这一步能帮你建立“Python模型到数据库表”的对应关系,看懂之后你基本就能理解为什么字段名、类型都要和模型保持一致了。
最后才真正修改数据库:
python manage.py migratemigrate会把当前项目里所有未应用的迁移全部落到数据库,包括之前说的那些内置应用的系统表。项目根目录会出现一个db.sqlite3文件,你用SQLite的图形工具打开它,就能看到polls_question和polls_choice两张表,以及django_migrations这张记录迁移历史的表。
4.3 迁移机制的常见疑问
有人会问:我明明改了模型,为什么不直接ALTER TABLE?原因很简单:迁移文件是版本化的,它不只是改变数据库结构,还让多人协作、线上升级有了可追溯的记录。你改了模型,同事拉代码后执行一条migrate,数据库就同步了。这在真实项目里几乎是保命操作。
还有个超实用的排查场景:makemigrations提示“No changes detected”,但你明明改了模型。最常见的三个原因:一是应用没注册进INSTALLED_APPS;二是改的是老迁移文件而不是当前模型;三是你在错误的目录下执行命令。按这三条逐个排查,几秒钟就能定位。
5. 管理后台:不用写页面也能增删改查,但要理解“注册”的含义
官方demo很早就安排了后台管理环节,这是我认为新手最应该感到惊艳的地方:你还没写任何页面,就能通过一个现成的后台系统管理所有模型数据了。这就是Django自带的admin模块,它靠的是“约定优于配置”:只要把模型注册进去,页面、表单、列表、删除功能全部自动生成,不需要你写一个前端文件。
5.1 创建管理员账号
进入后台前,要先有一个管理员账号:
python manage.py createsuperuser按提示输入用户名、邮箱、密码。密码有强度校验,输入太简单会要求重输。然后启动服务器,访问http://127.0.0.1:8000/admin/,登录后就能看到Django自带的分组和用户管理。
5.2 把模型挂到admin
默认状态下,后台只有“用户”和“分组”两组模型,Question和Choice还不可见。原因很简单:它们没有被注册。打开polls/admin.py:
from django.contrib import admin from .models import Question, Choice admin.site.register(Question) admin.site.register(Choice)刷新后台,就能看到Polls分组下面出现了两个模型入口。点进去,你能直接创建Question、添加Choice、修改选项、删除记录。整件事听起来很神奇,但机制一点不复杂:Django会读取注册信息,然后根据模型字段自动生成对应的表单和列表页。
5.3 用admin反推你的模型设计
我建议新手在后台多玩一会儿,重点不是点几个按钮,而是通过界面反推模型设计。比如你创建一个Question之后,想在Question详情页直接展开Choice列表,就需要给admin加一点配置:
from django.contrib import admin from .models import Question, Choice class ChoiceInline(admin.TabularInline): model = Choice extra = 3 class QuestionAdmin(admin.ModelAdmin): list_display = ("question_text", "pub_date") fieldsets = [ (None, {"fields": ["question_text"]}), ("Date information", {"fields": ["pub_date"]}), ] inlines = [ChoiceInline] admin.site.register(Question, QuestionAdmin)看到没有?admin的背后是Python对模型关系的完整表达,而不仅仅是一个数据管理工具。官方demo在这里埋了一个伏笔:模型设计得合理,后续页面、表单、管理后台全都顺;模型设计得乱,写多少代码都感觉在填坑。
admin还有一个容易踩的坑:如果你在模型里写了__str__,后台列表显示会优先用这个方法的返回值;如果你配置了list_display但没有把对应字段包含进去,或者字段名写错,页面就会出现ImproperlyConfigured之类的报错。遇到“后台显示异常”的问题,先查模型里的__str__和admin配置,往往能找到答案。
6. 视图、URL与模板:真正让demo“能用”的三板斧
模型和后台都就绪了,现在要做的是让普通用户不用登录admin也能使用这个投票应用。这就要讲Django最核心的请求处理链路了:URL路由映射到视图函数,视图函数拿到数据后交给模板渲染,最后返回HTML给浏览器。
6.1 URL路由:Django收到请求后的第一站
在项目的mysite/urls.py里,先把polls的路由引进来:
from django.contrib import admin from django.urls import include, path urlpatterns = [ path("polls/", include("polls.urls")), path("admin/", admin.site.urls), ]然后在polls应用下新建urls.py:
from django.urls import path from . import views app_name = "polls" urlpatterns = [ path("", views.index, name="index"), path("<int:question_id>/", views.detail, name="detail"), path("<int:question_id>/results/", views.results, name="results"), path("<int:question_id>/vote/", views.vote, name="vote"), ]include()的作用是把子路由交给polls应用自己管理。这样主urls.py只需要维护顶层路由,polls内部再怎么加页面,主文件都不用改。<int:question_id>是路径转换器,它会捕获URL里的数字部分,作为参数传给视图函数;如果URL对应位置不是数字,Django会直接放行到下一个匹配模式,最终都匹配不上就返回404。
app_name = "polls"是命名空间,以后模板里用{% url 'polls:detail' question.id %}这种带命名空间的反向解析,而不是硬编码URL字符串。这玩意看起来繁琐,但能让路由地址调整时不用满项目找链接,是真实项目里非常标准的做法。
6.2 视图函数:写逻辑之前先想清楚返回什么
视图函数的核心任务可以归纳成一句话:接收请求、处理业务、返回响应。官方demo的视图从一个最简单的HttpResponse开始:
from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. 这里是投票应用的首页。")但马上,我们希望它从数据库里拿真实数据。改写成:
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)order_by("-pub_date")表示按发布日期倒序排序,[:5]取前五条。render函数把模板和context数据合并,生成一个完整的HTML响应。注意:模板路径是polls/index.html而不是index.html,这是Django约定的模板命名空间机制,多个应用可能有同名模板,加上目录前缀能避免冲突。
detail视图在真实项目里通常还要处理“查不到记录”的情况:
from django.shortcuts import get_object_or_404, render from .models import Question def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, "polls/detail.html", {"question": question})用get_object_or_404可以替代“先查询再手动判断是否404”的样板代码。你当然可以用try/except手动捕获DoesNotExist,但get_object_or_404是一个更简洁的封装,而且意图清晰:如果记录不存在,直接返回404页面。
6.3 模板与静态文件:让页面看起来像正经应用
Django模板是HTML和模板语法的混合体。在polls/templates/polls/目录下创建index.html:
{% if latest_question_list %} <h1>最近的问题</h1> <ul> {% for question in latest_question_list %} <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li> {% endfor %} </ul> {% else %} <p>还没有任何投票问题。</p> {% endif %}模板标签的语法核心就四个:{% if %}、{% for %}、{% url %}、{{ variable }}。前两个控制逻辑,后两个输出内容。模板里不要写复杂业务逻辑,模板只负责展示。规则很简单:能放进视图函数计算的,就不要塞进模板。
detail.html可以先把当前问题文本显示出来:
<h1>{{ question.question_text }}</h1> <p>{{ question.pub_date }}</p>很多人第一次写好detail页面刷新后,发现页面非常简陋。那太正常了,Django默认不包含任何前端框架,你要自己写CSS。把CSS放进polls/static/polls/style.css,然后在模板顶部加上{% load static %},再引用{% static 'polls/style.css' %},页面就开始有风格了。静态文件这步在官方demo里属于进阶片段,但它和模板同样重要,后面任何项目都用得上。
6.4 加一个表单,demo彻底活起来
前面都是展示型页面,投票应用真正的高潮是表单交互:用户选中一个选项,点击投票,系统把票数加一,然后跳转到结果页。
先在detail.html里加表单:
<form action="{% url 'polls:vote' question.id %}" method="post"> {% csrf_token %} {% for choice in question.choice_set.all %} <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}"> <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label> {% endfor %} <input type="submit" value="投票"> </form>注意两点:method="post"表示提交后会修改服务器数据;必须带{% csrf_token %},这是Django防跨站请求伪造的内置机制,少了他会直接拒绝POST请求。这是新手最容易遗漏的地方,一写POST就收到403错误,然后满世界找原因。记住一句话就行:Django表单要提交POST,必须带csrf_token。
然后是vote视图:
from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.urls import reverse from .models import Choice, Question def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST["choice"]) except (KeyError, Choice.DoesNotExist): return render(request, "polls/detail.html", { "question": question, "error_message": "你没有选择任何选项。", }) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))request.POST["choice"]拿到用户选中的选项ID。如果用户压根没选,访问这个键会抛KeyError;如果选了个不存在的选项,会抛DoesNotExist。这两种情况都回到详情页并给出错误提示。正常情况下,把票数加一、保存,然后跳转到结果页——这就是经典的“POST后重定向”模式,能避免用户刷新页面导致重复提交。
最后是results视图和模板:
def results(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, "polls/results.html", {"question": question})<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }} —— {{ choice.votes }} 票</li> {% endfor %} </ul>到这一步,投票应用已经有了完整闭环:首页列问题、点进详情、投票、跳结果页。第一次跑通这个流程的感觉,我现在还记得:原来一个Web应用从数据到页面再到交互,并没有想象中那么玄乎,核心就是“模型提供数据,视图处理逻辑,模板负责展示”这三层之间的配合。
7. 从demo走向工程:测试、部署检查和继续学习的方向
官方demo的最后两课是自动化测试和部署。很多新手会直接跳过,觉得没必要。但我的判断是:demo阶段可以不追求“工程化得很彻底”,但至少要把测试和部署这两个词的真实含义搞清楚,因为你后面接触真实项目,早晚要面对它们。
7.1 给投票逻辑写测试,别让demo只停在“能跑”
Django自带测试框架,基于Python的unittest。在polls/tests.py里写一个最基础的测试:
import datetime from django.test import TestCase from django.utils import timezone from .models import Question def create_question(question_text, days): time = timezone.now() + datetime.timedelta(days=days) return Question.objects.create(question_text=question_text, pub_date=time) class QuestionModelTests(TestCase): def test_was_published_recently_with_future_question(self): future_question = create_question("未来的问题", days=30) self.assertIs(future_question.was_published_recently(), False)这里用到了was_published_recently这个模型方法,官方demo在后面的章节会补上它。如果你一路跟着文档走,文档会让你先尝试验证行为,再引导你写测试。我的建议是:写一个也行,写十个也行,关键是体会到“改模型、跑测试、看到红色或绿色”这套反馈循环。测试不是给项目贴金,是给你自己未来省时间。别急着追求测试覆盖率,先养成“测自己写过的重要逻辑”的习惯。
执行测试一条命令:
python manage.py test polls在输出里看到OK,那种踏实感,是“页面能跑”给不了的。
7.2 部署前必改的几个配置
开发阶段一切顺利,但如果你真的准备把它放到服务器上,有几个配置是必须改的:
DEBUG = False ALLOWED_HOSTS = ["your-domain.com"]DEBUG=False之后,代码报错时页面不会再返回堆栈信息,而是显示500错误页,避免暴露敏感配置。同时,静态文件不再由Django进程自动服务,需要执行:
python manage.py collectstatic把各应用里散落的静态文件收集到一个统一目录,再交给Nginx或CDN去托管。数据库也要从SQLite切到PostgreSQL或MySQL,这涉及连接配置变更。这些内容已经超出“demo搭建”的边界,但了解它们的存在很重要:它提醒你,本地能跑和线上能用之间,还差着不少配置和排查工作。
7.3 官方demo之后,下一步怎么走
搭完官方demo,你可能觉得还缺很多:没有用户注册和登录,没有好看的页面,没有API接口,没有缓存优化。这些都很正常,因为官方demo的目的本来就不是给你一个完整产品,而是让你拥有一个可以继续折腾的骨架。
我的个人建议是接下来做三件事。
第一,把官方教程的第二部分也过一遍,那里讲了自动化测试、静态文件、自定义后台,是demo的工程化延伸。
第二,拿这个投票应用练手加功能,比如加一个“创建问题”的前端页面,体验一次完整的CRUD闭环。加完你就知道,真实项目的复杂度是怎么从小功能里长出来的。
第三,把SQLite换成PostgreSQL。很多人一听换数据库就发怵,其实步骤就三样:装驱动、改DATABASES配置、重新migrate。能独立走完这一步,你对Django配置的信心会立刻不一样。
我写完这些内容最想说的其实是一句大白话:官方demo不是终点,甚至不是官方教程的终点,它是一个起点,是你真正开始“使用Django思考”的起点。踏踏实实把它从头到尾搭一遍,然后删掉,再搭一遍,你的Django地基会比绝大多数看视频跟着敲的人扎实得多。