Django 这个框架,在我个人做 Web 开发的这些年里,一直觉得它是把“后端入门”这件事讲得最明白的一个。它不像某些框架一样要你手动拼接一堆路由和数据库驱动,而是把项目结构、模板逻辑、数据库操作、后台管理这些基础能力一次性摆在你面前。这篇 Django 基础入门教程(第一篇),我不打算给你堆概念,而是直接带着你从零跑通一条完整链路:装好环境、创建项目、写第一个视图、套上模板、把数据库用起来。也就是说,只要你能看懂 Python 的基础语法,比如函数、类、import,剩下的事情跟我敲命令就行。读完这篇,你会拥有一个真正能运行、能加数据、能渲染到网页上的小站点,而不是停留在“装完就跑了个欢迎页”的阶段。
Django 适合什么样的人?我觉得三类人最合适:一是刚开始学后端、想找个完整框架练手的 Python 初学者;二是以前用 Flask 这类微框架写过一些小接口,现在想体验“全家桶”式开发的项目型选手;三是想快速搭一个内部工具、内容展示或后台管理系统的开发者。想真正掌握 Django,第一篇的关键就是把“项目的骨架怎么搭”“一条请求从浏览器到数据库再返回页面经历了什么”这两件事想清楚。后面很多东西都是在这个基础上堆功能的。
1. 动手之前,先把 Django 的定位和准备工作搞清楚
1.1 为什么很多人把 Django 当成后端第一站
Django 来自一个在线新闻编辑室项目,所以它从诞生起就很强调几个东西:内容发布、后台管理、安全防护、快速迭代。这也决定了它的气质——它是一个“自带电池”的全栈框架,翻译成人话就是:你能想到的 Web 开发常见需求,它都帮你内置了一套解决方案。
打个比方,如果用 Flask 这类微框架做后端,就像你拿到一套毛坯房,水管、电线、门窗都要自己找人来装;而 Django 更像一套精装交付的三居室,墙面刷好了、水电通了、衣柜和橱柜也打好了,你要做的只是往里面摆自己喜欢的家具。这里说的“衣柜橱柜”,就是 Django 自带的这些能力:
- 自带 ORM,可以把 Python 类直接映射成数据库表,不用手写 SQL;
- 自带 Admin 后台,数据模型写好后,一个命令就能获得增删改查的管理界面;
- 自带模板系统,可以在 HTML 里写变量和逻辑标签;
- 自带 URL 路由设计,一个入口文件管理所有请求分发;
- 内置 CSRF、XSS、SQL 注入等基础安全防护。
我经常跟别人说,Django 的核心思路是“约定优于配置”。它规定好了项目的目录长什么样、请求怎么走、配置写在哪个文件,你不用每天纠结结构问题,而是把所有精力放在业务逻辑上。对于新手,这种“被安排得明明白白”的感觉非常友好,因为你先看到了一个成熟 Web 框架解决问题的标准姿势,以后再去看别的框架,就有对比、有参照了。
1.2 环境准备:Python 版本、虚拟环境、安装命令
Django 是一个 Python 包,所以前提是你电脑上有 Python。我建议用Python 3.10 或更高版本,太老的版本跑新版 Django 会直接报错。如果你电脑上装了多个 Python 版本,建议在命令行先确认一下默认版本:
python --version如果显示 3.8 或者更老,建议先去官方下载页装一个新版本,安装时记得勾选“Add Python to PATH”。这一步非常重要,很多人后面提示“python 不是内部或外部命令”,基本都是因为安装时没把 Python 加进 PATH。
接下来是虚拟环境。虚拟环境的本质,就是给当前项目单独开一个独立的 Python 包运行空间。这样你项目 A 用 Django 4.2,项目 B 用 Django 5.0,互不干扰;而且以后换电脑、更新依赖时,只要看一个 requirements.txt 就能完整恢复环境。我见过不少初学者图省事直接全局安装,一开始没事,等换了几个项目后,各种版本冲突能把人磨到崩溃。所以从第一篇开始,养成用虚拟环境的习惯很值得。
mkdir django_basic cd django_basic python -m venv venvWindows 下激活命令:
venv\Scripts\activatemacOS / Linux 下激活命令:
source venv/bin/activate激活成功后,命令行前面会出现一个(venv)前缀,这就代表你已经进入虚拟环境了。之后在这个终端里执行的 pip 安装,都会装进这个独立环境里。
然后安装 Django:
pip install django如果你想固定版本,防止未来某个大版本更新后代码不兼容,可以这样装:
pip install "django==5.0.6"装完验证一下:
python -m django --version能看到版本号,说明环境已经打通。到这里,基础准备工作就做完了,下一步我们来创建第一个项目。
注意:如果你用的是 conda 之类的环境管理器,也可以直接
conda create -n django_basic python=3.11然后pip install django,思路是一样的,保证“项目依赖”和“全局环境”隔离就行。
2. 创建第一个 Django 项目:骨架文件逐个拆解
2.1 startproject 与那个容易忽略的“点”
Django 提供了一个命令行工具来生成项目骨架,这里我们用一个实际项目名来演示,比如myblog:
django-admin startproject myblog .注意末尾那个点.。很多人第一次创建项目时会漏掉它,结果生成了两层嵌套目录,运行命令时总搞不清楚manage.py到底在哪。这个点代表“在当前目录直接生成项目文件”,生成的目录结构大概是这样的:
django_basic/ ├── venv/ ├── manage.py └── myblog/ ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py如果你不加那个点,会变成myblog/myblog/...,多一层外壳目录。虽然也能用,但日常开发中大家更习惯让manage.py就在项目根目录下。
先不急着改代码,我们把服务跑起来看看:
python manage.py runserver然后浏览器打开http://127.0.0.1:8000/,会看到一个默认的欢迎页,火箭图标加“The install worked successfully”。看到这个页面,说明你的项目骨架已经能正常工作了。
2.2 自动生成的核心文件到底负责什么
很多人跑通欢迎页之后,面对这一堆文件还是很懵。我建议把这几个文件当成“公司里的不同部门”来理解:
| 文件 | 作用 | 日常开发接触频率 |
|---|---|---|
manage.py | 项目操作入口,相当于命令行管理员,所有管理命令都通过它执行 | 每次开发都用 |
settings.py | 全局配置文件,数据库、应用注册、模板、语言时区都在这里改 | 高 |
urls.py | 全局 URL 分发器,相当于前台接线员,决定不同地址交给谁处理 | 高 |
wsgi.py/asgi.py | 部署时用到的服务器接口,本地开发很少动它 | 极低 |
__init__.py | 标记这是一个 Python 包,平时不用改 | 极低 |
manage.py是你接触最多的一个文件。比如启动开发服务器是python manage.py runserver,创建应用是python manage.py startapp,操作数据库是python manage.py makemigrations,它就像项目的“总遥控器”。而settings.py则是整个项目的中央配置中心,很多让你捉摸不透的怪问题,最后追根溯源都跟这里的配置有关。
2.3 settings.py 里最关键的几个配置项
打开myblog/settings.py,不用全看懂,先抓住这几个核心配置即可:
INSTALLED_APPS:一个列表,里面是当前项目启用的所有应用和内置组件。你每新建一个应用,都要手动把应用名加到这里,否则 Django 不知道这个应用的存在,很多命令都会报错“No changes detected”。DATABASES:默认是 SQLite3 的配置。新手阶段用 SQLite 最合适,因为它是文件型数据库,一个.db文件就能存储全部数据,不用额外安装数据库服务端。等以后数据量大、需要高并发写入时,再改成 MySQL 或 PostgreSQL,代码层面基本不用动,改改配置就好了。LANGUAGE_CODE和TIME_ZONE:默认分别是en-us和UTC。建议一上来就改成zh-hans和Asia/Shanghai,否则以后 Admin 后台是英文界面,时间记录也和北京时间对不上。TEMPLATES:模板引擎配置。这里有个DIRS列表,如果你想让 Django 在全局模板目录、而不是应用各自模板目录里找模板,需要把路径加进去。STATIC_URL:静态资源(CSS、JS、图片)的 URL 前缀,默认/static/。静态文件是否加载得出来,新手阶段最容易在这里和目录结构上出问题。
我把常用开头配置给你贴一下,作为参考模板:
LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'books', # 这里放以后自己创建的应用名 ]这里要注意,应用注册列表里books必须放在内置应用后面,顺序不影响功能,但代码整洁性上好看一些。改完配置后重启runserver,刷新页面时,Django 会自动加载新的配置。
3. 从 URL 到视图:写第一个自己决定的页面
3.1 先理解 MTV 模式的请求链路
很多人一上来就被 MVC、MTV 这些缩写搞晕。其实可以把它理解成一次点餐流程:
- 你(浏览器)在菜单上选了一个菜(URL 地址);
- 服务员(URL 路由)把订单送到后厨;
- 厨师(视图函数)根据订单开始做菜,需要食材就去仓库拿(数据库);
- 菜做好后端上来(渲染成 HTML 页面),你直接吃。
Django 里的 MTV 其实和传统 MVC 是一一对应的概念,只是叫法不同:
| Django 术语 | 传统 MVC 称呼 | 对应职责 |
|---|---|---|
| Model | Model(模型) | 负责数据和业务规则,对应数据库表 |
| Template | View(视图表现层) | 负责展示页面,对应 HTML 模板 |
| View | Controller(控制器) | 负责业务逻辑,接收请求后决定响应什么 |
所以“视图”这个名字在 Django 里是业务控制器,不是页面的视觉设计。这条链路你一定要记住:浏览器请求某个 URL → 路由匹配到对应视图函数 → 视图函数从模型取数据 → 把数据交给模板渲染 → 返回给浏览器。
3.2 创建应用并写第一个视图
一个 Django 项目可以包含多个应用,每个应用相当于一个业务模块。比如我们做一个“读书笔记”站点,可以先创建一个books应用:
python manage.py startapp books执行后,项目目录中会多出一个books/文件夹,里面有models.py、views.py、admin.py等文件。注意,此时books还没有被项目“注册”,我们需要回到settings.py的INSTALLED_APPS中填入books。
然后在books/views.py中写一个最简单的视图函数:
from django.http import HttpResponse def index(request): return HttpResponse("Hello, Django!")每个视图函数至少接收一个request参数,表示当前的 HTTP 请求对象。它必须返回一个HttpResponse对象,Django 才会把它作为网页响应发送给浏览器。
3.3 配置 URL 路由
有了视图函数,还需要在 URL 配置里告诉 Django:“当用户访问某个地址时,请调用这个函数”。先改项目根目录的myblog/urls.py:
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('books.urls')), ]然后新建books/urls.py:
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), ]完成之后,访问http://127.0.0.1:8000/,浏览器里显示的就是你自己写的字了。
这里面有两点值得说明:
path('', views.index, name='index')中的空字符串表示网站根路径;name参数可以理解为给这个路由起的“别名”,后面在模板里用{% url 'index' %}生成链接时,靠的就是这个别名。- 主路由用
include('books.urls')把books应用下的子路由拉进来。这样做的好处是,以后应用多了,每个应用只管自己的路由,互不干扰,主路由文件始终很清爽。
3.4 带参数的 URL:让同一个视图处理不同内容
实际开发中,你会遇到很多类似“查看 id 为 3 的书籍详情”这样的需求。Django 路由支持参数传递,非常直观:
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), path('book/<int:book_id>/', views.book_detail, name='book_detail'), ]其中<int:book_id>表示这里匹配一个整数,并把值作为名为book_id的参数传给视图函数:
def book_detail(request, book_id): return HttpResponse(f"你正在查看书籍,编号是:{book_id}")你可以访问http://127.0.0.1:8000/book/5/来验证效果。<int:xxx>中的int是路径转换器,意思是“要求这里必须是整数”;类似的还有<str:xxx>匹配字符串、<slug:xxx>匹配短横线文本。合理使用这些转换器,能把很多参数校验的工作交给路由层,而不用在视图函数里自己写正则判断。
4. 模板系统:让页面真正变成用户看到的网页
4.1 模板放哪里,Django 怎么找到它
视图函数里直接返回字符串,这种写法只适合演示。真实项目里,页面长什么样应该由 HTML 模板决定,视图只负责把数据塞进模板。Django 在创建项目时,默认不会创建一个全局的模板目录,所以我习惯在项目根目录下手动建一个templates文件夹。然后把路径告诉 Django:
# settings.py import os TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'], 'APP_DIRS': True, ... }, ]注意BASE_DIR是 Django 自动定义的项目根目录,BASE_DIR / 'templates'这种写法在 Python 3.6+ 里可以直接通过/拼接路径,比os.path.join简洁太多。这里如果给DIRS填入路径,APP_DIRS保持True也没关系,两者的关系是:Django 会先到DIRS指定的目录找,找不到再去每个应用的templates子目录里找。
4.2 模板语法三件套:变量、过滤器、标签
模板文件本质上是“HTML + Django 模板语法”。最常用的是这三类:
变量:用双层花括号包裹,比如{{ book.title }},表示输出一个变量的值。如果变量是字典,也可以用点号访问键。
过滤器:对变量做简单处理,比如{{ book.publish_date|date:"Y-m-d" }}把日期格式化成指定样式,{{ title|default:"未命名" }}在变量为空时显示默认值。过滤器可以链式使用:{{ text|truncatechars:20|linebreaks }},先截断 20 个字符,再把换行转成<p>标签。
标签:用{% %}包裹,表示控制逻辑。最典型的就是if和for:
{% if book.is_read %} <p>这本书已经读完了</p> {% else %} <p>还在待读清单里</p> {% endif %} {% for book in book_list %} <li>{{ book.title }} —— {{ book.author }}</li> {% empty %} <li>暂无书籍</li> {% endfor %}{% empty %}是 Django 模板里一个很贴心的设计,当循环对象为空时执行,不用像 Python 里那样先判断再循环。
4.3 写一个带数据的页面:
我们继续在books/views.py里改造:先用一个 Python 列表充当数据,后面第五部分再换成数据库里的真实数据。
from django.shortcuts import render def index(request): books = [ {'title': 'Python编程:从入门到实践', 'author': 'Eric Matthes'}, {'title': '流畅的Python', 'author': 'Luciano Ramalho'}, {'title': 'Django 3.0 实战', 'author': 'William S. Vincent'}, ] context = { 'book_list': books, } return render(request, 'books/index.html', context)这里用render替代了之前的HttpResponse。render做了三件事:加载指定模板、把 context 中的数据渲染进去、返回一个完整的 HttpResponse。context就是传给模板的字典,模板里通过键名访问。
在templates/books/index.html里写:
<!DOCTYPE html> <html> <head> <title>我的读书清单</title> </head> <body> <h1>待读与已读</h1> <ul> {% for book in book_list %} <li>{{ book.title }} —— {{ book.author }}</li> {% empty %} <li>还没有记录任何书籍</li> {% endfor %} </ul> </body> </html>刷新首页,你就能看到自己的第一个动态页面了。虽然它的数据还是硬编码在 Python 列表里,但已经完整跑通了“视图 → 模板 → 浏览器”这一层。
4.4 模板继承:一个减少重复的利器
实际页面通常有统一的头部、导航、底部信息,如果每个页面都复制粘贴一份,改一行导航信息就能让你改到崩溃。模板继承就是来解决这个问题的。
先建一个templates/base.html:
<!DOCTYPE html> <html> <head> <title>{% block title %}我的站点{% endblock %}</title> </head> <body> <header> <nav>网站导航,首页 | 书籍列表 | 关于我们</nav> </header> <main> {% block content %} {% endblock %} </main> <footer> <p>版权信息</p> </footer> </body> </html>然后让页面模板继承它:
{% extends "base.html" %} {% block title %}书籍列表{% endblock %} {% block content %} <h1>我的读书清单</h1> <ul> {% for book in book_list %} <li>{{ book.title }} —— {{ book.author }}</li> {% endfor %} </ul> {% endblock %}{% extends "base.html" %}必须写在模板的第一行,表示“我是基于这个基础模板的”。{% block %}就是可以被子模板覆盖的“坑位”。这种模式一旦用顺了,你会发现在 Django 里搭页面变得特别快,核心页面只需要关注内容区即可。
4.5 静态文件:CSS、JS 放哪里
模板里要加载 CSS 和 JS,需要把静态文件放在正确的位置,并用特定语法引入。我一般在应用目录下创建books/static/books/css/style.css,然后在模板顶部加载静态文件标签:
{% load static %} ... <link rel="stylesheet" type="text/css" href="{% static 'books/css/style.css' %}">{% static %}标签会根据STATIC_URL生成正确的静态文件地址。这里最容易犯的错有两类:一是文件放错了目录,Django 默认只在每个应用的static目录下找;二是忘了写{% load static %},模板直接报Invalid block tag。这两点我在自己带的人身上见过太多次了。
5. 模型与数据库:让页面真正有数据可读
5.1 ORM 到底帮我们做了什么
很多初学者一听到“数据库”就有点紧张,觉得自己 SQL 没学好是不是就做不了 Web 开发。Django 的 ORM 可以让你暂时放下这种焦虑。ORM 全称是对象关系映射,通俗讲,它相当于一个翻译官:你把 Python 类写出来,它负责翻译成对应的 SQL 建表语句;你对类属性的操作,它翻译成对应的 SQL 增删改查语句。
这意味着,在开发阶段你可以完全用 Python 的思维方式操作数据库。等以后遇到复杂查询、性能瓶颈时再回头学 SQL 也会容易很多,因为那时候你已经理解了数据模型本身。
5.2 定义第一个模型
在books/models.py中定义一个书籍模型:
from django.db import models class Book(models.Model): title = models.CharField("书名", max_length=200) author = models.CharField("作者", max_length=100) pages = models.IntegerField("页数", default=0) is_read = models.BooleanField("是否已读", default=False) created_at = models.DateTimeField("创建时间", auto_now_add=True) def __str__(self): return f"《{self.title}》- {self.author}"每个模型类继承models.Model,类属性对应数据库字段。各字段类型的含义很直白:
CharField变长字符串,必须给max_length;IntegerField整数;BooleanField布尔值;DateTimeField日期时间,auto_now_add=True表示记录创建时自动写入当前时间;__str__方法决定这个对象在后台列表、控制台打印时的显示文本。
提示:
CharField里的第一个字符串参数是“字段的人类可读名称”,里直接写中文没问题,Django 对 Unicode 支持很好。
5.3 迁移:为什么不是直接建表
写好了模型,很多人下意识觉得“数据库表是不是已经建好了?”答案是没有。Django 采用“迁移”机制,两步走:
python manage.py makemigrations:根据模型生成一个迁移文件(可以理解成“建表计划”),这个文件会被记录在应用的migrations/目录下;python manage.py migrate:真正把这些计划应用到数据库里。
为什么要分两步?因为迁移文件是文本文件,可以放进版本控制里。协作开发时,别人拉取代码后执行一次migrate,就能把你新增的表建好,全程不用手工去生产数据库敲 SQL。这也是 Django 从设计理念上就很强调的一点:数据库结构应该跟随代码版本一起演进。
实际操作:
python manage.py makemigrations python manage.py migrate第一次执行完migrate之后,项目中会多出一个db.sqlite3文件,那就是整个开发数据库。Django 默认的表(用户、权限、会话等)也会一并建好。
5.4 用 Django Shell 操作数据
数据库表建好了,怎么往里面加数据?最简单的方式就是用 Django 自带的交互式 Shell,它是带项目环境的 Python 终端:
python manage.py shell然后输入:
from books.models import Book book = Book(title="Django基础入门", author="某开发者", pages=220, is_read=False) book.save()save()是创建单条记录的标准方法,Python 里创建对象没有被自动提交到数据库,必须调用save()。你还可以一次性处理多条数据:
Book.objects.create( title="Python网络爬虫实战", author="A同学", pages=310, is_read=True ) # 查询所有 all_books = Book.objects.all() for b in all_books: print(b.title, b.author) # 过滤查询 unread_books = Book.objects.filter(is_read=False) # 获取单条 first_book = Book.objects.get(id=1)Book.objects是模型的“管理器”,它提供了all()、filter()、get()、create()这些方法,分别对应查询全部、条件查询、查单条、创建记录。等以后学到复杂查询,你会发现这个管理器几乎覆盖了日常 90% 的数据需求。
5.5 把数据库数据渲染到页面
现在把视图里的硬编码列表换成数据库真实数据:
from django.shortcuts import render from .models import Book def index(request): book_list = Book.objects.all().order_by('-created_at') return render(request, 'books/index.html', {'book_list': book_list})刷新页面,你之前通过 Shell 写入的几条数据就会出现在列表里。再加一条数据试试,页面会跟着更新。到这个阶段,第一篇的核心链路已经全部打通:请求进来、路由分发、视图处理、模型查库、模板渲染、返回响应。
6. 新手最常见的坑与排查经验
6.1 端口被占用:runserver 起不来
你运行python manage.py runserver时报错 “Error: That port is already in use”,这是最常见的开局坑。多数情况是上一个开发服务器没有关干净,或者 8000 端口被其他程序占了。
处理方式很简单,换一个端口启动:
python manage.py runserver 8001如果你经常遇到这个问题,还可以在 Windows 上执行netstat -ano | findstr :8000,Linux/macOS 上执行lsof -i:8000,找到占用进程的 PID 后结束它。不过我更推荐直接换端口,毕竟本地开发阶段,端口不重要,跑起来才重要。
6.2 TemplateDoesNotExist:模板明明在,却找不到
Django 报这个错,大部分情况下不是模板真的不存在,而是搜索路径不对。排查顺序是:
- 确认模板文件的后缀和路径是否正确,比如你视图里写的是
books/index.html,那文件必须在某个templates目录下的books/index.html; - 如果放在了项目根目录的
templates文件夹,确认settings.py的DIRS是否配置了BASE_DIR / 'templates'; - 确认应用是否出现在
INSTALLED_APPS里,APP_DIRS模式下,应用没有注册,Django 是不会去它的templates目录找文件的; - 改完
settings.py后,是否重启了开发服务器。
这里有一个我踩过坑的细节:Django 的模板搜索按顺序进行,如果多个应用有同名模板文件,它只会加载第一个找到的。所以给应用内模板加一层以应用名命名的子目录,比如books/index.html而不是直接index.html,是个很好习惯。
6.3 时区警告和中文乱码
启动项目时如果冒出 “WE ARE IN THE YEAR ... You may need to set TIME_ZONE” 这类提示,意思是当前用的是默认 UTC 时间,本地时间会不准。按之前说的,在settings.py里设置:
LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai'然后重启服务器,警告就消失了,后台界面也会变成中文。
如果你以后把数据库切到 MySQL,中文乱码大概率是字符集问题。Django 连接 MySQL 时,默认使用的编码取决于配置,建议在建库时指定utf8mb4。不过在 SQLite 阶段基本不用关心这个,SQLite 的 Unicode 支持很省心。
6.4 表单提交 403
新手第一次写表单时,POST 提交几乎必遇 403 Forbidden。这个不是服务器拒绝你访问,而是 Django 的 CSRF 防护机制在起作用。Django 默认要求所有 POST 请求必须携带一个 CSRF 令牌,为了确认请求是来自本站页面。
模板里的表单只要加一行:
{% csrf_token %}就能解决。如果你正在用 Django 写 API 接口而不是传统表单页面,可以在视图上加@csrf_exempt装饰器,但初学阶段不建议这么做——CSRF 防护是你适应“安全和开发效率如何平衡”的第一个真实案例。
6.5 改了模型但数据库没变化
这里说的是:你改了models.py里的某个字段,然后直接刷新页面或重启服务器,发现表结构还是旧的样子。
Django 不会像某些脚本框架那样自动同步模型和数据库。你改了模型类,必须重新执行:
python manage.py makemigrations python manage.py migrate如果运行makemigrations时提示 “No changes detected”,先确认两件事:一类是否在INSTALLED_APPS中注册;二、类的代码是否真的保存了。很多时候改完没有保存,或者应用没注册,Django 压根看不到你的修改。
6.6 runserver 自动刷新:别迷信用热加载
开发服务器默认会在代码变化时自动重载,你改完views.py保存后,服务器会自动重启,页面刷新就能看到效果。但有些情况它不会自动生效,比如新增了模板目录下的文件、改了settings.py中的某些配置。我习惯是:搞不清楚为什么没生效时,直接 Ctrl+C 停掉,然后重新runserver。重启大法在 Django 开发阶段几乎是万能解药。
这篇 Django 入门教程,我写了很多容易踩的细节,因为我自己就是从这些问题里爬出来的。当年我第一个 Django 项目跑通时,其实只做了一件简单的事:把一个表格从数据库里取出来,渲染到网页上。但就是这么个小功能,把 MTV 模式、ORM、模板系统、URL 路由全都串起来了。
我个人的建议是,学 Django 不要贪多,先把今天这条链路反复跑几遍,直到你能不看教程写出一个“列表页”为止。下一篇我会接着讲 Admin 后台、表单、用户登录和分页这些进阶内容。练熟这篇,再看后面就会轻松很多。