类视图
为同一接口支持不同的请求类型
request
我们知道当URL文件匹配到用户输入的路径后,会调用对应的view函数,并将request对象作为第一个参数传入该函数。request就是HttpRequest对象的实例,属性包含了关于此次请求的大多数重要信息。
支持不同的请求类型
既然request带有类型,我们就根据不同类型判断,执行对应的操作。修改子应用的views.py文件。
def login(request):
if request.method == 'GET':
return HttpResponse('Hello, 这是一个get请求的登录的页面!')
elif request.method == 'POST':
return HttpResponse('Hello, 这是一个post请求的登录的页面!')
else:
return HttpResponse(f'Hello, 这是一个其他请求的登录的页面!{request.method}')
测试的时候发现,除get外,其他请求方式会报403错误。
这是因为Django发起其他请求时,会验证CSRF,需要携带CSRF TOKE才行,如果没有,就会报这个异常。我们可以在setting.py文件中关掉CsrfViewMiddleware中间件。
但是这种函数视图不好管理,所有的代码都在一个方法中,不好维护,我们可以使用类视图来优化一下。
类视图
我们将上面那个例子改写成类视图的写法:
class user_login(View):
def get(self, request):
return HttpResponse('Hello, 这是一个get请求的登录的页面!')
def post(self, request):
return HttpResponse('Hello, 这是一个post请求的登录的页面!')
def put(self, request):
return HttpResponse('Hello, 这是一个put请求的登录的页面!')
需要注意的是类视图要继承View。不同的请求方式是与请求方法对应的,如get请求就会自动调用get方法。
看了View的源码,支持的请求方式有:http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
修改完视图后,还需新增一个路由。修改urls.py文件:
urlpatterns = [
path('user_login/', user_login.as_view()),
]
与函数视图的路由不同的是,第二个参数为函数名.as_view()
。
总结一下,类视图可读性和复用性更好,不同的请求方式以不同的方法呈现,后面我们就都用类视图了。
我们前面的例子都有一个问题,就是没有一个完整的前端页面,我们现在把前端页面也加上。
在项目根目录新建一个templates文件夹,专门放前端代码。在此文件夹中新建一个login.html。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>项目列表页</title>
<style>
.container-fluid {
margin-top: 100px;
}
.table-striped th, .table-striped td {
text-align: center;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row" style="margin-bottom: 30px">
<div class="col"></div>
<div class="col"><h2 class="text-center text-info">项目列表信息</h2></div>
<div class="col"></div>
</div>
<div class="row">
<div class="col"></div>
<div class="col">
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th scope="col">序号</th>
<th scope="col">项目名称</th>
<th scope="col">项目负责人</th>
<th scope="col">应用名称</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1</th>
<td>一个测试项目</td>
<td>Aaron</td>
<td>测试项目</td>
</tr>
</tbody>
</table>
</div>
<div class="col"></div>
</div>
</div>
</body>
</html>
写完前端代码后,还需要在setting.py文件中指定前端代码的存放路径。
然后修改子应用的views.py文件,在适当的时候返回这个页面。
class user_login(View):
def get(self, request):
return render(request, 'login.html')
再刷新一下页面就看到效果了。
前后端不分离
前面的例子有一个缺点,就是页面是写死的,但是一般来说,我们前端页面的数据都是从数据库取的,所以还需要在修改一下。
将前端的页面修改一下,把刚才写好的数据删掉,替换成动态的(这个不需要掌握,了解一下即可):
{% for foo in datas %}
<tr>
<th scope="row">{{ forloop.counter }}</th>
<td>{{ foo.project_name }}</td>
<td>{{ foo.leader }}</td>
<td>{{ foo.app_name }}</td>
</tr>
{% endfor %}
修改子应用的views.py文件。
class user_login(View):
def get(self, request):
# 假设这是从数据库取的数据
datas = [
{
"project_name": "项目1",
"leader": "Aaron",
"app_name": "第一个app"
},
{
"project_name": "项目2",
"leader": "小伍",
"app_name": "第二个app"
},
]
return render(request, 'login.html', context=locals()) # context参数把数据传到html中
此时就可以看到前端页面数据是上面列表中的数据了。
但是这种方法不好,前后端职责不清,后端还需要去改前端代码,造成项目管理混乱,而且拓展性也差。所以一般我们不使用这种做法。我们采用前后端分离来做。
前后端分离
后端给前端返回json数据
如果需要后端给前端返回数据,那么子应用的views.py文件中,该方法的返回值就需要修改一下:
def get(self, request, pk):
# a.使用JsonResponse可以返回json数据
# b.如果第一个参数为字典,那么无需指定safe关键字参数
# c.如果第一个参数为嵌套字典的列表,那么必须指定safe=False关键字参数
# d.可以使用status来指定响应状态码
return JsonResponse(data=datas, safe=False, status=201)
后端解析前端的数据
query string参数接收
http://127.0.0.1:8000/login/user_login/?name=Aaron&age=18中的?name=Aaron&age=18
就是query string查询字符串参数。
通过debug我们不难发现,传入的参数在request对象中。
我们通过request.GET
就能获取到前端传来的query string字符串参数,request.GET获取到的是一个QueryDict类型的参数,这是继承了Dict的类型,我们可以使用字典的取值方式来取值。
如果字符串参数中的key重复,会把相同key的值放入列表。例如:?name=Aaron&age=18&name=heihei,我们获取到的就是{‘name’:['Aaron','heihei'],'age','18'}
,此时再使用字典获取值的方式获取name,获取到的数据就是‘heihei’,我们想要获取到name列表,就要用到QueryDict的getlist方法来获取。request.GET.getlist('name')
。
请求体参数接收
x-www-form-urlencoded
可以使用request.POST
获取x-www-form-urlencoded参数,具体操作方式同上面的query string参数。
json参数
可以使用request.body
获取json参数,获取到的是字节类型,我们需要使用decode('utf-8')
进行解码,解码后得到一个json的字符串形式,我们在转换成字典就可以取值了。
json_dict = json.loads(request.body.decode('utf-8')) # {‘name’:'Aaron','age','18'}
form-data
form-data和x-www-form-urlencoded的区别是form-data可以传文件参数,form-data的文本参数使用request.POST
获取,非文本参数使用request.body
获取。
请求头参数
可以使用request.META
获取请求头参数,传入的请求头参数为{‘name’:‘Aaron’,‘age’,‘18’},python会把参数名转换为大写,并且加上前缀HTTP_
。如果参数名有-,就转换成_。