视图层
内容概要
- request 对象方法
- FBV 与 CBV
- CBV 源码刨析
- 三板斧介绍
- JsonResponse 对象
- 文件上传
内容详细
request 对象方法
参考官方文档: https://docs.djangoproject.com/en/1.11/ref/request-response/
当浏览器向服务端请求一个页面时,请求数据会先经过wsgiref 模块进行处理,再把处理过后的请求数据给到Django,Django会创建一个HttpRequest对象,该对象包含关于请求的原数据,然后再通过路由配置调用相应的视图函数,并把HttpResponse对象当作参数传给视图函数,约定俗成视图函数用 request 来接收 HttpResponse 对象,此时 HttpResponse 对象也成为 request 对象
视图函数执行完毕之后,需要有一个返回值,也是返回一个 HttpResponse 对象。
视图函数:
def test1(request):
return HttpResponse('app01 test1')
def index(request):
return render(request, 'index.html')
def beauty(request):
return redirect('https://www.mmzztt.com')
注意: django 三板斧 render, HttpResponse, redirect,通过源码也可以知道 render,redirect 最终也是返回一个 HttpResponse 对象。
request 对象常用的方法以及作用
def test(request):
if request.method == 'POST':
print(request.POST)
if request.method == 'GET':
print(request.GET)
return HttpResponse('ok')
- method 请求中使用的HTTP方法的字符串表示,全大写表示。
包含 GET、POST、等提交方式字符串
- GET 包含所有HTTP GET参数的类字典对象
接收GET请求数据,一般为urlencode类型的数据,也可以接收url问号后面传输过来的数据
- POST 包含所有HTTP POST参数的类字典对象
当form表单添加method="post",数据提交方式变成了POST请求
- FILES 包含所有上传的文件信息
注意!form表单提交数据前需要添加属性 enctype="multipart/form-data" ,在后端 request.FILES 才能拿到文件数据
- body 请求体,byte类型 request.POST的数据就是从body里面提取到的
一个字符串,代表请求报文的主体。在处理非 HTTP 形式的报文时非常有用,例如:二进制图、XML,Json等。可以用 request.body 来获取前端传过来的 Json 数据类型
- path_info 返回用户访问url,不包括域名
- get_full_path_info() 返回用户访问的url地址(路径),还可以拿到问号后面的键值
"/music/bands/the_beatles/?print=true"
FBV 与 CBV
FBV (function base views)
顾名思义基于函数的视图类
我们之前写过的都是基于函数的view,就叫FBV
def login(request):
request.get_full_path_info()
if request.method == "POST":
print(request.POST)
print(request.FILES)
return render(request, 'login.html')
路由与视图函数对应关系:
urlpatterns = [
path('login/', views.login),
]
-
给 FBV 添加装饰器
直接正常在函数头上添加语法糖即可
@login_auth
def login(request):
if request.method == "POST":
print(request.POST)
print(request.FILES)
return render(request, 'login.html')
CBV (class base views)
基于类的视图类
用类来写视图函数,类中定义相应的请求方式函数,GET请求来了就去get的视图函数,POST请求来了就去get的视图函数
class MyClass(View):
def get(self, request):
return HttpResponse('get 请求')
def post(self, request):
return HttpResponse('post 请求')
路由与视图函数对应关系:
urlpatterns = [
path('CBV/', views.MyClass.as_view()),
]
# 记住: as_view() 要加括号调用,返回的是view
-
给 CBV 添加装饰器
类中的方法与独立函数不完全相同,因此不能直接将函数装饰器应用于类中的方法 ,我们需要先将其转换为方法装饰器。
Django中提供了method_decorator装饰器用于将函数装饰器转换为方法装饰器。需要导入 django.utils.decorators 装饰器模块 的 method_decorator 函数
给 CBV 添加方法装饰器的三种方式:
# 简易装饰器
def login_auth(func):
def inner(*args, **kwargs):
res = func(*args, **kwargs)
return res
return inner
1、在类中函数头上添加(不推荐使用)
from django.utils.decorators import method_decorator
class MyClass(View):
@method_decorator(login_auth)
def get(self, request):
return HttpResponse('get 请求')
@method_decorator(login_auth)
def post(self, request):
return HttpResponse('post 请求')
2、在类头上添加
这种方法还可以指定给不同的方法添加不同的装饰器
@method_decorator(login_auth, name='get')
@method_decorator(login_auth, name='post')
class MyClass(View):
def get(self, request):
return HttpResponse('get 请求')
def post(self, request):
return HttpResponse('post 请求')
3、根据源码,在 dispatch 函数上添加装饰器,既可以做到作用于所有类方法
class MyClass(View):
@method_decorator(login_auth)
def dispatch(self, request, *args, **kwargs):
pass
def get(self, request):
return HttpResponse('get 请求')
def post(self, request):
return HttpResponse('post 请求')
CBV 源码刨析
从路由文件 urls.py 的调用语句中看起
urlpatterns = [
path('CBV/', views.MyClass.as_view()),
]
# as_view() 函数加括号优先调用 as_view函数
按住 ctrl 键,鼠标点击查看 as_view 源码
@classonlymethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs) # self 是我们自定义类产生的对象
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.setup(request, *args, **kwargs)
if not hasattr(self, 'request'): # 自定义类必须接收 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 类中的dispath函数
# ... 中间省略部分源码
return view # 返回view函数对象
从源码中可以看出,as_view 调用之后得到的是 view 函数对象,这是个闭包函数,当视图函数被触发,调用的是 view函数,view函数return一个 dispatch 函数调用结果
我们再查看 dispatch 的源码:
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names: # 如果是request中的属性,则用反射调用自身的响应的函数
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
三板斧介绍
三板斧官方文档: https://docs.djangoproject.com/en/1.11/topics/http/shortcuts/
render
结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象。
参数:
request: 用于生成响应的请求对象。
template_name:要使用的模板的完整名称,可选的参数
context:添加到模板上下文的一个字典。默认是一个空字典。如果字典中的某个值是可调用的,视图将在渲染模板之前调用它。
content_type:生成的文档要使用的MIME类型。默认为 DEFAULT_CONTENT_TYPE 设置的值。默认为'text/html'
status:响应的状态码。默认为200。 useing: 用于加载模板的模板引擎的名称。一个简单的例子:
from django.shortcuts import render
def my_view(request):
# 视图的代码写在这里
return render(request, 'myapp/index.html', {'foo': 'bar'})
上面的代码等于
from django.http import HttpResponse
from django.template import loader
def my_view(request):
# 视图代码写在这里
t = loader.get_template('myapp/index.html')
c = {'foo': 'bar'}
return HttpResponse(t.render(c, request))
redirect
参数可以是:
- 一个模型:将调用模型的
get_absolute_url()
函数 - 一个视图,可以带有参数:将使用
urlresolvers.reverse
来反向解析名称 - 一个绝对的或相对的URL,将原封不动的作为重定向的位置。
默认返回一个临时的重定向;传递permanent=True
可以返回一个永久的重定向。
你可以用多种方式使用redirect()
函数。
传递一个具体的ORM对象(了解即可)
将调用具体ORM对象的get_absolute_url()
方法来获取重定向的URL:
from django.shortcuts import redirect
def my_view(request):
...
object = MyModel.objects.get(...)
return redirect(object)
传递一个视图的名称
def my_view(request):
...
return redirect('some-view-name', foo='bar')
传递要重定向到的一个具体的网址
def my_view(request):
...
return redirect('/some/url/')
当然也可以是一个完整的网址
def my_view(request):
...
return redirect('http://example.com/')
默认情况下,redirect()
返回一个临时重定向。以上所有的形式都接收一个permanent
参数;如果设置为True
,将返回一个永久的重定向:
def my_view(request):
...
object = MyModel.objects.get(...)
return redirect(object, permanent=True)
扩展阅读:
临时重定向(响应状态码:302)和永久重定向(响应状态码:301)对普通用户来说是没什么区别的,它主要面向的是搜索引擎的机器人。
A页面临时重定向到B页面,那搜索引擎收录的就是A页面。
A页面永久重定向到B页面,那搜索引擎收录的就是B页面。
HttpResponse
JsonResponse 对象
json 格式数据的作用:
前后端在进行跨语言数据交互时,需要传输字典等更多特殊数据类型,而不是仅仅只有字符串
json格式的数据在经过序列化和反序列化,可以实现多种数据类型跨语言传输,所以几乎每种语言都会有专门序列化和反序列化json格式数据的方法
# pyhton
import json
data = {'name': 'elijah', 'age': 18}
res = json.dumps(data, ensure_ascii=False) # ensure_ascii=False 取消中文转二进制
json.loads(res) # 反序列化
// javascript
data = {'name': 'elijah', 'age': 18}
res = json.stringfy(data) // 序列化
json.parse(res) // 反序列化
视图函数中使用 JsonResponse 方法可以直接把字典转为json数据并传给前端
首先得导入 JsonResponse 类
from django.http import JsonResponse
def my_json(request):
data = {'name': 'elijah', 'age': 18}
return JsonResponse(data)
这是 JsonResponse 的源码:
- safe
safe 值为 True 表示JsonResponse 只能接收字典类型,为 False 则表示可以接收列表等其它数据类型
- json_dumps_params
由源码可知,json_dumps_params 参数最后时一个字典, ** 符号的作用就是把字典中的键值对打散变为关键字参数传给 json.dump() 函数
所以如果不想中文被转为二进制,则需要给json_dumps_params赋值一个字典,值为 {'ensure_ascii': 'False'}
def my_json(request):
data = {'name': 'elijah', 'age': 18}
return JsonResponse(data, json_dumps_params={'ensure_ascii': False}, safe=False)
文件上传
前端:
前端给后端传输文件数据,则需要给form表单标签添加属性 enctype="multipart/form-data"
这样,前端的数据会以 formdata 的数据格式传给后端
前端常见传输的数据格式:
-
urlencode: name=elijah&age=18
-
formdata:可以传输文件数据
-
json:跨语言传输数据格式
<form action="" method="post" enctype="multipart/form-data">
<label for="username">username:</label>
<input type="text" class="form-control" id="username" name="username">
<div class="form-group">
<label for="f1">头像:
<img src="" alt="">
</label>
<input type="file" name="file" id="f1">
</div>
<input type="button" id="ajax_btn">
<input type="submit" class="btn btn-primary btn-lg pull-left">
</form>
ajax 传输文件数据:
如果是以 ajax 的方式传输文件数据,则需要使用 javascript 的 formdata 对象,并且传输数据时,需要告诉浏览器不要对formdata对象进行修改
<script>
$('#ajax_btn').on('click', function () {
// 实例化FormData对象
let formDataObj = new FormData()
// 添加普通数据
formDataObj.append('username', 'elijah')
// 添加文件数据
formDataObj.append('file', $('#f1')[0].file[0])
// ajax
$.ajax({
url:"",
type: 'post',
data: formDataObj,
// 提示浏览器不要修改传输的数据,和不定义数据类型
processData:false,
contentType:false,
success:function (arg){
console.log(arg)
}
})
})
</script>
后端:
后端用 request.FILES 方法接收文件数据(是个字典),再用.get() 方法获取文件对象,并保存到目录中,文件路径保存在表的文件字段中。
def login(request):
request.get_full_path_info()
if request.method == "POST":
print(request.POST)
print(request.FILES)
files_obj = request.FILES.get('file') # 获取的是文件对象
print(files_obj.name) # 获取文件名字
with open(files_obj.name, 'wb') as f:
for line in files_obj: # 文件对象可以循环读取并写入
f.write(line)
return render(request, 'login.html')
打印结果: