Django View 详解(二)

状态保持

概念

浏览器请求服务器是无状态的

无状态原因:浏览器与服务器是使用Socket套接字进行通信的,服务器将请求结果返回给浏览器之后,
会关闭当前的Socket连接,而且服务器也会在处理页面完毕之后销毁页面对象。

有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等

状态保持(State management)在 Web 开发中指的是在用户和服务器之间维持某种形式的状态或会话。在传统的 HTTP 协议中,每个请求都是无状态的,即服务器不会记住之前请求的任何信息。因此,状态保持机制对于创建交互式和个性化的用户体验至关重要。

状态保持的方法

  1. Cookies:
    • 小型数据文件存储在用户的浏览器中,每次请求时都会发送回服务器。可以用于跟踪用户会话。
    • Django 提供了对 cookies 的读写支持,通过 request.COOKIES 和 response.set_cookie()
  2. Session(会话):
    • 会话是一种高级状态保持机制,数据通常存储在服务器端,并使用一个唯一的会话 ID 在客户端标识。
    • Django 框架内置了会话处理功能,使用 request.session 附加数据到用户会话。
  3. Token(令牌):
    • 常用于 RESTful APIs,通过客户端每次发送 token 来验证用户身份。常见实现方式有 JWT(JSON Web Tokens)。
    • 由于其无状态特性,令牌适合于跨域和微服务架构。

特点对比

  • Cookies: 适合简单的客户端存储,数据量少且可能会被用户修改。
  • Session: 适合在服务器端存储用户相关的数据,更安全。
  • Token: 适合无状态的分布式系统和 API, 提供灵活且安全的身份验证。
Django View 详解(二)

Cookies

定义:
Cookie 是在 Web 开发中用于在用户和服务器之间保持状态的一种机制。它们是由服务器生成并存储在用户的浏览器中,然后在后续请求中发送回服务器。Cookie 是为了解决 HTTP 协议的无状态特性而设计的。

特征:

  • 存储位置: Cookies 存储在客户端(用户的浏览器)中。
  • 大小限制: 每个 cookie 大小通常限制在 4KB。
  • 有效期: Cookies 可以设置有效期,可以是会话 cookie(浏览器关闭后消失)或者持久 cookie(有指定的过期日期)。

应用:

  • 状态保持: 用于跟踪用户的登录状态、用户偏好等。
  • 跟踪: 使用第三方 cookies 进行广告跟踪和分析。

Cookie 的流程

1、服务器发送 Cookie:

  • 当客户端(如浏览器)第一次请求服务器(如访问网站)时,服务器可以将 cookie 设置在响应中,通过返回的 HTTP 响应中的 Set-Cookie 头将 Cookie 设置到客户端。
  • 格式示例:Set-Cookie: session_id=abcd1234; Expires=Wed, 09 Jun 2023 10:18:14 GMT; Path=/

2、客户端存储 Cookie:

  • 客户端浏览器接收到 Set-Cookie 后,会将 Cookie 存储到本地,再次请求同一服务器时,自动包含该 Cookie。
  • Cookie 存储于浏览器的内存或文件中,取决于 Cookie 的生命周期设置(会话或持久)。

3、客户端发送 Cookie:

  • 在后续请求中,浏览器会将存储的 Cookie (与请求路径和域名匹配的 Cookie)通过 Cookie 头发送给服务器。格式示例:Cookie: session_id=abcd1234

Cookie 的属性

  • 名称和值: 每个 Cookie 具有唯一名称和值,服务器通过它标识和存储用户信息。
  • Expires/Max-Age: 定义 Cookie 的有效期,会话 Cookie 在关闭浏览器后失效,持久 Cookie 则有指定的过期时间。
  • Path: 指定 Cookie 的生效路径。
  • Domain: 指定 Cookie 的生效域名。
  • Secure: 仅通过 HTTPS 传输 Cookie,确保在不安全连接时不发出。
  • HttpOnly: 通过设置此属性,保护 Cookie 不被 JavaScript 访问,从而提高安全性。

查看浏览器的 cookies:

Django View 详解(二)

在 Django 中使用 cookies

urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('set-cookie/', views.set_cookie, name='set_cookie'),
    path('get-cookie/', views.get_cookie, name='get_cookie'),
    path('delete-cookie/', views.delete_cookie, name='delete_cookie'),
]

views.py

from django.http import HttpResponse

# 创建 cookie 并设置过期时间
def set_cookie(request):
    response = HttpResponse("Cookie is set")
    # 设置一个会话 Cookie,过期时间为一小时(3600秒)
    response.set_cookie('session_id', 'abcd1234', max_age=3600, httponly=True)
    return response

# 读取cookie
def get_cookie(request):
    session_id = request.COOKIES.get('session_id')
    if session_id:
        return HttpResponse(f"Session ID: {session_id}")
    else:
        return HttpResponse("No session_id cookie found")

# 删除 Cookie
def delete_cookie(request):
    response = HttpResponse("Cookie is deleted")
    # 删除 Cookie
    response.delete_cookie('session_id')
    return response

浏览器测试过程

  1. 设置 Cookie:
    • 在浏览器地址栏中输入 http://127.0.0.1:8000/blog/set-cookie/,将触发 set_cookie 视图并设置 Cookie。
    • 使用浏览器开发者工具 (F12 或 Ctrl + Shift + I) 查看 Cookie 是否已添加。在 Application 标签下的 Cookies 中查看具体内容。
  2. 读取 Cookie:
    • 在浏览器地址栏中输入http://127.0.0.1:8000/blog/get-cookie/,调用 get_cookie 以显示已经设置的 Cookie 的值。
  3. 删除 Cookie:
    • 在浏览器地址栏中输入 http://localhost:8000/blog/delete-cookie/,这将触发 delete_cookie 视图并删除 Cookie。
    • 可以再次使用开发者工具查看 Cookie 的删除效果。

Chrome cookie操作参考文档:https://developer.chrome.com/docs/devtools/application/cookies?hl=zh-cn

Django View 详解(二)

查看 set-cookie 操作时的 response headers

Django View 详解(二)

get cookie

Django View 详解(二)

在 cookie 被删除之前,浏览器期间内请求的的请求头中都会包含cookie 信息

Django View 详解(二)
Django View 详解(二)

设置多个 cookie 并指定有效信息

可以添加一个暂时性主题偏好 Cookie,过期时间为一天

from django.http import HttpResponse

# 创建 cookie 并设置过期时间
def set_cookie(request):
    response = HttpResponse("Cookie is set")
    # 设置一个会话 Cookie,过期时间为一小时(3600秒)
    response.set_cookie('session_id', 'abcd1234', max_age=3600, httponly=True)
    
    # 另外设置一个暂时性主题偏好 Cookie,过期时间为一天(86400秒)
    response.set_cookie('user_theme', 'dark', max_age=86400)  # 24 hours
    
    return response

# 读取 cookie
def get_cookie(request):
    session_id = request.COOKIES.get('session_id')
    user_theme = request.COOKIES.get('user_theme')
    
    if session_id and user_theme:
        return HttpResponse(f"Session ID: {session_id}, Theme: {user_theme}")
    elif session_id:
        return HttpResponse(f"Session ID: {session_id}, No theme preference found.")
    else:
        return HttpResponse("No session_id cookie found")

# 删除 cookie
def delete_cookie(request):
    response = HttpResponse("Cookie is deleted")
    # 删除特定 Cookie
    response.delete_cookie('session_id')
    response.delete_cookie('user_theme')
    return response

在浏览器地址栏中输入 http://localhost:8000/set-cookie/ 来设置 Cookie,检查 Expires 或 Max-Age 信息以查看过期时间设置是否正确

Django View 详解(二)

Session

定义:
在 Web 开发环境中,会话(Session)是指在服务器端记录特定用户与服务进行的一系列交互的状态管理机制。与 Cookie 不同的是,会话数据主要存储在服务器端,并通过一个唯一的识别符来管理,而不是直接储存在用户的浏览器中。以下是 Session 的详细概念:

特征:

  • 存储位置: 会话数据存储在服务器端。
  • 安全: 用户只能访问与其会话 ID 相关的数据,数据存储更安全。
  • 定制: 可以存储复杂对象和较大数据量。

应用:

  • 用户认证: 通过会话存储用户的登录状态。
  • 应用场景: 可以存储购物车信息、权限数据等需要跨请求保持的用户信息。

session 的流程简介

Django View 详解(二)
  • 客户端向服务器发起请求,请求中可以携带一些信息(用户名/密码)。
  • 当服务器接收到请求后,进行用户名和密码验证,没问题后服务器会设置一个 session 信息(保存在服务端),同时服务器会在响应头中设置一个 session id 的 cookie 信息。
  • 会话 ID 通常通过 Cookie 发送到客户端,客户端一般保存在浏览器中
  • 客户端后续请求都会携带这个会话session ID,服务器通过这个 ID 从存储中检索会话数据,验证成功后,可以获取ID 对应的 session 信息 。
  • 会话持续有效,直至超时或显式结束。
优势:
安全性: 比客户端存储的 Cookie 更安全,因为会话数据保存在服务器端,不容易受客户端的访问控制漏洞影响。
存储复杂数据: 可以用于存储较大的或复杂的数据对象。

限制:
会话受服务器性能和存储空间限制,较长时间的会话管理可能影响服务器负载。

session 项目实战

示例项目:展示会话在用户登录、保持状态和注销过程中如何运作。

我们将创建一个简单的用户登录系统,其中会使用 Django 的 Session 来保持用户登录状态。用户可以登录,然后看到欢迎信息,再可以选择注销。

项目结构

该示例包括以下内容:

  • 用户登录视图:验证用户身份并设置 Session。
  • 用户状态视图:显示用户的登录状态。
  • 用户注销视图:清理 Session,结束当前会话。

urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('login/', views.login, name='login'),
    path('status/', views.status, name='status'),
    path('logout/', views.logout, name='logout'),
]

views.py

from django.shortcuts import render, redirect
from django.http import HttpResponse

# Mock user database
USER_DATABASE = {
    'john': 'password123',  # Username: john, Password: password123
    'jane': 'securepassword',  # Username: jane, Password: securepassword
}

def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        
        if username in USER_DATABASE and USER_DATABASE[username] == password:
            # 登录成功后设置 Session 数据
            request.session['username'] = username # 这一行代码直接将所有 request.session 数据打包保存在数据库session_data字段中,同时生成了一个 session ID,以 set cookie 形式返回客户端
            return redirect('status')
        else:
            return HttpResponse("Invalid login attempt")
    return render(request, 'blog/login.html')

def status(request):
    # 检查用户是否已登录
    username = request.session.get('username')
    session_key = request.session.session_key  # 获取 session ID

    if username:
        return HttpResponse(
            f"Welcome, {username}!<br>"
            f"Session ID: {session_key}<br>"
            f"Session Data: {dict(request.session)}" # 在这里,dict(request.session) 调用会将会话数据的底层存储转化为一个 Python 字典,这是因为 Django 自动为请求中的会话数据进行解码和反序列化,将其可用的原始数据形式呈现给开发者。
        )
    else:
        return HttpResponse("You are not logged in")

def logout(request):
    # 注销用户,清除 Session 数据
    request.session.flush()
    return HttpResponse("You have been logged out")

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
    <h2>Login</h2>
    <form method="post">
        {% csrf_token %}
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required><br><br>
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required><br><br>
        <button type="submit">Login</button>
    </form>
</body>
</html>

工作流程

  1. 用户登录:
    • 用户访问 /login/ 并输入凭据。
    • 登录表单通过 POST 提交。
    • 如果用户名和密码正确,应用设置用户会话 request.session['username'] 并重定向至 /status/ 查看状态。
  2. 检查登录状态:
    • 访问 /status/ 检查会话中的用户名信息。
    • 如果用户已登录,显示欢迎信息。
    • 如果未登录,提示用户登录。
  3. 注销:
    • 用户访问 /logout/ 清除会话。
    • 调用 request.session.flush() 全面清除会话数据。
Django View 详解(二)

成功登录

Django View 详解(二)

成功登录后 session ID 会存储在客户端浏览器

客户端存储内容为:

Session ID: jhj0enal4whwn494o8kgux6jlpxxvb29

当客户端下次访问到服务端,服务端会通过Session ID 在本地存储找到

Session Data: {'username': 'john'}

Django View 详解(二)

访问 logout,退出登录,服务端调用 request.session.flush() 全面清除会话数据,客户端session id 消失

Django View 详解(二)

额外拓展:

上面的项目是直接 mock 了一组数据,其实用户登陆和密码数据存储在数据库中,需要通过下面步骤完善:

1. 创建 Django 模型
首先,定义一个模型来存储用户数据。

# models.py
from django.db import models

class User(models.Model):
    username = models.CharField(max_length=150, unique=True)
    password = models.CharField(max_length=128)

    def __str__(self):
        return self.username

配置 MySQL 数据库并确保已安装 MySQL 数据库适配器 mysqlclient,执行数据迁移 python manage.py makemigrations && python manage.py migrate

在 Django shell 中插入数据

# 这部分可以在 Django shell 中执行
from blog.models import User

# 创建模拟用户
users = [
    {'username': 'john', 'password': 'password123'},
    {'username': 'jane', 'password': 'securepassword'}
]

# 插入到数据库
for user in users:
    User.objects.create(username=user['username'], password=user['password'])

最后修改 view.py 即可

...
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')

        try:
            # 从数据库中查询用户对象
            user = User.objects.get(username=username)
            if user.password == password:
                # 登录成功后设置 Session 数据
                request.session['username'] = username
                return redirect('status')
            else:
                return HttpResponse("Invalid password")
        except User.DoesNotExist:
            return HttpResponse("User does not exist")

    return render(request, 'blog/login.html')
...

session 存储

settings.py 中查看是否已经注册 sessions

Django View 详解(二)

session 这里保存在我们的 mysql 中

Django View 详解(二)

数据库存储: 数据在存储时被编码以便于存储,并确保其安全性。只有在提取并被 Django 反序列化之后,开发者才能看到原始的数据结构。编码和加密是为了防止会话数据被直接修改,保持完整性和安全性。

字段解释

  1. session_key:
    • 这是一个唯一的字符串,作为会话标识符(Session ID)。
    • session_key 用于识别特定会话数据,并在请求时通过 Cookie 发回服务器以关联会话。
  2. session_data:
    • 存储在会话中的具体数据内容,这里的 username 是被序列化后的数据。
    • 数据通常是通过 Django 的签名和序列化机制来存储的,以保持安全性和数据完整性。这种机制使得数据内容不可直接人类可读,避免篡改。
    • 具体格式为:<base64编码的序列化数据>:<签名字符串> 这里的冒号 : 是分隔符,冒号前是经过序列化和 base64 编码的 session 内容,冒号后是用 SECRET_KEY 签名的字符串,用于验证数据完整性和防篡改。
  3. expire_date:
    • 会话的过期时间。定义了会话在数据库中的有效期。
    • 超过该时间后,系统会认为会话无效,并自动清理相关数据。
    • 确保不活跃会话在一段时间后被垃圾收集。

Token(熟悉即可)

定义:
Token 通常指的是一种认证机制,客户端通过在请求头中发送 token 来验证身份。最常见的实现是 JWT(JSON Web Tokens)。

在 Django 中,Token 尤其适用于创建RESTful API。当你希望你的应用能够以无状态的方式进行身份验证时,使用Token是一个非常好的选择。以下示例演示了如何在Django项目中使用Token进行用户认证,尤其是使用JSON Web Tokens(JWT)。

特征:

  • 无状态性: Token 是独立的,不依赖服务器存储任何 session 数据。
  • 自主性: Token 可以包含大量信息,比如用户角色、权限等,服务器不需要额外查找。
  • 安全性: 使用签名确保 token 数据未被篡改。

应用:

  • API 认证: 常用于 RESTful API 中的身份认证。
  • 单点登录: 在分布式系统中使用 token 进行用户身份识别。

Django 使用到的技术栈

  1. Django: 用于构建应用逻辑。
  2. Django REST Framework: 提供API的构建框架。
  3. djangorestframework-simplejwt: 简化JWT在DRF中的使用。

设置环境

1. 安装依赖

确保Django和Django REST Framework是已安装的:

pip install django djangorestframework
pip install djangorestframework-simplejwt

2. Django 配置
settings.py 中添加对REST框架和JWT的支持:

INSTALLED_APPS = [
    # ... 其他应用
    'rest_framework',
    'rest_framework_simplejwt',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
}

JWT 的基本用法

创建用户模型和API视图

models.py:

from django.contrib.auth.models import User

views.py:

from django.contrib.auth.models import User
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

class UserListView(generics.ListCreateAPIView):
    queryset = User.objects.all()
    permission_classes = [IsAuthenticated]

    def get(self, request, *args, **kwargs):
        users = [user.username for user in self.queryset]
        return Response(users)

urls.py:

from django.urls import path
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)
from .views import UserListView

urlpatterns = [
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    path('users/', UserListView.as_view(), name='user_list'),
]

使用和验证

进入 Django Shell:

python manage.py shell

创建用户

from django.contrib.auth.models import User

# 创建用户
user = User.objects.create_user(username='yourusername', password='yourpassword')
user.save()

# 激活用户
user = User.objects.get(username='yourusername')
user.is_staff = True
user.save()

获取JWT Token

  • 对 api/token/ 发送POST请求以获取访问和刷新Token,传递用户的凭据:
{
  "username": "yourusername",
  "password": "yourpassword"
}

响应将类似于:

{
  "access": "ACCESS_TOKEN",
  "refresh": "REFRESH_TOKEN"
}
Django View 详解(二)

使用Token访问API

使用获取的access Token对需要认证的API发送请求,把Token附加到请求头上:

Authorization: Bearer ACCESS_TOKEN

每个请求会携带这个Token,服务器通过解码和验证Token判断请求的合法性。

使用 Postman 示例

  1. 在 Postman 中设置请求
  2. 设置 Authorization
    • 在请求的 Headers 选项卡中,添加一个新的头部:
      • Key: Authorization
      • Value: ACCESS_TOKEN
  3. 发送请求
    • 点击 Send 按钮进行请求并查看响应
Django View 详解(二)

作用和优势

  • 无状态: 使用Token进行认证无需服务器保持任何用户状态,减少了服务器的负担。
  • 自主: Token包含用户信息和权限,减少服务器频繁访问数据库去查询用户信息。
  • 安全: 签名确保Token未被篡改,标准算法加密敏感信息。

项目实战:view.py 复杂视图逻辑

在 Django 项目中,合理定义各种视图的逻辑是构建一个清晰、可维护应用的重要部分。以下是一个简单的 Django 项目示例,功能覆盖注册、登录、退出、登录后访问保护、页面跳转等核心逻辑,重点放在 views.py 的写法和讲解。

我们会做到:

  • 表单提交与数据处理(注册 + 登录)
  • 会话管理(session 保存用户状态)
  • 跳转控制(登录成功跳转、未登录限制访问)
Django View 详解(二)

1. 项目目录结构

myproject/
    manage.py
    myapp/
        __init__.py
        admin.py
        apps.py
        models.py
        tests.py
        views.py
        urls.py
        templates/
            register.html
            login.html
            home.html
            dashboard.html
    myproject/
        __init__.py
        settings.py
        urls.py
        wsgi.py

项目具体开发流程

创建子应用

python manage.py startapp myapp

settings.py 注册 “myapp”

1、数据模型 — models.py

from django.db import models

class User(models.Model):
    username = models.CharField(max_length=20, unique=True)
    password = models.CharField(max_length=128)  # 存储加密后的密码

    def __str__(self):
        return self.username

生成数据表
python manage.py makemigrations
python manage.py migrate

2、路由 — myapp/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('', views.home, name='home'),  # 首页
    path('register/', views.register, name='register'),  # 注册
    path('login/', views.login_view, name='login'),  # 登录
    path('logout/', views.logout_view, name='logout'),  # 退出
    path('dashboard/', views.dashboard, name='dashboard'),  # 登录后的页面
]

myproject/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('myapp.urls')),
]

3、核心逻辑 — views.py

from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.contrib import messages
from django.contrib.auth.hashers import make_password, check_password
from .models import User

# 首页
def home(request):
    return render(request, 'home.html')

# 注册视图
def register(request):
    if request.method == 'POST':
        username = request.POST.get('username').strip()
        password = request.POST.get('password').strip()

        # 表单校验
        if not username or not password:
            messages.error(request, "用户名和密码不能为空!")
            return redirect('register')

        # 检查是否已存在
        if User.objects.filter(username=username).exists():
            messages.error(request, "用户名已存在!")
            return redirect('register')

        # 创建用户(密码加密)
        hashed_password = make_password(password)
        User.objects.create(username=username, password=hashed_password)

        messages.success(request, "注册成功,请登录!")
        return redirect('login')

    return render(request, 'register.html')

# 登录视图
def login_view(request):
    if request.method == 'POST':
        username = request.POST.get('username').strip()
        password = request.POST.get('password').strip()

        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            messages.error(request, "用户名不存在!")
            return redirect('login')

        # 验证密码
        if check_password(password, user.password):
            # 保存用户 session
            request.session['user_id'] = user.id
            request.session['username'] = user.username
            messages.success(request, f"欢迎回来,{user.username}!")
            return redirect('dashboard')
        else:
            messages.error(request, "密码错误!")
            return redirect('login')

    return render(request, 'login.html')

# 退出视图
def logout_view(request):
    request.session.flush()  # 清空所有 session
    messages.success(request, "您已退出登录!")
    return redirect('home')

# 登录后访问的页面
def dashboard(request):
    # 判断是否登录
    if not request.session.get('user_id'):
        messages.warning(request, "请先登录!")
        return redirect('login')

    username = request.session.get('username')
    return render(request, 'dashboard.html', {'username': username})
函数功能关键逻辑
register用户注册POST 获取表单 → 校验 → 密码加密 → 数据入库
login_view用户登录POST 获取表单 → 查询用户 → 校验密码 → request.session 存储登录状态
logout_view退出request.session.flush() 清除会话
dashboard受保护页面判断 session 是否有 user_id,无则跳转登录
home首页提供导航入口

4、前端模板 — templates/*.html

home.html

<h1>首页</h1>
<p><a href="{% url 'register' %}">注册</a> | <a href="{% url 'login' %}">登录</a> | <a href="{% url 'dashboard' %}">用户中心</a></p>

register.html

<h1>用户注册</h1>
<form method="post">
    {% csrf_token %}
    <p>用户名: <input type="text" name="username"></p>
    <p>密码: <input type="password" name="password"></p>
    <button type="submit">注册</button>
</form>
<a href="{% url 'home' %}">返回首页</a>

login.html

<h1>用户登录</h1>
<form method="post">
    {% csrf_token %}
    <p>用户名: <input type="text" name="username"></p>
    <p>密码: <input type="password" name="password"></p>
    <button type="submit">登录</button>
</form>
<a href="{% url 'home' %}">返回首页</a>

dashboard.html

<h1>欢迎,{{ username }}</h1>
<p><a href="{% url 'logout' %}">退出</a></p>

关键逻辑链路梳理

  1. 注册
    • 用户 → 注册 → 验证 → 保存用户 → 跳转 login
  2. 登录
    • 用户 → 登录 → 验证账号密码 → request.session 存储登录状态 → 跳转 dashboard
  3. 访问受保护页面
    • 如果未登录(session 不存在),跳转 login
    • 如果已登录,正常显示
  4. 退出
    • 调用 logout_viewrequest.session.flush() 清除登录信息 → 跳转首页

拓展:views.py类视图写法

使用 Django 的 类视图(Class-Based Views,简称 CBV)

这样做的好处是:

  • 代码更清晰,复用性更强;
  • 更容易扩展功能,比如添加表单校验、权限控制等;
  • 遵循 Django 官方推荐的写法。
from django.shortcuts import render, redirect
from django.views import View
from django.contrib import messages
from django.contrib.auth.hashers import make_password, check_password
from .models import User


# 首页视图
class HomeView(View):
    def get(self, request):
        """
        GET 请求:渲染首页模板
        """
        return render(request, 'home.html')


# 注册视图
class RegisterView(View):
    def get(self, request):
        """
        GET 请求:渲染注册页面
        """
        return render(request, 'register.html')

    def post(self, request):
        """
        POST 请求:处理注册逻辑
        1. 获取表单数据
        2. 校验是否为空
        3. 校验用户名是否已存在
        4. 密码加密
        5. 保存到数据库
        6. 提示成功并跳转到登录页
        """
        username = request.POST.get('username', '').strip()
        password = request.POST.get('password', '').strip()

        # 校验是否为空
        if not username or not password:
            messages.error(request, "用户名和密码不能为空!")
            return redirect('register')

        # 校验用户名是否已存在
        if User.objects.filter(username=username).exists():
            messages.error(request, "用户名已存在!")
            return redirect('register')

        # 密码加密
        hashed_password = make_password(password)

        # 保存到数据库
        User.objects.create(username=username, password=hashed_password)

        messages.success(request, "注册成功,请登录!")
        return redirect('login')


# 登录视图
class LoginView(View):
    def get(self, request):
        """
        GET 请求:渲染登录页面
        """
        return render(request, 'login.html')

    def post(self, request):
        """
        POST 请求:处理登录逻辑
        1. 获取表单数据
        2. 检查用户名是否存在
        3. 校验密码是否正确
        4. 如果成功 → 保存 session 登录状态
        5. 如果失败 → 提示错误
        """
        username = request.POST.get('username', '').strip()
        password = request.POST.get('password', '').strip()

        # 检查用户名是否存在
        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            messages.error(request, "用户名不存在!")
            return redirect('login')

        # 校验密码
        if check_password(password, user.password):
            # 保存 session 数据,用于后续判断用户是否登录
            request.session['user_id'] = user.id
            request.session['username'] = user.username
            messages.success(request, f"欢迎回来,{user.username}!")
            return redirect('dashboard')
        else:
            messages.error(request, "密码错误!")
            return redirect('login')


# 退出登录视图
class LogoutView(View):
    def get(self, request):
        """
        GET 请求:退出登录
        1. 清除所有 session 数据
        2. 提示退出成功
        3. 跳转到首页
        """
        request.session.flush()
        messages.success(request, "您已退出登录!")
        return redirect('home')


# 用户中心(需要登录才能访问)
class DashboardView(View):
    def get(self, request):
        """
        GET 请求:访问用户中心
        1. 判断用户是否已登录(检查 session)
        2. 如果未登录 → 提示并跳转到登录页
        3. 如果已登录 → 渲染 dashboard.html,并传递用户名
        """
        user_id = request.session.get('user_id')
        if not user_id:
            messages.warning(request, "请先登录!")
            return redirect('login')

        username = request.session.get('username')
        return render(request, 'dashboard.html', {'username': username})

路由改写

myapp/urls.py 里,引用类视图时需要用 .as_view() 方法:

from django.urls import path
from .views import HomeView, RegisterView, LoginView, LogoutView, DashboardView

urlpatterns = [
    path('', HomeView.as_view(), name='home'),
    path('register/', RegisterView.as_view(), name='register'),
    path('login/', LoginView.as_view(), name='login'),
    path('logout/', LogoutView.as_view(), name='logout'),
    path('dashboard/', DashboardView.as_view(), name='dashboard'),
]

说明

  • 这里每个视图是一个类,继承自 django.views.View
  • 通过定义 get()post() 方法,分别处理 GET 和 POST 请求。
  • 业务逻辑和之前函数式几乎一致,但结构更清晰。
  • 如果后续功能复杂,可以继承更丰富的 CBV(比如 FormViewTemplateViewDetailView 等)或自定义。

类视图原理

函数视图和类视图对比:

FBV(函数视图)

Django 直接把 HttpRequest(以及 URL 捕获的 args/kwargs)传给你写的函数 def view(request, *args, **kwargs),你直接处理并返回 HttpResponse

CBV(类视图)

CBV(类视图):Django 使用 YourView.as_view() 生成一个 可调用函数,当请求进来这个函数会实例化你的类,执行 setup()dispatch() → 根据 request.method 调用 get/post/...,并把 requestargskwargs 传给这些方法,同时把它们保存在 self.request/self.args/self.kwargs 上。

详细调用链(从浏览器到你的 get()

1. 从 urls.py 开始

你在 urls.py 里写了:

from django.urls import path
from .views import RegisterView

urlpatterns = [
    path('register/', RegisterView.as_view(), name='register'),
]

注意RegisterView.as_view() 这里就执行了一次,返回的是一个 函数,而不是一个类。

2. as_view() 做了什么?

def as_view(cls, **initkwargs):
    def view(request, *args, **kwargs):
        self = cls(**initkwargs)         # ① 创建视图类的实例
        self.setup(request, *args, **kwargs)  # ② 初始化一些属性
        return self.dispatch(request, *args, **kwargs)  # ③ 分发请求
    return view

关键点:

  • cls 是视图类(比如 RegisterView
  • initkwargs 是初始化参数(可以在 urls.py 里传)
  • view() 是一个闭包函数,被 Django 作为 最终视图函数 存到 URLConf 里
  • 当 HTTP 请求到达时,Django 会调用这个 view(request, *args, **kwargs) 函数

3. 请求到来时,view() 的执行流程

假设用户访问 /register/,Django URL 匹配到 view(这个闭包),会调用:

view(request=<HttpRequest对象>, *args=[], **kwargs={})

执行步骤:

1、实例化视图类

self = cls(**initkwargs)

这时,self 就是一个 RegisterView 实例,还没绑定 request

2、调用 setup()

self.setup(request, *args, **kwargs)

setup() 源码:

def setup(self, request, *args, **kwargs):
    if hasattr(self, "get") and not hasattr(self, "head"):
        self.head = self.get
    self.request = request    # 绑定 request
    self.args = args
    self.kwargs = kwargs

这里把 requestargskwargs 存到实例属性

后续 get()post() 方法就可以直接 self.request 访问

3、调用 dispatch()

return self.dispatch(request, *args, **kwargs)

dispatch() 源码:

def dispatch(self, request, *args, **kwargs):
    if request.method.lower() in self.http_method_names:
        handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
    else:
        handler = self.http_method_not_allowed
    return handler(request, *args, **kwargs)

# 定位到 http_method_names 默认为:
    http_method_names = [
        "get",
        "post",
        "put",
        "patch",
        "delete",
        "head",
        "options",
        "trace",
    ]
  • 判断请求方法(GET / POST / ...)
  • 找到对应方法,比如 self.get()self.post()
  • 调用并返回结果(HttpResponse

方法调用链示意

Django View 详解(二)

参数传递链路是:

request → view() 闭包 → setup() → dispatch() → get()/post()/...

为什么 get(self, request)request 还能传?

你可能觉得奇怪:

  • 我们在 setup() 里已经 self.request = request
  • 为什么 get() 还会接到一个 request 参数?

这是因为 dispatch() 在调用 handler(request, *args, **kwargs) 时,依然按函数签名把 request 传了进去,所以 get(self, request) 同时能通过 self.request 或直接用 request 访问同一个对象。

Django 类视图多重继承

在 Django 中,虽然多重继承可以实现代码的重用,但使用时需谨慎,因为这可能会导致复杂的类层次结构和难以维护的代码。Django 的类视图(Class-Based Views, CBV)提供了良好的结构来实现通用视图的重用,利用多重继承可以组合不同的行为。

下面是一个实际的 Django Demo,展示了如何使用多重继承实现类视图。该示例包括一个简单的用户管理系统,提供用户列表、用户创建和用户详细信息的视图。

然后创建一个新的 Django 应用:

django-admin startapp users

设置 settings.py 将 users 应用添加到 INSTALLED_APPS 中:

INSTALLED_APPS = [
    ...
    'users',
]

定义一个简单的用户模型 (models.py):

# users/models.py
from django.db import models

class User(models.Model):
    username = models.CharField(max_length=150)
    email = models.EmailField()

    def __str__(self):
        return self.username

创建视图 (views.py),使用多重继承来定义多个视图:

# users/views.py
from django.views import View
from django.shortcuts import render, redirect
from .models import User
from django.http import HttpResponse

# 列表视图
class UserListView(View):
    def get(self, request):
        users = User.objects.all()
        return render(request, 'users/user_list.html', {'users': users})

# 创建视图
class UserCreateView(View):
    def get(self, request):
        return render(request, 'users/user_form.html')

    def post(self, request):
        username = request.POST.get('username')
        email = request.POST.get('email')
        User.objects.create(username=username, email=email)
        return redirect('user_list')

# 详细视图
class UserDetailView(View):
    def get(self, request, pk):
        user = User.objects.get(pk=pk)
        return render(request, 'users/user_detail.html', {'user': user})

# 组合视图
class UserManagementView(UserListView, UserCreateView):
    template_name = 'users/user_management.html'

    def get(self, request):
        user_list = super().get(request)
        user_create = super(UserCreateView, self).get(request)
        return render(request, self.template_name, {
            'user_list': user_list,
            'user_create': user_create
        })

创建 URL 路由 (urls.py):

# users/urls.py
from django.urls import path
from .views import UserListView, UserCreateView, UserDetailView, UserManagementView

urlpatterns = [
    path('', UserListView.as_view(), name='user_list'),
    path('create/', UserCreateView.as_view(), name='user_create'),
    path('<int:pk>/', UserDetailView.as_view(), name='user_detail'),
    path('management/', UserManagementView.as_view(), name='user_management'),
]

然后,将应用的 URL 添加到项目的主 URL 路由 (myproject/urls.py):

# myproject/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    ...
    path('users/', include('users.urls')),
]

创建模板文件来渲染视图。 在 users/templates/users/ 中创建以下文件:

user_list.html

<h1>User List</h1>
<ul>
    {% for user in users %}
        <li><a href="{% url 'user_detail' user.pk %}">{{ user.username }}</a></li>
    {% endfor %}
</ul>
<a href="{% url 'user_create' %}">Create User</a>

user_form.html

<h1>Create User</h1>
<form method="post">
    {% csrf_token %}
    <input type="text" name="username" placeholder="Username" required>
    <input type="email" name="email" placeholder="Email" required>
    <button type="submit">Create</button>
</form>

user_detail.html

<h1>User Detail</h1>
<p>Username: {{ user.username }}</p>
<p>Email: {{ user.email }}</p>
<a href="{% url 'user_list' %}">Back to List</a>

user_management.html

<h1>User Management</h1>
<div>
    <h2>User List</h2>
    {{ user_list }}
</div>
<div>
    <h2>Create User</h2>
    {{ user_create }}
</div>

进行数据库迁移:

python manage.py makemigrations
python manage.py migrate

你可以在浏览器中访问 http://127.0.0.1:8000/users/ 查看用户列表、创建新用户、查看用户详情等功能。这样,利用 Django 的类视图和多重继承,我们实现了多个视图的组合和重用。

运行开发服务器:

python manage.py runserver
Django View 详解(二)

发布者:LJH,转发请注明出处:https://www.ljh.cool/43619.html

Like (0)
LJH的头像LJH
Previous 2025年8月1日 下午5:26
Next 2天前

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注