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.pypath('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)

关键认知

  1. HTML 的 name 不是变量,是字段名——它告诉浏览器发送时叫什么
  2. 真正的传递载体是 HTTP 请求体——不是魔法通道直连 Python
  3. Django 是"翻译层"——把 HTTP 协议数据翻译成 Python 对象
  4. 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 的完整链路 理论

八、关键心法总结

  1. GET 拿数据,参数在 URL 里,幂等,可缓存。
  2. POST 交数据,参数在 body 里,非幂等,不缓存。
  3. HTML 的 name 是后端收数据的钥匙。 request.POST.get('name') 靠的就是它。
  4. HTTP 是传输层,Django 是翻译层,视图是应用层。 三层各司其职。
  5. CBV 的核心是 dispatch() 方法分发。 GET → get(),POST → post()。
  6. .get() 永远优于方括号。 key 不存在时返回默认值,不会炸。
  7. Python 导入磁盘文件,编辑器保存 ≠ 文件已更新。 Ctrl+S 是第一生产力。
  8. {% csrf_token %} 是 Django 的保镖。 POST 表单必须有它,否则 403。

九、下一步计划

  • HttpRequest:请求组成、标头及相关参数

今日总结: 从 FBV 巩固到 HTTP 协议深度理解,从第一个 HTML 模板到表单数据完整传递链路,再到 CBV 实战注册功能——Day2 的核心收获是搞清楚了"数据从浏览器到 Python 变量"这条链路上发生的每一件事。这是 Web 开发最本质的骨架,吃透之后学什么框架都不会迷路。💪


posted @ 2026-07-31 23:43  YiYezc  阅读(5)  评论(0)    收藏  举报