练习
基于APIView写过滤排序分页
分析
根据ListModelMixin分析
class ListModelMixin:
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset()) # 过滤-->GenericAPIView里的filter_queryset
page = self.paginate_queryset(queryset) # 分页--GenericAPIView里的paginate_queryset
if page is not None:
serializer = self.get_serializer(page, many=True) # 如果page有值,序列化
return self.get_paginated_response(serializer.data) # 返回了GenericAPIView--》self.paginator.get_paginated_response(data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
鼠标点进GenericAPIView里的paginate_queryset
def paginate_queryset(self, queryset): # GenericAPIView里的paginate_queryset
if self.paginator is None:
return None # 如果没有,返回none
return self.paginator.paginate_queryset(queryset, self.request, view=self) # 返回:分页类对象.paginate_queryset()
鼠标点进GenericAPIView里的paginator
@property
def paginator(self): # # GenericAPIView里的paginator
if not hasattr(self, '_paginator'): # 没有_paginator就执行
if self.pagination_class is None: # 视图里面写的列表
self._paginator = None
else:
self._paginator = self.pagination_class()
return self._paginator # 返回none或分页类对象
鼠标点进GenericAPIView的get_paginated_response()
def get_paginated_response(self, data): # GenericAPIView的get_paginated_response()
assert self.paginator is not None # 断言自己有分页类对象
return self.paginator.get_paginated_response(data) # 分页类对象调用get_paginated_response()--->PageNumberPagination的get_paginated_response
鼠标点进PageNumberPagination的get_paginated_response
def get_paginated_response(self, data): # PageNumberPagination的get_paginated_response
return Response(OrderedDict([
('count', self.page.paginator.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('results', data)
]))
实现
from rest_framework.views import APIView
from app01.models import Publish
from .serializer import PublishSerializer
from rest_framework.response import Response
from .page import MyPage
class PublishView(APIView):
def get(self,request):
qs = Publish.objects.all()
name = request.query_params.get('name')
addr = request.query_params.get('addr')
order = request.query_params.get('ordering')
print(order)
# 排序
if order:
publish_list = qs.order_by(order).all()
# 过滤 ?name=红&addr=四川
if name:
qs = qs.filter(name__contains=name).all()
if addr:
qs = qs.filter(name__contains=addr).all()
# 分页
# page = MyPage().paginate_queryset(qs,request,self)
pagination = MyPage()
page = pagination.paginate_queryset(qs,request,self)
ser = PublishSerializer(instance=page,many=True)
# return Response({'code':100,'msg':'查询成功','data':ser.data})
return Response({'code':100,
'msg':'查询成功',
'count':pagination.page.paginator.count,
'next':pagination.get_next_link(),
'previous':pagination.get_previous_link(),
'results':ser.data})
全局异常处理
1.对于前端来讲,后端即便报错,也要返回统一的格式,前端便于处理:{code:999,msg:'系统异常,请联系系统管理员'}
2.drf只要出了异常,就会执行drf配置文件中的:'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
3.只要三大认证,视图类的方法出了异常,都会执行一个函数:rest_framework.views import exception_handler
APIView执行流程
1.有了drf,后期都写CBV,都是继承APIView及其子类
2.执行流程
1.入口:path('books/', BookView.as_view()) -->请求来了执行:BookView.as_view()(request,)
2.自己写的BookView类里没有as_view绑定的方法--->父类APIView里有as_view绑定的方法
3.请求来了,执行 :(View类的as_view中的view,只是去掉了csrf的认证)(request)
4.执行父类APIView中有dispatch方法
3.总结
1.以后只要继承APIView的所有视图类的方法,都没有csrf的校验了
2.以后只要继承APIView的所有视图类的方法 中的request是新的request了
3.在执行视图类的方法之前,执行了三大认证(认证,权限,频率)
4.期间除了各种错误,都会被异常捕获,统一处理
分析
APIView中的dispatch
def dispatch(self, request, *args, **kwargs):
request = self.initialize_request(request, *args, **kwargs)
self.request = request
try:
self.initial(request, *args, **kwargs) # 执行了认证,权限和频率
# 在执行视图类方法之前,去掉了csrf认证,包装了新的request,执行了认证频率和权限
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
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
# 无论是在三大认证,还是视图类的方法中,出现错误,都会被异常捕获,统一处理
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
鼠标点进:self.handle_exception(exc)-->APIView中的handle_exception()方法
def handle_exception(self, exc):
if isinstance(exc, (exceptions.NotAuthenticated,
exceptions.AuthenticationFailed)):
auth_header = self.get_authenticate_header(self.request)
if auth_header:
exc.auth_header = auth_header
else:
exc.status_code = status.HTTP_403_FORBIDDEN
exception_handler = self.get_exception_handler()
context = self.get_exception_handler_context()
response = exception_handler(exc, context) # 处理异常
if response is None:
self.raise_uncaught_exception(exc)
response.exception = True
return response
鼠标点进:from rest_framework.views import exception_handler
def exception_handler(exc, context):
if isinstance(exc, Http404):
exc = exceptions.NotFound()
elif isinstance(exc, PermissionDenied):
exc = exceptions.PermissionDenied()
if isinstance(exc, exceptions.APIException):
headers = {}
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
if isinstance(exc.detail, (list, dict)):
data = exc.detail
else:
data = {'detail': exc.detail}
set_rollback()
return Response(data, status=exc.status_code, headers=headers)
return None
ps:
1.异常对象是drf的APIException对象,就会返回Response(data, status=exc.status_code, headers=headers),不是就返回None
2.exception_handler只处理了drf的异常,其它的异常需要我们自己处理
3.如果异常对象不是drf的APIException对象,就会返回None
注意:源码是不能改的,我们可以自定义一个函数,配置一下,以后出了异常,执行我们自己的函数
自定义报错函数
from rest_framework.response import Response
from rest_framework.exceptions import APIException
from rest_framework.views import exception_handler
def common_exception_handler(exc, context):
res = exception_handler(exc, context)
if res:
# 说明drf异常
if isinstance(res.data, dict):
detail = res.data.get('detail')
else:
print(11)
detail = res.data
return Response({'code': 102, 'msg': detail})
else:
# 说明其他异常
# return Response({'code': 101, 'msg': str(exc)})
return Response({'code': 999, 'msg': '系统异常,请联系系统管理员'})
ps:需要在项目的配置文件注册
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app02.exception.common_exception_handler',
}
补充
1.isinstance() 判断一个对象是不是某个类的对象 isinstance(对象,类)
2.issubclass() 判断一个类,是不是另一个类的子类
接口文档
概要
1.后端把接口写好后,如登录接口、登录接口、查询所有图书带过滤接口
2.前端人员需要根据接口文档,进行前端开发
3.前后端需要做对接---->对接第一个东西就是这个接口文档--->前端照着接口文档开发
4.ps:公司3个人,每个人开发了10个接口,3个人都要同时写接口文档
接口文档的编写形式
1.world、md编写--->写完放在git,公司的文档管理平台上
2.第三方的接口文档平台(收费)
eg:https://www.showdoc.com.cn/
3.公司自己开发接口文档平台
4.公司使用开源的接口文档平台,搭建
1.YAPI:百度开源的
ps:docker-compose部署Yapi-->https://zhuanlan.zhihu.com/p/366025001
5.项目自动生成接口文档
coreapi
swagger --> django推荐的
使用coreapi自动生成接口文档
步骤:
1.安装:pip3 install coreapi
2.加路由
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('docs/', include_docs_urls(title='站点页面标题'))
]
3.在视图类上加注释
1.单一方法的视图,可直接使用类视图的文档字符串,如
class BookListView(generics.ListAPIView):
"""
返回所有图书信息.
"""
2.包含多个方法的视图,在类视图的文档字符串中,分开方法定义,如
class BookListCreateView(generics.ListCreateAPIView):
"""
get:
返回所有图书信息.
post:
新建图书.
"""
3.对于视图集ViewSet,仍在类视图的文档字符串中封开定义,但是应使用action名称区分,如
class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
"""
list:
返回图书列表数据
retrieve:
返回图书详情数据
latest:
返回最新的图书数据
read:
修改图书的阅读量
"""
4.配置文件中配置:openapi--->coreapi
原来的: 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema',
改为:'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
5.访问地址:
http://127.0.0.1:8000/docs/
ps:1.表模型或序列化类的字段上写help_text--->会显示在接口文档的字段介绍上
2.参数的Description需要在模型类或序列化器类的字段中以help_text选项定义,如:
class Student(models.Model):
...
age = models.IntegerField(default=0, verbose_name='年龄', help_text='年龄')
...
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = "__all__"
extra_kwargs = {
'age': {
'required': True,
'help_text': '年龄'
}
}
3.视图集ViewSet中的retrieve名称,在接口文档网站中叫做read
jwt介绍和原理
Cookie,Session,Token发展史:https://www.cnblogs.com/liuqingzheng/p/16154439.html
1.cookie,session,token发展历史
1.会话管理
2.cookie:客户端浏览器的键值对
3.session:服务端的键值对(djangosession表,内存中,文件,缓存数据库)
ps:cookie+session:成千上万条的数据对服务器来说是有巨大压力的,严重的限制了服务器扩展能力
4.token:服务器生成的加密字符串,如果存在客户端浏览器上,就叫cookie
不在服务器上存储,保证安全,加密
-三部分:头,荷载,签名
-签发:登录成功,签发
-认证:认证类中认证
2. jwt:Json web token (JWT),web方向的token认证
Jwt认证
举例:
小F已经登录了系统, 我给他发一个令牌(token), 里边包含了小F的 user id, 下一次小F 再次通过Http请求访问的时候, 把这个token通过Http header带过去不就可以了。
不过这和session id没有本质区别啊, 任何人都可以可以伪造, 所以我得想点儿办法, 让别人伪造不了。
那就对数据做一个签名吧, 比如说我用HMAC-SHA256 算法,加上一个只有我才知道的密钥, 对数据做一个签名, 把这个签名和数据一起作为token , 由于密钥别人不知道, 就无法伪造token了。
这个token不保存, 当小F把这个token 给我发过来的时候,再用同样的HMAC-SHA256 算法和同样的密钥,对数据再计算一次签名, 和token 中的签名做个比较, 如果相同, 我就知道小F已经登录过了,并且可以直接取到小F的user id , 如果不相同, 数据部分肯定被人篡改过, 我就告诉发送者: 对不起,没有认证
Token 中的数据是明文保存的(虽然我会用Base64做下编码, 但那不是加密), 还是可以被别人看到的, 所以我不能在其中保存像密码这样的敏感信息。
基于session认证和基于token认证区别
基于session认证
基于jwt认证
base64编码和解码
1.base64并不是一种加密方式,只是编码解码方式
2.编码:base64.b64encode(二进制数据)
3.解码:base64.b64decode(二进制数据)
4.例子
import json
import base64
d = {'user_id':1,'username':'nana'}
d_str = json.dumps(d)
res = base64.b64encode(bytes(d_str,encoding = 'utf-8'))
print(res) # b'eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogIm5hbmEifQ=='
res = base64.b64decode(res)
print(res) # b'{"user_id": 1, "username": "nana"}'
5.记住: base64编码,长度一定是4的倍数。如果不够,用 = 补齐
6.base64的用途
1.互联网中,前后端数据交互,使用base64编码
2.jwt 字符串使用base64编码
3.互联网中一些图片,使用base64编码
作业
1.写一个全局异常处理,无论drf或其它异常,都统一返回格式,并写接口测试
2.使用coreapi自动生成接口文档
3.随便选择一个接口文档平台,注册,试用如何写接口文档
4.了解一下什么是mock数据(对测试,前端人来讲)
了解一下什么是mock数据(对测试,前端人来讲)
1.什么是mock数据?
前后端同时开发的时候,后端接口数据没有出来,前端可以mock假数据,模拟开发;
端知识杂记
2.mock数据的几种方式?
1.直接在页面写死数据,后期等接口来了,再改成动态的; 小型的项目,不出5个页面的可以解决,或是每个页面的数据很少的可以解决,但是不推荐,后期太麻烦
2.在js里直接声明变量,并给变量赋值,在逻辑脚本中使用,并渲染到dom;
3.将模拟数据编辑成json数据或者是零碎的js脚本中,通过请求,取回数据,并进行业务逻辑处理,渲染到dom
4. 前后台在需求分解之后,一起定义好接口api,包含:请求url(项目前缀+具体的接口名称)、请求方式、请求参数、数据响应;
前端研发人员根据接口约定,模拟请求返回对应的数据,完成对应的交互;
后台人员根据接口约定,完成对应的api,并完成对应的自测;
待后台人员交付接口api后,前端人员直接修改接口项目前缀,切换到对应的环境,即可进入项目提测