Django框架(⼆⼗四:前后端分离之分页的设置和csrf认证的解决)
前后端分离开发和混合开发的区别还是很⼤的。前后端分离我们需要遵循restful规范,先介绍什么是restful api规范
a.同⼀种数据的操作,只设置⼀个url路由。也就是根据请求⽅法来区分具体的处理逻辑。⽽不再设置多个增删改查的路由。
(1)可以基于FBV来通过请求⽅法的不同,处理不同的逻辑。
url(r'^order/', der),
def order(request):
hod == 'GET':
return HttpResponse('获取订单')
hod == 'POST':
return HttpResponse('创建订单')
hod == 'PUT':
return HttpResponse('更新订单')
hod == 'DELETE':
return HttpResponse('删除订单')
(2)可以基于CBV来实现处理不同的逻辑
url(r'^order/', views.OrderView.as_view()),
class OrderView(View):
def get(self,request,*args,**kwargs):
return HttpResponse(json.dumps(ret),status=201)
def post(self,request,*args,**kwargs):
return HttpResponse('创建订单')
def put(self,request,*args,**kwargs):
return HttpResponse('更新订单')
def delete(self,request,*args,**kwargs):
return HttpResponse('删除订单')
⽽两种⽅式中,最建议使⽤CBV的⽅式去写接⼝,更加简洁,不⽤判断了。
b. 域名建议
为了对⽤户使⽤的url和⽹页中使⽤的接⼝api进⾏区别,设置如下规则
(1)⼦域名的⽅式区分(需要解决跨域的问题):
www.baidu (⽤户在浏览器中输⼊的地址,可以访问⽹站页⾯)
但是⽹页需要到后台请求接⼝,获取数据,那么接⼝的api应该如何命名呢?
api.baidu/v1/login.json
⽤户⼀看到域名是以api开头的,就知道是接⼝,返回的是json数据。
(2)URL的⽅式进⾏区分(不需要解决跨域问题):
www.baidu (⽤户使⽤的URL)
www.baidu/api/v1/login.json
不管使⽤哪种⽅式,就是为了能够⼀眼区分出来这是⼀个api接⼝。
两种⽅式哪⼀种更好呢?
答案是第⼆种,因为第⼀种可能会出现跨域请求,也就是当域名不同或者端⼝不同的时候,都会出现跨域请求,⽽第⼆种保证了域名和端⼝的⼀致性,只是url不⼀样⽽已。
跨域:因为浏览器的同源策略,当你通过浏览器向www.baidu前端页⾯发送请求的时候,⽹页需要向后台请求接⼝,但是如果接⼝的域名和当前的域名不⼀致,就会出现跨源请求的错误,⽆法访问到页⾯。⽽跨源是⽹页向api发送请求之后,服务器响应了这个请求,但是是浏览器端把这⼀次请求的响应给阻⽌了,并不是在请求不同域名的接⼝时,服务端不会响应这个请求。跨源是浏览器端的阻⽌⾏为,⽽不是服务器端的。
c. 版本规则
两个版本共存的时候,应该将API的版本号放⼊URL。
另⼀种做法是,将版本号放在HTTP头信息中,但不如放⼊URL⽅便和直观。Github采⽤这种做法。
d. ⾯向资源编程
将⽹络中的任何东西都看作是资源,对资源可以进⾏增删改查的操作,但是资源表⽰的是⼀个名称,如果⼀个url后⾯跟的是⼀个名词(单复数都可以),所⽤的名词往往与数据库的表格名对应,就表⽰要对这个资源进⾏增删改查的操作了。⽽get/post/delete/put是动词,所以url中不建议出现动词。
www.baidu/api/v1/order/ (遵循规范)
www.baidu/api/v1/orders/ (遵循规范)
www.baidu/api/v1/get_order/ (没有遵循规范)
e. HTTP⽅法规范
GET:从服务器上获取⼀个或者多个资源
POST:在服务器上新建⼀个资源
PUT:在服务器跟新全部资源
PUT:在服务器跟新全部资源
PATCH:在服务器更新部分资源
⽐如⽤户表就有⽤户名,密码,性别,如果是PUT就全部更新。如果是PATHCH就只更新密码。
f. 过滤规范
www.baidu/api/v1/orders/?status=1&page=2&offset
g. 状态码规范(状态码+code码)
后台提供的状态码,供前端使⽤。
200系列,300系列表⽰重定向,400系列表⽰客户端错误,500系列表⽰服务端错误(后台代码错误)。
但是只有状态码还是不够的,请求的状态太多,所以除了使⽤状态码表⽰状态以外,还应该有code码来表⽰更加详细的请求情况。
⽐如:⽀付宝的code码,20000,20001等
def get(self,request,*args,**kwargs):
ret = {
'code':1000,
'msg':'没有携带cookie'
}
return HttpResponse(json.dumps(ret),status=201)
h. 请求的返回值规范
GET /order/:返回资源对象的列表(数组)
GET /order/1/:返回单个资源对象
POST /order/:返回新⽣成的资源对象
PUT /order/1/:返回完整的资源对象
PATCH /order/1/:返回完整的资源对象
DELETE /order/1/:返回⼀个空⽂档
i. Hypermedia API超链接规范
希望在请求结果中包含这⼀个资源的详细信息的api。
⽐如,我们请求商品列表信息得到如下的结果:
[
{
"id": 1,
"name": "袜⼦"
},
{
"id": 2,
"name": "裤⼦"
},
{
"id": 3,
"name": "鞋⼦"
}
]
如果你想查看id=1的商品的详细信息,你需要使⽤这个id拼接详情页的url地址,并发出请求,但是restful中希望这个详情页的url直接包含在json数据中,不⽤再单独进⾏拼接了。
[
{
"id": 1,
"name": "袜⼦",
"url": "www.baidu/api/v1/1/"
},
{
"id": 2,
"name": "裤⼦"
"url": "www.baidu/api/v1/2/"
},
{
"id": 3,
"name": "鞋⼦"
"url": "www.baidu/api/v1/2/"
}
]
根据上⾯的规范来进⾏实例的操作:
url路由的定义应该尽量使⽤通⽤视图:
ib import admin
from django.urls import path
from stuapp.views import *
urlpatterns = [
path('admin/', admin.site.urls),
path('token/', get_csrftoken),
path('students/', StudentView.as_view()),
path('inspect/', StudentInspectView.as_view()),
]
views.py⽂件中实现通⽤视图类函数,并将相关数据传给前端
# CBV和FBV
# CBV:基于类的视图 class StudentView(View):
# FBV:基于函数的视图 def student(request):
class StudentView(View):
@method_decorator(allow_origin)
def get(self, request):
"""
学⽣数据查询接⼝
功能:分页返回学⽣数据,前端传递page=1就返回第⼀页的数据;
page: 页码
size: 每页的数据个数
:param request:
:return:
"""
error = ''
# 1. 查询所有的学⽣数据
stus = StuModel.objects.all()
# 2. 根据前端ajax传递的page&size参数开始做分页数据
size = int(('size', '2'))
page_number = int(('page', '1'))
paginator = Paginator(stus, size)
try:
page = paginator.page(page_number)
except (EmptyPage, PageNotAnInteger, InvalidPage):
error = '已经是最后⼀页了'
page = paginator.page(paginator.num_pages)
page_number = paginator.num_pages
# 3. 开始做分页
# 假设分页器上只显⽰5个页码,分页器出现滚动之后,当前页始终在中间,当前页前后各两个页码; if paginator.num_pages <= 5:
# 全部展⽰,将当前所有页码的值返回给前端
page_nums = [x for x in range(1, paginator.num_pages + 1)]
elif page_number < 4:
# 如果总页数超过5页了,但是当前页的页码⼩于4的时候,分页器是同样不会滚动的。
# 1 2 3 4 5
# 2 3 4 5 6
# 3 4 5 6 7
page_nums = [x for x in range(1, 6)]
elif page_number - 4 >= 0 and page_number <= paginator.num_pages - 2:
django项目实例# 如果总页数超过5页了,分页器需要滚动
page_nums = [x for x in range(page_number - 2, page_number + 3)]
else:
# 超过5页,但是已经到最后⼀页了,页⾯不再滚动
page_nums = [x for x in range(paginator.num_pages - 4, paginator.num_pages+1)]
#4. 向前端返回json数据
previous = page.has_previous()
next = page.has_next()
data = {
'code': 100,
'status': 'ok',
'error': error,
# 总的数据个数
'total_pages': len(stus),
# 是否有上⼀页
'has_previous': previous,
'previous_url': page_number-1 if previous else None,
# 是否有下⼀页
'has_next': next,
'next_url': page_number+1 if next else None,
'page_nums': page_nums,
# 当前页的数据列表
'results': object_to_json(page.object_list),
'current_page': page_number
}
# response = JsonResponse(data)
# 允许所有的源,向这个接⼝发送请求并得到响应。(改变浏览器默认的禁⽌跨域,此时就是允许跨域。)
# response['Access-Control-Allow-Origin'] = '*'
return data
from django.http import JsonResponse
def allow_origin(func):
def _func(*args, **kwargs):
data = func(*args, **kwargs)
response = JsonResponse(data)
response['Access-Control-Allow-Origin'] = '*'
return response
return _func
另外还使⽤了object_to_json()函数,它的作⽤是将QuerySet对象转为⼀个字典,因为前端不能识别QuerySet对象,实现代码如下:
from dels.query import QuerySet
def object_to_json(model, ignore=None):
"""
函数的作⽤就是将ORM中的Model对象,转化成json对象,再返回给前端
:param model:
:param ignore:
:return:
"""
if ignore is None:
ignore = []
if type(model) in [QuerySet, list]:
json = []
for element in model:
json.append(_django_single_object_to_json(element, ignore))
return json
else:
return _django_single_object_to_json(model, ignore)
def _django_single_object_to_json(element, ignore=None):
return dict([(attr, getattr(element, attr)) for attr in [f.name for f in element._meta.fields if f not in ignore]])
前端的js代码:
function loadData(page) {
// 该函数是根据page的值,加载当前页数据的函数;
// page: 表⽰当前页的页码
list_url = '127.0.0.1:8000/students/?page=' + page + '&size=3';
$.get(list_url, function(data){
// 解析后台接⼝返回的json数据
/
/ 因为loadData(page)是循环调⽤的,所以在每次append()之前,先将上⼀次的数据清空,然后再append()新的数据。
$('tbody').empty();
for (var index sults){
var student = sults[index];
tr = $('<tr>');
// 向<tr>标签中添加三个<td>,分别是姓名、ID、年龄
tr.append($('<td>').text(student.name), $('<td>').text(student.id), $('<td>').text(student.age));
// 继续向<tr>标签中添加编辑图标和删除图标
tr.append($('<td>').append($('<span>').attr({
'class': 'glyphicon glyphicon-edit',
'data-toggle': 'modal',
'data-target': '#editModal'
})));
tr.append($('<td>').append($('<span>').attr({'class': 'glyphicon glyphicon-remove'})));
$('tbody').append(tr);
}
// 开始设置分页
$('.pagination').empty();
if (data.has_previous){
// 有上⼀页
$('.pagination').append($('<li>').append($('<a>').attr({'aria-label': 'Previous', 'href': 'javascript:loadData(' + data.previous_url + ');'}).text('<<')));
}
// [2 3 4 5 6]
for (var index in data.page_nums){
var page_number = data.page_nums[index];
var li = $('<li>');
if (data.current_page == page_number){
//如果当前请求的页码的值current_page和遍历出来的page_number的值相等,将这个页码标记为选中状态。
li.attr('class', 'active');
}
// 向这个li内部添加⼀个a标签
li.append($('<a>').attr({'href': 'javascript:loadData(' + page_number + ');'}).text(page_number));
// 再将这个li添加到ul标签中;
$('.pagination').append(li);
}
// 下⼀页
if (data.has_next){
$('.pagination').append($('<li>').append($('<a>').attr({'aria-label': 'Next', 'href': 'javascript:loadData(' + _url + ');'}).text('>>')));
}
});
}
loadData(1);
前后端分离中CSRF的问题:
1. 在前后端分离中,接⼝的调⽤本⾝就是⼀种跨站请求,因为这个接⼝既要被安卓端的站点访问,⼜要被苹果端的站点访问,所以在这
种模式下,CSRF的认证就失去作⽤了。解决⽅案:取消CsrfMilldeware中间件对于CSRF的认证;
2. 如果在前后端分离中,必须要进⾏csrf认证,也可以实现;
CSRF⼯作原理:
FORM表单提交POST请求时:
每次渲染页⾯,在Html模版中,{{ csrf_token }}都会加载⼀个随机字符串,每次的值都是不⼀样的;这个值会放在请求体Form Data中,提交⾄后台;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论