昨日回顾
1 频率限制
-写一个类,继承SimpleRateThrottle,重写get_cache_key,
返回什么就以什么做限制(限制ip,限制用户id,手机号),
-再写一个类属性scope='字符串', 需要跟配置文件中对应 '字符串':'5/m'
-局部配置,全局配置
# 注意:若是以ip限制全局配置,那么所有的接口总计不能超过指定的次数,就不单是某个接口次数限制
2 过滤和排序
-内置过滤类:SearchFilter
-内置排序类:OrderingFilter
-配置在继承GenericAPIView+ListModelMixin及其子类的view接口中,类属性:filter_backends=[]
-配置相应的字段
-search_fields=['name']
-ordering_fields = ['price']
3 第三方过滤类
-配置在类属性上:filter_backends = [DjangoFilterBackend]
-配置字段:filterset_fields=['name','price']
-http://127.0.0.1:8000/books/?name=红楼梦&price=12
4 自定义过滤类
-自己写一个类,继承BaseFilterBackend,重写filter_queryset,返回queryset对象,queryset是过滤后的
-配置在继承GenericAPIView+ListModelMixin及其子类,类属性:filter_backends
5 分页功能
-三个分页类
PageNumberPagination:基本分页 ?page=1&size=2
-四个类属性
LimitOffsetPagination:偏移分页 ?limit=3&offset=2
-四个类属性
CursorPagination:游标分页 选择上一页和下一页
-三个类属性
-cursor_query_param = 'cursor' # 查询条件
-page_size = 2 # 每页显示多少条
-ordering = 'id' #按谁排序
-只需要配置在继承GenericAPIView+ListModelMixin及其子类,类属性:pagination_class
今日内容
1. drf整体流程
# drf处于的位置:路由匹配成功,进视图类之前
# 整体处理流程:
1.包装新的request对象
2.处理编码(urlencoded,formdata,json)
3.三大认证(顺序:认证、权限、频率)(*****)
4.进入视图类:
-4.1 若是继承(APIView)(*****)
-去模型中取数据
-序列化(*****)
-返回数据
-4.2 若是继承(GenericAPIView+ListModelMixin)(*****)
-去模型中取数据
-进行过滤和排序
-分页
-序列化(*****)
-返回数据
-5.处理了响应(浏览器,json)
-6.处理了全局异常
2. 源码分析--认证、频率、权限
2.1 认证源码
# 1.入口:APIView的dispatch()中--->self.initial()中处理三大认证--->认证类的代码:self.perform_authentication(request)
# 2.self.perform_authentication(request)--->就是APIView的perform_authentication()
def perform_authentication(self, request):
request.user # 执行了新的request对象的user方法
# 3.Request类的user方法
@property
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors():
# 核心就是这句话,是Request的_authenticate()方法
self._authenticate()
return self._user
# 4.Request类的_authenticate(self)方法 (核心)
def _authenticate(self):
for authenticator in self.authenticators: # self.authenticators是个列表,列表中放了一个个认证类的对象
try:
# self是request,所以自定义认证类的authenticate,有两个参数(self,request),第二个参数request给了这里的self
user_auth_tuple = authenticator.authenticate(self) # 执行认证类的authenticate方法
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple # 解压赋值,后续的request对象就有user属性了
return
# 源码可知:若是写了多个认证类,只要有一个认证类中认证通过,返回了user和token,后续的认证类就不会for循环执行了
self._not_authenticated()
# 5.Request类的self.authenticators属性 是多个认证类对象的列表
-是在Request初始化的时候,传入的
-Request类是在什么时候初始化的---》APIView的dispatch中的刚开始位置
-APIView的dispatch()---> request = self.initialize_request(request, *args, **kwargs)
# 6.APIView的self.initialize_request方法
def initialize_request(self, request, *args, **kwargs):
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),# APIView的
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
# 7.APIView的get_authenticators方法
def get_authenticators(self):
# 列表中放了一个个认证类的对象
return [auth() for auth in self.authentication_classes]
2.2 权限源码
# 1.入口:APIView的dispatch()中--->self.initial()中处理三大认证--->权限类的代码self.check_permissions(request)
# 2.APIView的check_permissions方法
def check_permissions(self, request):
for permission in self.get_permissions(): # 列表,是一个个视图类中配置的权限类的对象
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
# 3.APIView的self.get_permissions():
def get_permissions(self):
# 列表里放了一个个权限类的对象
return [permission() for permission in self.permission_classes]
# 4.权限认证失败,返回中文
-在权限类中配置message即可(给对象,类都可以)
2.3 频率源码
# 1.入口:APIView的dispatch()中--->self.initial()中处理三大认证--->频率类的代码self.check_throttles(request)
# 2.APIView的self.check_throttles(request)
def check_throttles(self, request):
throttle_durations = []
for throttle in self.get_throttles(): # 列表,是一个个视图类中配置的频率类的对象
# 如果被限制了,就把剩余时间 追加到 throttle_durations 限制持续时间 列表中
if not throttle.allow_request(request, self):
throttle_durations.append(throttle.wait())
# 3.APIView的self.get_throttles()
def get_throttles(self):
# 列表里放了一个个频率类的对象
return [throttle() for throttle in self.throttle_classes]
# 从频率的源码可知:
若是自定义的频率类 是继承SimpleRateThrottle的,可以直接将限制的值 写在频率限制类中
THROTTLE_RATES = {'ip_m_3': '3/m'}
# SimpleRateThrottle源码分析:
def get_rate(self): # 根据配置的限制值的key:scope,从THROTTLE_RATES中 取出限制值:rate
"""
Determine the string representation of the allowed request rate.
"""
if not getattr(self, 'scope', None):
msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
self.__class__.__name__)
raise ImproperlyConfigured(msg)
try:
return self.THROTTLE_RATES[self.scope] # scope:'user' => '3/min'
except KeyError:
msg = "No default throttle rate set for '%s' scope" % self.scope
raise ImproperlyConfigured(msg)
def parse_rate(self, rate): # 根据配置拆分出 限制次数 和 持续时间
"""
Given the request rate string, return a two tuple of:
<allowed number of requests>, <period of time in seconds>
"""
if rate is None:
return (None, None)
# 3 m
num, period = rate.split('/') # rate:'3/min'
num_requests = int(num)
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
return (num_requests, duration)
def allow_request(self, request, view):
if self.rate is None:
return True
# 当前登录用户的ip地址
self.key = self.get_cache_key(request, view) # key:'throttle_user_1'
if self.key is None:
return True
# 从缓存中取出 存放IP访问时间 的列表,若是初次访问,缓存为空,self.history为[]
self.history = self.cache.get(self.key, [])
# 获取一下当前时间,存放到 self.now
self.now = self.timer()
# Drop any requests from the history which have now passed the throttle duration
# 将时间列表中 超过持续时间的 全部删除
# 当前访问与第一次访问时间间隔如果大于60s,将第一次记录清除,不再算作一次计数
# self.history:[10:23,10:55]
# now:10:56
while self.history and self.now - self.history[-1] >= self.duration:
self.history.pop()
# history的长度与限制次数3进行比较
# history 长度第一次访问0,第二次访问1,第三次访问2,第四次访问3失败
if len(self.history) >= self.num_requests:
# 直接返回False,代表频率限制了
return self.throttle_failure()
# history的长度未达到限制次数3,代表可以访问
return self.throttle_success()
def throttle_success(self): # 将当前时间插入到history列表的开头,将history列表、key、过期时间作为数据存到缓存中
"""
Inserts the current request's timestamp along with the key into the cache.
"""
self.history.insert(0, self.now)
self.cache.set(self.key, self.history, self.duration)
return True
3. 自定义全局异常处理
# 1.自定义一个全局异常处理函数
def common_exception_handler(exc, context):
"""
# 一般会将错误信息添加到日志:
通过参数 exc可获取错误提示信息,通过参数context可获取错误详细信息
str(exec) # 错误提示信息
str(context['view']) # 错误发生的视图
context['request'].META.get('REMOTE_ADDR') # IP地址
context['request'].user.id # 用户的ID
"""
# 1. 执行drf默认的异常处理
response = exception_handler(exc, context)
# 2.判断是否能被默认的异常处理捕获
if response:
# 2.1 若能被默认的捕获到,会返回一个Response对象出来,我们可.data 拿出原本默认要返回给前端的数据,再结合自己的格式 指定返回
return Response(data={'code': 9998, 'msg': response.data})
else:
# 2.2 若不能被默认的捕获到,会返回None,就自定义处理 返回给前端
return Response(data={'code': 9999, 'msg': '服务器异常,请联系系统管理员'})
# 2.在配置文件中
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler',
}
4. 自动生成接口文档
# 前后的分离
-前端一批人
-根本不知道你写了什么接口,请求参数什么样,响应数据什么样
-使用什么编码都不知道
-后端一批人
-我们写了很多接口
# 需要写接口文档(不同公司有规范)
-1 公司有接口文档平台,后端在平台上录入接口
-2 使用第三方接口文档平台,后端写了在平台录入
-Yapi:开源
-3 使用md,word文档写,写完传到git上
-4 自动生成接口文档(swagger,coreapi)
-通过swagger自动生成后导出,再导入到Yapi中
# coreapi 第三方模块--自动生成接口文档
# 1 安装:pip install coreapi
# 2 在路由中配置
from rest_framework.documentation import include_docs_urls
urlpatterns = [
...
path('docs/', include_docs_urls(title='站点页面标题'))
]
# 3 视图类:自动接口文档能生成的是继承自APIView及其子类的视图。
-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 在配置文件中配置
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
补充
1.函数显示传参类型和返回值
# python3.5以上版本,typing模块提高代码健壮性 (公司常见写的方式)
from typing import List, Tuple, Dict
def test(a: int, string: str, f: float, b: bool) -> Tuple[List, Tuple, Dict, bool]:
ll=[1,2,3,4]
tup = (string, a, string)
dic = {"xxx": f}
boo = b
return ll, tup, dic, boo
print(test(12, "lqz", 2.3, False))
作业
# 1 三个认证的源码---》自己捋一遍
# 2 写一个全局异常处理函数,保证无论出什么异常,前端都返回固定格式
def common_exception(exc, context):
# 第一步:记录异常信息到日志
print(
f'异常视图:{str(context["view"])} 访问IP:{context["request"].META.get("REMOTE_ADDR")} '
f'访问用户ID:{context["request"].user.id} 异常信息:{str(exc)}')
# 第二步:调用rest_framework 的异常捕获
response = exception_handler(exc, context)
# 第三步:判断是否能被内置的捕获到
if response:
# 若能,将内部返回的数据取出来,再加上我们自己的格式 返回给前端
return Response(data={'code': 9999, 'msg': response.data})
return Response(data={'code': 99998, 'msg': '系统出错,请联系管理员'})
# 3 试一下coreapi自动生成接口文档
# 4 给你一个地址,上地址看看Yapi怎么用