Django+Vue 学习日志(02视图实例)
PART02:视图层深入与表单交互
记录时间:2026年7月31日
环境:Fedora 44 + Python 3.12.13 + Django 5.2.16
前言
Day1 搞定了路由系统和嵌套路由,Day2 正式深入视图层。从 FBV 巩固到 HTTP 协议理解,从第一个 HTML 模板编写到表单数据的完整传递链路,再到 CBV 实战注册功能——今天的信息量很大,但每一步都踩得很实。
一、函数视图(FBV)巩固
Day1 一直在用 FBV,今天把它彻底理清楚。
request 对象里有什么
request 是 Django 自动传入的第一个参数,类型为 django.core.handlers.wsgi.WSGIRequest。
| 属性 | 类型 | 含义 |
|---|---|---|
request.method |
str |
请求方法(GET/POST/PUT/DELETE) |
request.path |
str |
请求路径 |
request.GET |
QueryDict |
GET 参数(URL 中的 ?key=value) |
request.POST |
QueryDict |
POST 表单数据 |
request.body |
bytes |
原始请求体 |
request.META |
dict |
请求头信息 |
request.user |
User 对象 |
当前用户 |
获取 URL 参数和查询参数
# URL 路径参数(来自 urls.py 的 <int:article_id>)
def article_detail(request, article_id):
pass # article_id 直接从参数拿
# 查询参数 ?page=2&sort=desc
def article_list(request):
page = request.GET.get('page', 1)
sort = request.GET.get('sort', 'asc')
return HttpResponse(f'page={page}, sort={sort}')
访问 /article/list/?page=2&sort=desc → 得到 page=2, sort=desc。
二、HTTP GET 与 POST 深入
GET 请求
浏览器地址栏输入 URL、点链接、搜东西 → 基本都是 GET。
http://127.0.0.1:8000/article/list/?page=2&sort=desc
| 特征 | 说明 |
|---|---|
| 参数在 URL 里 | ?page=2&sort=desc 直接暴露在地址栏 |
| 有长度限制 | 一般 2048 字符左右 |
| 可以被收藏 | URL 完整包含所有参数 |
| 可以被缓存 | 浏览器、CDN、代理都会缓存 |
| 幂等 | 执行多次结果一样,无副作用 |
| 语义上只读取 | 不应该修改数据 |
Django 接收:
def article_list(request):
page = request.GET.get('page', 1)
sort = request.GET.get('sort', 'asc')
return HttpResponse(f'page={page}, sort={sort}')
POST 请求
表单提交、登录、注册、下单 → 基本都是 POST。
<form action="/login/" method="post">
<input type="text" name="username">
<input type="password" name="password">
<button type="submit">提交</button>
</form>
| 特征 | 说明 |
|---|---|
| 参数在请求体里 | 不在 URL 中,地址栏看不到 |
| 没有长度限制 | 理论上想发多少发多少 |
| 不能被收藏 | 参数不在 URL 里 |
| 默认不缓存 | 浏览器不缓存 POST 结果 |
| 非幂等 | 多次执行可能产生不同结果 |
| 语义上用于修改 | 创建、更新、删除 |
Django 接收:
def login(request):
if request.method == 'POST':
username = request.POST.get('username', '')
password = request.POST.get('password', '')
return HttpResponse(f'用户{username}登录')
GET vs POST 对比
| 维度 | GET | POST |
|---|---|---|
| 参数位置 | URL | 请求体 |
| 可见性 | 地址栏可见 | 不可见 |
| 长度限制 | 有 | 无 |
| 缓存 | 可缓存 | 不缓存 |
| 收藏 | 可收藏 | 不可收藏 |
| 幂等性 | 是 | 否 |
| 语义 | 读取 | 修改 |
| 安全性 | 不适合敏感数据 | 相对适合(仍需 HTTPS) |
⚠️ POST 不是"安全"的代名词。不用 HTTPS 的话,POST 数据在网络上照样能被抓包。
三、第一个 HTML 模板
HTML 文件逐行分析
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录</title>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
<div>
用户名:
<input type="text" name="username" placeholder="请输入用户名">
</div>
<div>
密码:
<input type="password" name="password" placeholder="请输入密码">
</div>
<button type="submit">登录</button>
</form>
</body>
</html>
<!DOCTYPE html>
声明文档类型为 HTML5,告诉浏览器按 HTML5 标准解析。没有这行会进入"怪异模式"。
<html lang="zh-CN">
HTML 根标签。lang="zh-CN" 告诉屏幕阅读器和搜索引擎页面主要语言是中文。
<head> 里的内容(不显示在页面上)
| 标签 | 作用 |
|---|---|
<meta charset="UTF-8"> |
告诉浏览器用 UTF-8 解码,中文不乱码 |
<meta name="viewport" ...> |
移动端适配,页面宽度 = 设备宽度 |
<title> |
浏览器标签页显示的标题 |
<form> 表单容器
| 属性 | 作用 |
|---|---|
action="" |
提交到当前页面 URL |
method="post" |
用 POST 方式提交 |
{% csrf_token %} |
Django 防跨站请求伪造的隐藏字段 |
<input> 输入框
| 属性 | 作用 |
|---|---|
type="text" |
普通文本输入 |
type="password" |
密码输入(显示为圆点) |
name="username" |
提交给后端的键名,request.POST.get('username') 靠它 |
placeholder |
输入框为空时的灰色提示文字 |
<button type="submit">
提交按钮,点击后浏览器收集所有带 name 的字段,按 method 发送到 action。
关键标签总结
| 标签 | 是否成对 | 作用 |
|---|---|---|
<html> |
✅ | 根标签 |
<head> |
✅ | 给浏览器看的说明书 |
<body> |
✅ | 用户看到的内容 |
<form> |
✅ | 表单容器 |
<input> |
❌ 自闭合 | 输入框 |
<button> |
✅ | 按钮 |
<div> |
✅ | 块级容器,用于布局 |
<meta> |
❌ 自闭合 | 元信息 |
{% csrf_token %} |
❌ | Django 模板语法,渲染为隐藏 input |
四、表单数据传递的完整链路
从 HTML 到 views.py 的九步旅程
以用户输入 zhangsan 和密码 123456 为例:
第 0 步:HTML 里只有 DOM
<input name="username"> <!-- value 为空 -->
第 1 步:用户打字
浏览器更新 DOM 中 input 的 value:
document.querySelector('input[name="username"]').value === 'zhangsan'
此时还没跟服务器通信。
第 2 步:点击提交按钮
浏览器找到 <form>,遍历所有带 name 的控件,收集键值对:
[{ name: 'username', value: 'zhangsan' },
{ name: 'password', value: '123456' }]
第 3 步:编码成 HTTP 请求体
username=zhangsan&password=123456
组装完整 HTTP 请求:
POST /login/ HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Cookie: csrftoken=xxx...
username=zhangsan&password=123456
第 4 步:请求到达 Django
WSGI 服务器接收原始字节流。
第 5 步:Django 创建 WSGIRequest 对象
解析请求行、请求头、请求体。
第 6 步:解析请求体,填充 request.POST
Django 内部使用 urllib.parse.parse_qs() 解析:
'username=zhangsan&password=123456'
↓
QueryDict: {'username': ['zhangsan'], 'password': ['123456']}
第 7 步:URL 匹配,找到视图函数
urls.py 中 path('login/', views.login) → 调用 login(request)。
第 8 步:Django 调用你的视图
def login(request):
# request 是 Django 刚创建好的 WSGIRequest 对象
...
第 9 步:request.POST.get() 取出数据
request.POST.get('username', '')
# 从 QueryDict 里找 key='username'
# 返回字符串 'zhangsan'
# 赋给局部变量 username(类型 str)
完整链条一图流
用户键盘输入 'zhangsan'
↓
<input name="username"> 的 value 变成 'zhangsan'
↓
点击 submit,浏览器收集所有带 name 的表单元素
↓
编码成 'username=zhangsan&password=123456'
↓
作为 HTTP POST 请求体发送给服务器
↓
Django 接收原始字节流
↓
WSGIRequest 对象解析请求体
↓
QueryDict 把字符串解析成 {'username': ['zhangsan']}
↓
request.POST.get('username', '') 取出字符串 'zhangsan'
↓
username = 'zhangsan'(Python 局部变量,类型 str)
关键认知
- HTML 的
name不是变量,是字段名——它告诉浏览器发送时叫什么- 真正的传递载体是 HTTP 请求体——不是魔法通道直连 Python
- Django 是"翻译层"——把 HTTP 协议数据翻译成 Python 对象
request对象是链路的终点——你写视图时面对的就是它
五、类视图(CBV)实战
RegisterView 完整代码
# account/views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.views import View
def login(request):
if request.method == 'POST':
username = request.POST.get('username', '')
password = request.POST.get('password', '')
return HttpResponse(f'用户{username}的密码是{password}')
elif request.method == 'GET':
return render(request, 'login.html')
class RegisterView(View):
def get(self, request):
return render(request, 'register.html')
def post(self, request):
username = request.POST.get('username', '')
password = request.POST.get('password', '')
confirm_password = request.POST.get('confirm_password', '')
if password != confirm_password:
return HttpResponse('两次输入的密码不一致')
else:
return HttpResponse(f'用户{username}的密码是{password}')
路由配置
# account/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('register/', views.RegisterView.as_view(), name='register'),
]
CBV 的执行流程
请求 → as_view() → dispatch() → 根据 method 分发 → get()/post()/put()/delete()
dispatch() 是 CBV 的核心方法,它根据 HTTP 方法决定调用哪个类方法。后面继承 DRF 的 APIView 时,重载 dispatch() 是很常见的定制手段。
CBV vs FBV 选择
| 场景 | 推荐 |
|---|---|
| 简单接口、快速验证 | FBV |
| RESTful API(同路径多方法) | CBV |
| 需要继承和复用逻辑 | CBV |
| 中间件式处理(认证、限流) | CBV |
.as_view() 为什么必须有
path('register/', views.RegisterView.as_view()) # ✅
path('register/', views.RegisterView()) # ❌ TypeError
as_view() 返回一个可调用的视图函数,Django 的 URL 系统只认可调用对象。
六、踩坑记录
| 坑 | 原因 | 解决 |
|---|---|---|
AttributeError: module 'account.views' has no attribute 'RegisterView' |
views.py 修改后未保存,Python 导入的是磁盘旧文件 |
按 Ctrl+S 保存后重启 runserver |
HTML 中 placeholder 引号未闭合 |
"请输入用户名> 少了结尾 " |
补全为 "请输入用户名" |
| POST 表单提交 403 Forbidden | 模板中缺少 {% csrf_token %} |
在 <form> 内添加 {% csrf_token %} |
request.POST['username'] 抛出 KeyError |
用方括号取值,key 不存在时直接抛异常 | 改用 .get('username', '') 给默认值 |
CBV 路由写成 RegisterView() |
忘了 .as_view(),直接实例化类 |
改为 RegisterView.as_view() |
关于"没保存文件"的终极教训
今天这个坑踩了三次(Day1 的 HttpResponse、Day2 的 RegisterView),必须刻进 DNA:
Python 导入的是磁盘上的文件,不是编辑器里你看到的内容。
编辑器没保存 = 磁盘没变 = Python 看不到你的修改。
验证方法:
# 在文件顶部加一行
print("=== views.py loaded ===")
如果控制台没打印,说明加载的不是你正在看的文件。
七、今日成果
| 功能 | 方式 | 状态 |
|---|---|---|
| 登录页面(GET 返回表单 + POST 处理数据) | FBV | ✅ |
| 注册页面(GET 返回表单 + POST 校验密码) | CBV | ✅ |
| 第一个 HTML 模板(login.html) | Django 模板 | ✅ |
| HTTP GET/POST 理解 | 理论 + 实践 | ✅ |
| 表单数据从 HTML 到 views 的完整链路 | 理论 | ✅ |
八、关键心法总结
- GET 拿数据,参数在 URL 里,幂等,可缓存。
- POST 交数据,参数在 body 里,非幂等,不缓存。
- HTML 的
name是后端收数据的钥匙。request.POST.get('name')靠的就是它。- HTTP 是传输层,Django 是翻译层,视图是应用层。 三层各司其职。
- CBV 的核心是
dispatch()方法分发。 GET → get(),POST → post()。.get()永远优于方括号。 key 不存在时返回默认值,不会炸。- Python 导入磁盘文件,编辑器保存 ≠ 文件已更新。 Ctrl+S 是第一生产力。
{% csrf_token %}是 Django 的保镖。 POST 表单必须有它,否则 403。
九、下一步计划
- HttpRequest:请求组成、标头及相关参数
今日总结: 从 FBV 巩固到 HTTP 协议深度理解,从第一个 HTML 模板到表单数据完整传递链路,再到 CBV 实战注册功能——Day2 的核心收获是搞清楚了"数据从浏览器到 Python 变量"这条链路上发生的每一件事。这是 Web 开发最本质的骨架,吃透之后学什么框架都不会迷路。💪

浙公网安备 33010602011771号