过滤类的源码剖析
1、为什么在视图类中配置了一个过滤类,就可以走?
-filter_backends = [SearchFilter,MyFilter]
2、前提条件是必须继承在视图类中继承GenericAPIView:
因为filter_backends是GenericAPIView的类属性。
3、如果光继承了GenericAPIView还是不行,还需要再继承ListModelMixin,因为过滤是在ListModelMixin中做的。
当我的程序来到:
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
-我list方法中没有这个方法,最终就会找到我GenericAPIView中的filter_queryset方法:
def filter_queryset(self, queryset):
# 将我视图类中的filter_backends先强转成一个列表,然后for循环
for backend in list(self.filter_backends):
# 再实例化我这个过滤类,调用里面的filter_queryset方法得到一个对象,再将对象返回出去
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
get请求来了------>会触发我list方法的执行,会调用GenericAPIView中的filter_queryset方法,在这个方法中会将
filter_backends强转成列表形式(所以我在视图类中也可以直接让filter_backends=过滤类),然后使用for循环遍历这个列表,然后再实例化我这个过滤类,执行里边的filter_queryset方法进行筛选数据,将筛选后的数据返回出去
分页类的源码剖析
1.1、视图类中配置了分页类,为什么就有分页了?
- pagination_class = CommonCursorPagination # GenericAPIView类属性
如果光继承了GenericAPIView还是不行,还需要再继承ListModelMixin,因为分页是在ListModelMixin中做的。
当我的程序来到:
def list(self, request, *args, **kwargs):
# 过滤开始
queryset = self.filter_queryset(self.get_queryset())
#######################分页开始#######################
# 当程序走到这里就会调用paginate_queryset方法,但是这个方法List类中没有,最终会找到GenericAPIView中
page = self.paginate_queryset(queryset)
if page is not None: # 如果return None就不做分页
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
#######################分页结束#######################
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
GenericAPIView中的paginate_queryset方法:
def paginate_queryset(self, queryset):
# 调用下面的paginator的方法,得到一个None或者一个分页类
if self.paginator is None:
return None
# 得到一个分页类就会调用该类里面的paginate_queryset方法,完成分页
return self.paginator.paginate_queryset(queryset, self.request, view=self)
-self.paginator 方法包装成了属性,就是分页类的对象 CommonCursorPagination()
@property
def paginator(self):
self._paginator = self.pagination_class() # 咱们配置的分页类 CommonCursorPagination
return self._paginator
- 调用了 分页类对象的paginate_queryset--->完成真正的分页
分页类对象.paginate_queryset(queryset, self.request, view=self)
get请求过来----->执行list方法,先进行过滤,过滤完以后,开始分页,会执行GenericAPIView中的paginate_queryset方法,这个方法又会触发paginator的方法执行,在paginator的方法中会调用pagination_class属性,返回我分页类的一个对象,在paginate_queryset会拿到这个对象,判断是否为None,如果为None返回None,否则就
会调用分页类中的paginate_queryset方法,来完成分页
异常的源码剖析
为什么在配置文件中配置了自己写的全局异常处理函数,只要出了异常,它就会走?
-APIView执行流程--->dispatch的
try:
except Exception as exc:
# 执行handle_exception方法
response = self.handle_exception(exc) # exc看这里,exc是错误对象
-self.handle_exception(exc):# 执行我视图类中的handle_exception
def handle_exception(self, exc):
exception_handler = self.get_exception_handler() # 拿到的就是配置文件中配的那个函数
response = exception_handler(exc, context) #执行这个函数,传了俩参数
return response
-self.get_exception_handler()是如何从配置文件中拿出来的
-self.settings.EXCEPTION_HANDLER # 先从项目配置文件中找:key为它的EXCEPTION_HANDLER,如果项目配置文件没有,拿drf内置的配置文件
# 如果你写了全局异常处理函数,配置好了,但是前端还没有返回固定格式,可能的原因是什么?
-1 这个错误不是在三大认证和视图类的方法中产生的,之前产生的-->中间件,包装新的request
-2 你写的common_exception执行出错了
之所以能捕捉到异常是因为在APIView中的dispatch中try了一下,如果出现异常了就会执行APIView中的handle_exception方法,这个方法中需要传入一个我的异常信息,这个方法会调用get_exception_handler方法,get_exception_handler方法就是在配置文件中拿到我配置的捕捉异常的那个函数,如果我没有配置就会用drf中的那个捕捉异常的函数
接口文档
作用:
让前端知道我每个接口的用途,所以我们需要写一个接口文档来供前端使用
接口文档的展现形式:
1、word、.md
2、自动生成接口文档------>后端通过配置----->把所有接口都自动生成----->地址---->访问这个地址就可以看到所有接口
3、公司内部搭建接口文档平台:
- 开源:Yapi--->同学搭建一个,给搭建用
-https://zhuanlan.zhihu.com/p/366025001
- 自己开发(自研)
4 使用第三方平台(花钱)-->showdoc
接口文档该如何写:
以用户注册为例:
1、接口描述
2、请求地址
3、请求方式
4、编码格式:json、urlencoded、form-data
5 请求参数:参数详解
-请求地址参数
-请求体参数
6 返回格式示例--》返回参数说明
7 备注(可有可无)--》错误码
自定生成接口文档:
1、pip install coreapi
2、设置接口文档访问路径:
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('docs/', include_docs_urls(title='站点页面标题'))
]
3、在视图中加注释
4、配置文件中配置:
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
jwt介绍和构成(理论)
# 做会话保持的发展历史
-https://www.cnblogs.com/liuqingzheng/p/8990027.html
# jwt:Json Web Token:web方向的token认证方案
# 在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证(token串)。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制
# Json web token (JWT), JWT用在咱们前后端做登录认证的,如果登录了,就携带token过来,如果没登录,就不携带---》后端通过验证token的准确性,确定是谁访问我们
#JWT的构成--三部分
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
# 头:header
-一般放公司信息,加密方式(没放秘钥)
# 荷载:payload
-当前用户的信息:用户名,用户id,token过期时间。。。
# 签名:signature
-第一部分和第二部分通过加密得到的字符串
jwt的签发与认证
以后使用jwt,最核心的就两个地方
-签发:(登录接口)
-登录接口、登录成功、签发token(三段式)
-header,用base64编码,暂放
{"company": "公司信息",}
-payload,用base64编码,暂放
{用户名,用户权限,过期时间}
-使用加密方式:md5,把header和payload 都update进md5中--->生成前面--->base64编码
-三段拼接起来---》用 . 分割
-认证:(认证类)
-用户携带token过来,认证
-取出第一部分的token
-取出第二段的payload
-使用之前同样的加密算法(密码),得到前面的
-跟token的第三部分比较,如果一样的话,表示没有被修改,顺利执行下去,返回两个值
-如果被修改了抛出异常
base64编码
作用:
1、token串使用的就是base64编码格式
2、互联网中前后端数据交互,可以使用base64编码
3、图片二进制可以使用base64编码传递
介绍:
Base64编码是一种将二进制数据转换为可打印ASCII字符的编码方式。它常用于在网络传输中传递二进制数据,或者在文本协议中嵌入二进制数据。
注意事项:
需要注意的是,base64.b64encode和base64.b64decode函数的参数和返回值都是字节串(bytes类型),而不是字符串(str类型)
使用方法
import json
import base64
d = {'name': "杨赋华", 'age': 18, "gender": "男"}
#######编码
d_json = json.dumps(d)
# 注意使用base64加密必须是字节类型,所以一定要encode一下
d_base = base64.b64encode(d_json.encode("utf-8"))
print(d_base)
# b'eyJuYW1lIjogIlx1Njc2OFx1OGQ0Ylx1NTM0ZSIsICJhZ2UiOiAxOCwgImdlbmRlciI6ICJcdTc1MzcifQ==' 这就是一个标准的base64编码
#######解码
d_base_decode = base64.b64decode(d_base)
print(json.loads(d_base_decode))
# {'name': '杨赋华', 'age': 18, 'gender': '男'}
drf-jwt的介绍
# django 中使用jwt
# 可以自己写,使用第三方
-django-rest-framework-jwt:有点老
-djangorestframework-simplejwt:新的
-自己写: https://gitee.com/liuqingzheng/rbac_manager/blob/master/libs/lqz_jwt/token.py
# 下载:
pip install djangorestframework-jwt
django-rest-framework-jwt快速使用
签发:
默认是在auth_user表中签发 ------>登录接口------>内部已经封装好了
路由层:
rom rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
] # obtain_jwt_token登录接口
# 在auth_user中添加一条数据,post访问改地址,就会得到一个token串
局部认证:
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class Publish(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializers
authentication_classes = [JSONWebTokenAuthentication]
# 问题:配置了登录才可以访问该接口,但是不起作用,还是可以直接访问该接口,所以还需要配合权限类使用
权限:
from rest_framework.permissions import IsAuthenticated
permission_classes = [IsAuthenticated]
-IsAuthenticated源码解析:
class IsAuthenticated(BasePermission):
def has_permission(self, request, view):
# 如果都为True权限通过
return bool(request.user and request.user.is_authenticated)
默认认证类:带了token过来,它会校验,不带,就不校验------>不校验,request.user就没有值------>使用IsAuthenticated类做限制
前端固定写法
Authorization:jwt 中间要空一格eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InlhbmciLCJleHAiOjE2OTQyMzczNjMsImVtYWlsIjoiMTIzQHFxLmNvbSJ9.tsBOxloMlKtA6kDKANxZemDX2BzTRTYZHCa6Gey_vGM
定制签发返回格式
# 自定义认证返回结果
# 写个函数
def jwt_response_payload_handler(token, user=None, request=None):
return {
'status': 100,
'msg': '登录成功',
'token': token,
'username': user.username
}
# 配置文件配置
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.response.jwt_response_payload_handler',
}
源码分析
为什么 路由这样配了,就会有个登录接口?
path('login/', obtain_jwt_token), # obtain_jwt_token
# obtain_jwt_token = ObtainJSONWebToken.as_view()
ObtainJSONWebToken源码:
class ObtainJSONWebToken(JSONWebTokenAPIView):
# 这里面就写了一个序列化类
serializer_class = JSONWebTokenSerializer
JSONWebTokenAPIView源码:# 这里面写了一个登录接口
class JSONWebTokenAPIView(APIView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
user = serializer.object.get('user') or request.user
token = serializer.object.get('token')
response_data = jwt_response_payload_handler(token, user, request)
response = Response(response_data)
if api_settings.JWT_AUTH_COOKIE:
expiration = (datetime.utcnow() +
api_settings.JWT_EXPIRATION_DELTA)
response.set_cookie(api_settings.JWT_AUTH_COOKIE,
token,
expires=expiration,
httponly=True)
return response
# 在父类中:JSONWebTokenAPIView post
-登录走的是JSONWebTokenAPIView的post,签发token 是在序列化类中
-签发完token执行了,咱们写的jwt_response_payload_handler,所以才能定制返回格式