Ⅰ Django框架之视图层
【一】Django的三板斧
from django.shortcuts import HttpResponse, render, redirect, reverse
【1】HttpResponse
能返回字符串形式的数据
- HttpResponse 是 Django 中用于创建 HTTP 响应对象的类,它允许你构建并返回服务器对客户端请求的数据和状态
- 当需要直接返回纯文本数据(如 JSON 格式的数据)或者 HTML 页面时,可以使用 HttpResponse
from django.http import HttpResponse
def simple_view(request):
data = {"message": "Hello, world!"}
content = json.dumps(data) # 将字典转换为 JSON 字符串
response = HttpResponse(content, content_type="application/json")
return response
- 在这个例子中,视图函数 simple_view 创建了一个包含 JSON 数据的响应。
【2】render
能返回一个页面进行渲染
- render 函数是 Django 的模板渲染机制的一部分,通常与模板引擎(如 Django 的默认模板引擎 - Jinja2 或者其它支持的模板引擎)一起使用。
- 当你想要返回带有动态内容的 HTML 页面时,可以使用 render 函数将请求的数据与预定义的 HTML 模板结合起来:
from django.shortcuts import render
from .models import MyModel # 假设有一个名为 MyModel 的模型
def detail_view(request, pk):
obj = MyModel.objects.get(pk=pk)
context = {'object': obj} # 将模型实例添加到上下文(context)
return render(request, 'my_template.html', context)
- 在这个例子中,视图函数 detail_view 使用 render 函数从数据库获取 MyModel 实例,并将其作为参数传递给名为 my_template.html 的 HTML 模板进行渲染,最终返回给客户端。
【3】redirect
重定向至新的路由地址
- redirect 函数用于实现网页间的重定向,即将用户从当前 URL 引导向另一个 URL。
- 它不返回任何内容,而是引发一个 HTTP Redirection 错误,迫使客户端发送一个新的请求到指定地址:
from django.shortcuts import redirect
def login_required_view(request):
if not request.user.is_authenticated:
return redirect('login') # 重定向到登录页面
# 如果已登录,继续执行后续操作...
- 在这个例子中,如果用户未登录,login_required_view 视图会调用 redirect 函数将用户重定向至名为 'login' 的URL对应的视图(通常是登录页面)。
- 如果用户已登录,则继续执行原视图中的其他逻辑。
【4】reverse
解析路由的别名
【二】JsonResponse
- 如果返回的是字典格式的数据
- 我们在页面上看到的是 字典的键
- 我们想看到的是字典 (json格式的字符串)
【1】返回的是字典格式的数据
from django.shortcuts import HttpResponse, render, redirect, reverse
def index(request):
user_data = {
"username": "silence",
"password": "123456"
}
return HttpResponse(user_data)
# 我们在页面上看到的是 字典的键
【2】将字典格式的数据转换为 json 字符串
import json
from django.shortcuts import HttpResponse, render, redirect, reverse
def index(request):
user_data = {
"username": "silence",
"password": "123456"
}
# 将字典格式的数据转换为 json 字符串
user_data_str = json.dumps(user_data)
# 再通过 HttpResponse 返回给前端
# 前端看到的就是一个json格式的字符串
return HttpResponse(user_data_str)
# {"username": "silence", "password": "123456"}
- Content-Type: text/html; charset=utf-8
- 是一个HTML页面
- 我们想要的是json格式的数据 user_data.json
【3】增加一个额外的参数 content_type="application/json"
import json
from django.shortcuts import HttpResponse, render, redirect, reverse
def index(request):
user_data = {
"username": "silence",
"password": "123456"
}
# 将字典格式的数据转换为 json 字符串
user_data_str = json.dumps(user_data)
# 增加一个额外的参数 content_type="application/json"
# Content-Type: application/json
# 前端接收到的数据类型就变成了 json 数据
return HttpResponse(user_data_str, content_type="application/json")
# {
# "username": "silence",
# "password": "123456"
# }
Ⅱ form表单文件上传下载
form表单上传数据以及后端如何获取
【一】form知识回顾
<form action="" method="post" enctype="multipart/form-data"></form>
- form表单想要上传文件类型的数据
- method 参数必须改为post
- enctype 参数必须指定成 form-data 类型
【二】数据准备
- 路由urls.py
from django.contrib import admin
from django.urls import path
from user.views import index,register
urlpatterns = [
path('admin/', admin.site.urls),
path('', index),
path('register/', register,name='register'),
]
- 前端register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>注册</h1>
<form action="" method="post">
<p>username:<input type="text" name="username"></p>
<p>password:<input type="text" name="password"></p>
<p>hobby:
listen<input type="checkbox" name="hobby" value="listen">
run<input type="checkbox" name="hobby" value="run">
music<input type="checkbox" name="hobby" value="music">
</p>
<p>avatar:<input type="file" name="avatar"></p>
<p><input type="submit" ></p>
<p><input type="reset" ></p>
</form>
</body>
</html>
【三】数据处理
- 默认的提交数据方式是 enctype="application/x-www-form-urlencoded"
- 默认按照键值对提交数据 , 不会对键值对以外的数据进行额外的处理 文件数据
【1】POST请求数据和文件数据获取
- csrf报错,记得注释掉settings里面的中间件
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
from django.shortcuts import HttpResponse, render, redirect, reverse
def register(request):
if request.method == 'POST':
print(request.POST)
username = request.POST.get('username')
password = request.POST.get('password')
hobby = request.POST.getlist('hobby')
avatar = request.POST.getlist('avatar')
print(f'username>>>>: {username}')
print(f'password>>>>: {password}')
print(f'hobby>>>>: {hobby}')
print(f'avatar>>>>: {avatar}', type(avatar))
return redirect(reverse('register'))
return render(request, "register.html",locals())
username>>>>: silence
password>>>>: 741
hobby>>>>: ['listen', 'run']
avatar>>>>: ['sty.jpg'] <class 'list'>
- 通过这种方式,我们只能获取到我们输入的文本数据,而拿不到我们想要的文件数据
【2】使用方法
- 想要提交文件数据
- enctype="multipart/form-data"
- 键值对数据还是按照原本的方式进行提交
- 文件对象会被单独处理
- 到了后端提取数据的时候 键值对数据直接 get 文件数据就要用 request.FILES 提取
from django.shortcuts import HttpResponse, render, redirect, reverse
def register(request):
if request.method == 'POST':
print(request.POST)
username = request.POST.get('username')
password = request.POST.get('password')
hobby = request.POST.getlist('hobby')
avatar = request.POST.getlist('avatar')
print(f'username>>>>: {username}')
print(f'password>>>>: {password}')
print(f'hobby>>>>: {hobby}')
# 获取当前的图像文件,应该是一个文件数据(二进制类型)
# 但是现在返回的是avatar>>>>: ['sty.jpg'] <class 'list'>
print(f'avatar>>>>: {avatar}', type(avatar))
# 获取数据传输中的 文件数据
data_obj = request.FILES
print(data_obj)
# <MultiValueDict: {}> 空的
return redirect(reverse('register'))
return render(request, "register.html",locals())
- 获取数据传输中的 文件数据
- data_obj = request.FILES <MultiValueDict: {}> 空的
- 修改 form 表单的数据提交方式 enctype="multipart/form-data"
from django.shortcuts import HttpResponse, render, redirect, reverse
def register(request):
if request.method == 'POST':
print(request.POST)
username = request.POST.get('username')
password = request.POST.get('password')
hobby = request.POST.getlist('hobby')
avatar = request.POST.getlist('avatar')
print(f'username>>>>: {username}')
print(f'password>>>>: {password}')
print(f'hobby>>>>: {hobby}')
# 获取当前的图像文件,应该是一个文件数据(二进制类型)
# 但是现在返回的是avatar>>>>: ['sty.jpg'] <class 'list'>
print(f'avatar>>>>: {avatar}', type(avatar))
# 获取数据传输中的 文件数据
data_obj = request.FILES
print(data_obj)
# <MultiValueDict: {'avatar': [<TemporaryUploadedFile: sty.jpg (image/jpeg)>]}>
data = data_obj.get('avatar')
print(data, type(data))
# sty.jpg <class 'django.core.files.uploadedfile.TemporaryUploadedFile'>
return redirect(reverse('register'))
return render(request, "register.html",locals())
- 想要保存文件数据 ---> 写入的文件数据是二进制数据才能被写入
from django.shortcuts import HttpResponse, render, redirect, reverse
def register(request):
if request.method == 'POST':
print(request.POST)
username = request.POST.get('username')
password = request.POST.get('password')
hobby = request.POST.getlist('hobby')
avatar = request.POST.getlist('avatar')
print(f'username>>>>: {username}')
print(f'password>>>>: {password}')
print(f'hobby>>>>: {hobby}')
# 获取当前的图像文件,应该是一个文件数据(二进制类型)
# 但是现在返回的是avatar>>>>: ['sty.jpg'] <class 'list'>
print(f'avatar>>>>: {avatar}', type(avatar))
# 获取数据传输中的 文件数据
data_obj = request.FILES
print(data_obj)
# <MultiValueDict: {'avatar': [<TemporaryUploadedFile: sty.jpg (image/jpeg)>]}>
data = data_obj.get('avatar')
print(data, type(data))
# sty.jpg <class 'django.core.files.uploadedfile.TemporaryUploadedFile'>
with open (data.name, 'wb') as fp:
fp.write(data)
# 不能直接写上面获取的对象
return redirect(reverse('register'))
return render(request, "register.html",locals())
- 调用Django文件对象的内置方法 .chunks()
- .chunks()提取文件对象中的二进制数据 参数是 每次提取多少个数据
import json
from django.shortcuts import HttpResponse, render, redirect, reverse
def index(request):
user_data = {
"username": "silence",
"password": "123456"
}
# 将字典格式的数据转换为 json 字符串
user_data_str = json.dumps(user_data)
# 增加一个额外的参数 content_type="application/json"
# Content-Type: application/json
# 前端接收到的数据类型就变成了 json 数据
return HttpResponse(user_data_str, content_type="application/json")
def register(request):
if request.method == 'POST':
print(request.POST)
username = request.POST.get('username')
password = request.POST.get('password')
hobby = request.POST.getlist('hobby')
avatar = request.POST.getlist('avatar')
print(f'username>>>>: {username}')
print(f'password>>>>: {password}')
print(f'hobby>>>>: {hobby}')
# 获取当前的图像文件,应该是一个文件数据(二进制类型)
# 但是现在返回的是avatar>>>>: ['sty.jpg'] <class 'list'>
print(f'avatar>>>>: {avatar}', type(avatar))
# 获取数据传输中的 文件数据
data_obj = request.FILES
print(data_obj)
# <MultiValueDict: {'avatar': [<TemporaryUploadedFile: sty.jpg (image/jpeg)>]}>
data = data_obj.get('avatar')
print(data, type(data))
# sty.jpg <class 'django.core.files.uploadedfile.TemporaryUploadedFile'>
# with open (data.name, 'wb') as fp:
# fp.write(data)
# 不能直接写上面获取的对象
with open(data.name, 'wb') as fp:
for line in data.chunks():
fp.write(line)
return redirect(reverse('register'))
return render(request, "register.html",locals())
- 获得form表单下载的文件
【3】代码展示
- views.py
import json
from django.shortcuts import HttpResponse, render, redirect, reverse
def index(request):
user_data = {
"username": "silence",
"password": "123456"
}
# 将字典格式的数据转换为 json 字符串
user_data_str = json.dumps(user_data)
# 增加一个额外的参数 content_type="application/json"
# Content-Type: application/json
# 前端接收到的数据类型就变成了 json 数据
return HttpResponse(user_data_str, content_type="application/json")
def register(request):
if request.method == 'POST':
print(request.POST)
username = request.POST.get('username')
password = request.POST.get('password')
hobby = request.POST.getlist('hobby')
avatar = request.POST.getlist('avatar')
print(f'username>>>>: {username}')
print(f'password>>>>: {password}')
print(f'hobby>>>>: {hobby}')
# 获取当前的图像文件,应该是一个文件数据(二进制类型)
# 但是现在返回的是avatar>>>>: ['sty.jpg'] <class 'list'>
print(f'avatar>>>>: {avatar}', type(avatar))
# 获取数据传输中的 文件数据
data_obj = request.FILES
print(data_obj)
# <MultiValueDict: {'avatar': [<TemporaryUploadedFile: sty.jpg (image/jpeg)>]}>
data = data_obj.get('avatar')
print(data, type(data))
# sty.jpg <class 'django.core.files.uploadedfile.TemporaryUploadedFile'>
# with open (data.name, 'wb') as fp:
# fp.write(data)
# 不能直接写上面获取的对象
with open(data.name, 'wb') as fp:
for line in data.chunks():
fp.write(line)
return redirect(reverse('register'))
return render(request, "register.html",locals())
- register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>注册</h1>
<form action="" method="post" enctype="multipart/form-data">
<p>username:<input type="text" name="username"></p>
<p>password:<input type="text" name="password"></p>
<p>hobby:
listen<input type="checkbox" name="hobby" value="listen">
run<input type="checkbox" name="hobby" value="run">
music<input type="checkbox" name="hobby" value="music">
</p>
<p>avatar:<input type="file" name="avatar"></p>
<p><input type="submit" ></p>
<p><input type="reset" ></p>
</form>
</body>
</html>
- urls.py
from django.contrib import admin
from django.urls import path
from user.views import index,register
urlpatterns = [
path('admin/', admin.site.urls),
path('', index),
path('register/', register,name='register'),
]
- sty.jpg
【4】模板步骤
def register(request):
# 【1】form表单以 post 请求提交数据
if request.method == 'POST':
# 【2】form 表单中的键值对数据
# print(request.POST)
# Python中的 None 对应MySQL数据库中的 null
# 【3】提取出键值对数据
username = request.POST.get('username')
password = request.POST.get('password')
hobby = request.POST.getlist('hobby')
# 【4】如果一个键对应多个值的情况要用getlist 提取数据
avatar = request.POST.getlist('avatar')
# 【5】获取当前的头像
# ---> 如果提交数据方式是 enctype="application/x-www-form-urlencoded"
# 获取到的是一个列表 列表里面存的是当前文件的文件名
# print(f"avatar :>>>>{avatar, type(avatar)}")
# 【6】修改 form 表单的数据提交方式 enctype="multipart/form-data"
# 获取数据传输中的 文件数据
data_obj = request.FILES
# print(data_obj)
# <MultiValueDict: {'avatar': [<InMemoryUploadedFile: Alist.png (image/png)>]}>
# 【7】根据键将对应的文件数据对象提取出来
data = data_obj.get("avatar")
print(data, type(data))
# 拿到文件数据对象 ---> 这个对象是Django的内置对象
# Alist.png <class 'django.core.files.uploadedfile.InMemoryUploadedFile'>
# 【8】想要保存文件数据 ---> 写入的文件数据是二进制数据才能被写入
# 因为上面的是Django的一个文件对象 不是二进制数据
# with open(data.name, "wb") as fp:
# # 不能直接写上面获取到的对象
# fp.write(data)
# 【9】调用Django文件对象的内置方法 .chunks()
# 提取文件对象中的二进制数据 参数是 每次提取多少个数据
with open(data.name, "wb") as fp:
for line in data.chunks():
fp.write(line)
# 返回到原本的页面
return redirect(reverse("register"))
return render(request, "register.html", locals())
【四】总结request对象方法
【1】request.method
- request.method:该方法返回客户端用于发起请求的HTTP方法。
- 例如,可以是'GET'、'POST'、'PUT'、'DELETE'等。
- 您可以使用该方法来确定请求的类型,并相应地执行特定操作。
【2】request.POST
- request.POST:该属性是一个类似字典的对象,包含了请求中通过POST方法发送的所有参数。
- 这些参数通常是通过HTML表单发送的。
- 您可以使用参数的名字作为键来访问单个参数,例如request.POST['username']。
【3】request.GET
- request.GET:类似于request.POST,该属性包含了请求中通过GET方法发送的所有参数。
- 这些参数通常会附加在URL之后,以问号分隔。
- 您可以使用参数的名字作为键来访问单个参数,例如request.GET['page']。
【4】request.FILES
- request.FILES:该属性是一个类似字典的对象,包含了请求中通过文件上传组件发送的所有文件。
- 当表单中包含文件上传字段时,通过request.FILES可以访问上传的文件。
- 您可以使用文件的名字作为键来访问单个文件,例如request.FILES['file']。
【5】request.path
只能获取到路由地址,无法获取到参数
- request.path:该属性表示请求URL中的路径部分。
- 它包含在域名之后,在任何查询参数之前。
- 例如,如果请求的URL是"http://example.com/foo/bar/",那么request.path将为"/foo/bar/"。
【6】request.path_info
只能获取到路由地址,无法获取到参数
- 用于表示请求URL的路径部分,不包括域名和查询参数。
- 与 request.path 相比,request.path_info 更加原始和未经解析。
- 它保留了URL路径中的任何编码、特殊字符或斜杠等信息。
- 例如,对于以下请求URL:"http://example.com/foo/bar/?page=2",request.path_info 的值将是 "/foo/bar/"。
- 通常情况下,您可以使用 request.path 来获取丢弃域名后的路径,而使用 request.path_info 来获取原始的、未解析的路径。这在某些情况下非常有用,例如当您需要对URL进行一些自定义操作或路由处理时。
【7】request.get_full_path()
即能获取到路由地址又能获取到完整的路由地址后面的参数
- request.get_full_path():该方法返回请求URL的完整路径,包括路径部分和任何查询参数。
- 当您需要将完整URL作为字符串使用时,这个方法非常有用。
- 例如,如果请求的URL是"http://example.com/foo/bar/?page=2",request.get_full_path()将返回"/foo/bar/?page=2"。
【8】request方法补充
<!--method="get"-->
<form action="" method="get" enctype="multipart/form-data">
- views.py
import json
from django.shortcuts import HttpResponse, render, redirect, reverse
def login(request):
print(request.GET)
# <QueryDict: {'username': ['silence'], 'password': ['741'], 'hobby': ['listen', 'run'], 'avatar': ['sty.jpg']}>
print(request.FILES)
# <MultiValueDict: {}>
# 提交的请求方式是 GET 请求
# form 表单的数据格式 enctype="multipart/form-data"
# 后端只能接收到键值对数据 无法接收到 文件数据
# POST 请求在浏览器的地址栏中你会看到数据吗? 看不到 只能看到127.0.0.1:8000/register/
# GET 请求在浏览器地址栏中你会看到数据吗? 可以看到 GET 请求携带的请求体数据大小是有限制的!5MB
# http://localhost:8000/login/?username=silence&password=741&hobby=listen&hobby=run&avatar=sty.jpg
# 想获取 GET 请求提交的数据
# request.GET.get('key')
# request.GET.getlist('key')
print(request.path) # /login/
print(request.get_full_path())
# /login/?username=silence&password=741&hobby=listen&hobby=run&avatar=sty.jpg
print(request.path_info)
# /login/
print(request.get_full_path_info())
# /login/?username=silence&password=741&hobby=listen&hobby=run&avatar=sty.jpg
return render(request,'login.html',locals())
- login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登陆页面</h1>
<form action="" method="get" enctype="multipart/form-data">
<p>username:<input type="text" name="username"></p>
<p>password:<input type="text" name="password"></p>
<p>hobby:
listen<input type="checkbox" name="hobby" value="listen">
run<input type="checkbox" name="hobby" value="run">
music<input type="checkbox" name="hobby" value="music">
</p>
<p>avatar:<input type="file" name="avatar"></p>
<p><input type="submit" ></p>
<p><input type="reset" ></p>
</form>
</body>
</html>
Ⅲ FBV与CBV引入
-
视图函数既可以是函数也可以是类
-
我们之前写过的都是基于函数的view,就叫FBV
-
还可以把view写成基于类的,就叫CBV
-
FBV function 函数
-
CBV class 类
【一】FBV和CBV
- FBV(Function-Based Views)与 CBV(Class-Based Views)是 Django 框架中用于处理视图(Views)的两种不同的编程方式。
【1】FBV
# FBV版添加班级
def add_class(request):
if request.method == "POST":
class_name = request.POST.get("class_name")
models.Classes.objects.create(name=class_name)
return redirect("/class_list/")
return render(request, "add_class.html")
【2】CBV
(1)路由
# CBV 路由 - 根据请求方式的不同选择不同的入口动作
path('login/', views.MyLogin.as_view())
(2)视图
from django.views import View
class MyLogin(View):
def get(self, request, *args, **kwargs):
return HttpResponse("get")
def post(self, request, *args, **kwargs):
return HttpResponse("post")
- FBV和CBV各有各的特点,都有应用
- CBV特点
- 能够直接根据请求方式的不同直接匹配到对应的方法执行
【二】Class-Based Views(CBV)使用步骤
- CBV 是 Django 提供的另一种视图编程方式,引入了面向对象的概念。
- 使用类来表示视图,并使用类中的方法来处理不同类型的HTTP请求。
- Django 提供了许多内置的 CBV 类,可以方便地扩展和重写这些类来实现自定义功能。
【1】引入一个类
from django.views import View
【2】创建一个类 , 继承上面那个类
class HomeView(View):
【3】在自定义类里面定义两个方法
# def get(self, request) : 当前端发送 get 请求的时候会被触发
# def post(self, request) : 当前端发送 post 请求的时候会被触发
def get(self, request):
print(f'触发GET请求!')
return render(request,'home.html',locals())
def post(self, request):
# 【5】提交 post 请求
print(request.POST)
return redirect(reverse("home"))
【4】配置路由映射关系
# 中间的视图函数变成了 类.as_view()
path('home/', HomeView.as_view(),name='home'),
【5】提交 post 请求
print(request.POST)
return redirect(reverse("home"))
【6】总代码展示
- views.py
from django.shortcuts import HttpResponse, render, redirect, reverse
# 【1】导入Django的内置类
from django.views import View
# 【2】自定义类继承Django的内置类
class HomeView(View):
# 【3】在类内部写视图函数
def get(self, request):
print(f'触发GET请求!')
return render(request,'home.html',locals())
def post(self, request):
# 【5】提交 post 请求
print(request.POST)
return redirect(reverse("home"))
- urls.py
from django.urls import path
from user.views import index,register,login,HomeView
urlpatterns = [
# 【4】定义路由映射 函数就变成了 类.as_view()
path('home/', HomeView.as_view(),name='home'),
]
- login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登陆页面</h1>
<form action="" method="get" enctype="multipart/form-data">
<p>username:<input type="text" name="username"></p>
<p>password:<input type="text" name="password"></p>
<p>hobby:
listen<input type="checkbox" name="hobby" value="listen">
run<input type="checkbox" name="hobby" value="run">
music<input type="checkbox" name="hobby" value="music">
</p>
<p>avatar:<input type="file" name="avatar"></p>
<p><input type="submit" ></p>
<p><input type="reset" ></p>
</form>
</body>
</html>
Ⅳ CBV源码剖析
- Django请求声明周期
# 【1】从前端入手 前端发送请求
# 【2】wsgiref 模块上对 Request 进行分装
# 【3】Django框架上 中间件 (相当于你的公司的保安 你的第一道防线)
# 【4】Django框架上的 路由映射 urls.py 去根据路径映射制定的视图函数
# 【5】Django框架上的 视图函数 views.py 中
# (1)获取前端页面 (模版语法渲染)
# (2)读取数据库数据 (增删改查)
# (3)记录你的操作日志 (记录日志)
# 返回 response对象
# 【6】Django框架上的 urls.py 根据制定路径回去
# 【7】回到了 wsgiref 模块上 对Django的 response 对象进行封装
# 【8】回到前端解包渲染
【一】分析入口
# 当前端的请求进入到Django之后会先经过 中间件 (我没写东西所以不管)
# 紧接着就走到了路由层 路由映射关系 urls.py 中
path('home/', HomeView.as_view(),name='home'),
【二】思考
# 为什么 要用 类.as_view()
# as_view 属于类的绑定方法
# as_view() ---> 一定有一个返回值
# 原来写的是函数名 --->现在变成了as_view() --->as_view() 返回了什么?
【三】找到了入口 as_view
CTRL加鼠标左键 点击path('home/', HomeView.as_view(),name='home'),里的as_view()
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process."""
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError(
'The method name %s is not accepted as a keyword argument '
'to %s().' % (key, cls.__name__)
)
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
【1】确定了 as_view 是类的绑定方法,类可以直接调用不需要传递额外的参数
@classonlymethod
def as_view(cls, **initkwargs):
# 确定了 as_view 是类的绑定方法 类可以直接调用不需要传递额外的参数
【2】**initkwargs
我在调用 HomeView.as_view() 的时候没有传递参数 所以**initkwargs 没东西
【3】http_method_names是什么
CTRL加鼠标左键 点击http_method_names
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
# 如果传递额外的参数进来了 并且在上面的列表中
if key in cls.http_method_names:
# 就会报错
raise TypeError(
'The method name %s is not accepted as a keyword argument '
'to %s().' % (key, cls.__name__)
)
# 现在知道http_method_names是什么了 不走这些代码
【4】hasattr
# hasattr 判断当前对象中是否存在指定的属性 有就是 True 没有 False
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
# 我没有传递参数 所以这一步也不走 继续向下执行代码
【5】view
# 发现 initkwargs 没东西
# 就继续向下走 就走到了内嵌函数 view
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.setup(request, *args, **kwargs)
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view
(1)cls(**initkwargs)
def view(request, *args, **kwargs):
# 调用当前类 实例化得到一个当前类的对象
# cls ---> 当前的自定义类 HomeView
# path('home/', HomeView.as_view(),name='home'),
# 由于 HomeView 没有 __init__ 方法 所以初始化的时候就不需要传递额外的参数
# self 就是当前 HomeView 类的一个对象
self = cls(**initkwargs)
(2)setup
# HomeView 类里面没有 setup 方法
# 于是就去父类 View 里面找 向下找
# 为自己的对象初始化属性并且 保证我一定有 get 方法
self.setup(request, *args, **kwargs)
def setup(self, request, *args, **kwargs):
"""Initialize attributes shared by all view methods."""
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
从as_view 的 内嵌函数 view 函数 进入到当前的 setup
# 从as_view 的 内嵌函数 view 函数 进入到当前的 setup
def setup(self, request, *args, **kwargs):
# (1)hasattr(self, 'get') 判断自己是否有 get 属性
# 有 自己定义的get方法
# (2)not hasattr(self, 'head') 没写过 head
if hasattr(self, 'get') and not hasattr(self, 'head'):
# 增加了一个属性 head 属性
# self.get : 自己的 get 函数的内存地址
self.head = self.get
# (3)自己有定义def get(self, request):
# 把 request 对象初始化进自己的属性中
self.request = request
# (4)def get(self, request):
# def get(self, request, *args, **kwargs):自己知道*args, **kwargs一定没有参数所以没写
# 如果有位置参数及关键字参数则添加到自己的属性中
self.args = args
self.kwargs = kwargs
(3)执行完setup就又回到了原处
# 为自己的对象初始化属性并且 保证我一定有 get 方法
self.setup(request, *args, **kwargs)
(4)request
- 如果 自己没有 request 属性,就报错了
# 如果 自己没有 request 属性
if not hasattr(self, 'request'):
# 就报错了
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
- 但是在setup里有初始化request,所以不写也不会报错
# 把 request 对象初始化进自己的属性中
self.request = request
(5)return self.dispatch(request, *args, **kwargs)
self.dispatch(request, *args, **kwargs)
# 调用了自己的 dispatch 方法
# dispatch 方法 有一个返回值 并且 这个返回值一定是 Django的 response 对象
# path('login/', login, name="login"), login 是一个函数地址 能被正常响应
# path('home/', HomeView.as_view(), name="home"), HomeView.as_view() 返回的一定也是函数地址 view 的函数地址
# self.dispatch(request, *args, **kwargs) 返回的也是函数内存地址
从as_view 的 内嵌函数 view 函数 进入到当前的 dispatch
# 【6】从as_view 的 内嵌函数 view 函数 进入到当前的 dispatch
def dispatch(self, request, *args, **kwargs):
# (1)request.method.lower() : 当前浏览器提交的请求方式的小写
# http_method_names指的是HTTP请求中支持的方法(methods)的列表:包括GET,POST,HEAD...
# request.method.lower()将GET变为小写get 所以一定在 self.http_method_names
if request.method.lower() in self.http_method_names:
# getattr 从对象中映射出指定键对应的属性值
# 如果能拿到对应键对应的函数内存地址则就是函数内存地址
# request.method.lower()相当于get 如果拿不到函数内存地址 就去后面拿
# self.http_method_not_allowed CTRL加鼠标左键看其属性(解释在下方)
# 否则就是一个 HttpResponseNotAllowed 对象 提示当前请求方法不被允许
handler =getattr(self,request.method.lower(),self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
self.http_method_not_allowed
# 如果请求方式是在允许的范围内 就会正常响应
# 如果不是正确的请求方式就会提示 当前请求方式不被允许
def http_method_not_allowed(self, request, *args, **kwargs):
logger.warning(
'Method Not Allowed (%s): %s', request.method, request.path,
extra={'status_code': 405, 'request': request}
)
return HttpResponseNotAllowed(self._allowed_methods())
handler()
# handler : get 函数的内存地址
# handler() : get 函数加 ()
return handler(request, *args, **kwargs)
(6)执行完dispatch又返回原处接受到 self.dispatch 返回的数据 HttpResponse对象
# 当请求进来的时候会触发 view 函数执行
# 接受到 self.dispatch 返回的数据 DjangoResponse对象
return self.dispatch(request, *args, **kwargs)
(7)返回view对象
# 初始化类属性
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view
【四】小总结
分析:
- 人口请求打进来路由层的时候一定触发.as_view()
- 进入.as_view(),按住CTRL加鼠标左键进去
- 点进去看进入as_view()函数,由于没有传参数,**initkwargs就没有值,所以下方代码不走
- 然后往下走 调用view(),view()最后的返回值是view
- 然后就可以确定as_view()接收到的就是view,就是内嵌函数def view(request, *args, **kwargs):的内存地址
- 当请求进来的时候触发HomeView.as_view()的执行,就是view函数的执行
- 点进去看def view(request, *args, **kwargs):
- 执行self = cls(**initkwargs)代码 得到当前类的一个对象
- 然后调用类的setup方法,自己没有定义setup方法,需要去父类里面查看setup方法
- 就是当前这个类,然后往下找就找到了def setup(self, request, *args, **kwargs):这个方法
- 进入之后在里面判断我有get方法并且没有head方法
- 并且会初始化head,self.head = self.get,将head方法替换成我的get方法
- 初始化request对象
- 如果自己携带了参数,初始化携带的位置参数以及关键字参数,self.args = args,self.kwargs = kwargs
- 走完然后返回
- 然后校验自己有没有request对象,没有就报错
- 在之后就进去dispatch方法,return self.dispatch(request, *args, **kwargs)
- 自己没有定义dispatch方法,需要去父类里面查看dispatch方法
- 就是当前这个类,然后往下找就找到了def dispatch(self, request, *args, **kwargs):这个方法
- 判断自己这个方法在不在允许的方法内if request.method.lower() in self.http_method_names:
- 在允许方法内
- 映射出来这个方法的内存地址,把他赋给handlerhandler = getattr(self, request.method.lower(), self.http_method_not_allowed)
- 不在允许方法内
- 返还一个不允许的提示,HttpResponseNotAllowed(self._allowed_methods())
- 在允许方法内
- 再往下走到,return handler(request, *args, **kwargs)
- handler:get方法调用
- handler(request, *args, **kwargs) ,返回的应该是 HttpResponse对象,或者是render,….三剑客里面的
- return handler(request, *args, **kwargs) 返回响应对象
- 判断自己这个方法在不在允许的方法内if request.method.lower() in self.http_method_names:
- 然后返回到dispatch
- 然后return self.dispatch(request, *args, **kwargs)再将响应对象返回出去
- 然后前端就接收到了,就能将其渲染了
【五】总的代码加步骤
# 【一】分析入口
# 当前端的请求进入到Django之后会先经过 中间件 (我没写东西所以不管)
# 紧接着就走到了 路由映射关系 urls.py 中
# path('home/', HomeView.as_view(), name="home"),
# 【二】思考
# 为什么 要用 类.as_view()
# as_view 属于类的绑定方法
# as_view() ---> 一定有一个返回值
# 原来写的是函数名 ---> as_view() --->as_view() 返回了什么?
# 【三】找到了入口 as_view
'''
# 【一】确定了 as_view 是类的绑定方法 类可以直接调用不需要传递额外的参数
@classonlymethod
def as_view(cls, **initkwargs):
# 【二】**initkwargs 我在调用
# 【1】HomeView.as_view() 的时候没有传递参数 **initkwargs 没东西
for key in initkwargs:
# 【2】http_method_names : 知道这个东西是什么 不走
# http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
# 如果传递额外的参数进来了 并且在上面的列表中
if key in cls.http_method_names:
# 就报错了
raise TypeError(
'The method name %s is not accepted as a keyword argument '
'to %s().' % (key, cls.__name__)
)
# 【3】hasattr 判断当前对象中是否存在指定的属性 有就是 True 没有 False
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
# 【三】发现 initkwargs 没东西
# 就继续向下走 就走到了内嵌函数 view
def view(request, *args, **kwargs):
# 【1】调用当前类 实例化得到一个当前类的对象
# cls ---> 当前的自定义类 HomeView
# 由于 HomeView 没有 __init__ 方法 所以初始化的时候就不需要传递额外的参数
# self 就是当前 HomeView 类的一个对象
self = cls(**initkwargs)
# 【2】HomeView 类里面没有 setup 方法
# 于是就去父类 View 里面找 向下找
# 为自己的对象初始化属性并且 保证我一定有 get 方法
self.setup(request, *args, **kwargs)
# 【4】回到了这里
# 如果 自己没有 request 属性
if not hasattr(self, 'request'):
# 就报错了
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
# 【5】self.dispatch(request, *args, **kwargs)
# 调用了自己的 dispatch 方法
# dispatch 方法 有一个返回值 并且 这个返回值一定是 Django的 response 对象
# path('login/', login, name="login"), login 是一个函数地址 能被正常响应
# path('home/', HomeView.as_view(), name="home"), HomeView.as_view() 返回的一定也是函数地址 view 的函数地址
# self.dispatch(request, *args, **kwargs) 返回的也是函数内存地址
# 【7】当请求进来的时候会触发 view 函数执行
# 接受到 self.dispatch 返回的数据 HttpResponse对象
return self.dispatch(request, *args, **kwargs)
# 初始化类属性
view.view_class = cls
view.view_initkwargs = initkwargs
update_wrapper(view, cls, updated=())
update_wrapper(view, cls.dispatch, assigned=())
return view
# 【3】从as_view 的 内嵌函数 view 函数 进入到当前的 setup
def setup(self, request, *args, **kwargs):
# (1)hasattr(self, 'get') 判断自己是否有 get 属性
# 有 自己定义的get方法
# (2)not hasattr(self, 'head') 没写过 head
if hasattr(self, 'get') and not hasattr(self, 'head'):
# 增加了一个属性 head 属性
# self.get : 自己的 get 函数的内存地址
self.head = self.get
# (3)吧 request 对象初始化尽自己的属性中
self.request = request
# (4)如果有位置参数及关键字参数则添加到自己的属性中
self.args = args
self.kwargs = kwargs
# 【6】从as_view 的 内嵌函数 view 函数 进入到当前的 dispatch
def dispatch(self, request, *args, **kwargs):
# (1)request.method.lower() : 当前浏览器提交的请求方式的小写
# GET ---> get 一定在 self.http_method_names
if request.method.lower() in self.http_method_names:
# getattr 从对象中映射出指定键对应的属性值
# 如果能拿到对应键对应的函数内存地址则就是函数内存地址
# 否则就是一个 HttpResponseNotAllowed 对象 提示当前请求方法不被允许
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
# (2)handler : get 函数的内存地址
# handler() : get 函数加 ()
return handler(request, *args, **kwargs)
# 如果请求方式是在允许的范围内 就会正常响应
# 如果不是正确的请求方式就会提示 当前请求方式不被允许
def http_method_not_allowed(self, request, *args, **kwargs):
logger.warning(
'Method Not Allowed (%s): %s', request.method, request.path,
extra={'status_code': 405, 'request': request}
)
return HttpResponseNotAllowed(self._allowed_methods())
'''
标签:get,self,request,视图,源码,CBV,POST,data,view
From: https://www.cnblogs.com/zyb123/p/18262490