内容回顾
# drf:方便我们在django框架上写出符合restful规范的接口
# 请求和响应
-请求类的对象
-请求解析编码格式:局部,全局
-响应对象
-data,status,header
-响应格式:浏览器,json
# 序列化类
-Serializer
-ModelSerializer(用的多,表模型有对应关系)
# 视图类
-两个视图基类
-APIView:执行流程
-GenericAPIView:两个类属性,三个方法
-5个视图扩展类(不是视图类)
-需要配合GenericAPIView使用
-9个视图子类
-视图集
-ModelViewSet
-ReadOnlyModelViewSet
-ViewSet
-GenericViewSet
-ViewSetMixin
# 路由
-自动生成路由(SimpleRouter,DefaultRouter)
-action装饰器
# 认证类
-写一个类继承BaseAuthentication重写authenticate在里面判断用户是否登录,如果登录了返回两个值,第一个值必须是当前登录用户,如果认证失败,抛出异常
-局部使用
-全局使用
# 权限类
-写一个类继承BasePermission重写has_permission在里面判断用户是否有权限,如果有,return True,否则return False
-局部使用
-全局使用
今日内容
1. drf之频率限制
# 限制用户的访问次数:根据用户ip地址限制
例:一个IP地址,一分钟之内只允许访问三次
# 获取访问者IP地址
-request.META.get('REMOTE_ADDR') # REMOTE:远程
# request.META:请求头中所有的数据
# 自定义请求头的数据,也在request.META中,且给你转成 key为 'HTTP_大写'的形式
1.1 频率类定义
# 1.写一个类,继承SimpleRateThrottle
# 2.重写get_cache_key(),返回以什么做限制 (返回ip就以ip限制)
# 3.配置 频率类 限制值的 key
在类中写一个类属性:scope = 'ip_m_3' key自定义,要在 配置文件中或频率类中 直接配置一个与之对应的值
# 4.配置 频率类 限制的值
# 写在配置文件中
在settings.py中写
REST_FRAMEWORK = {
# key值是频率类中scop字段对应的值,value是访问次数限制值
'DEFAULT_THROTTLE_RATES': {'ip_m_3': '3/m',}
}
# 写在频率限制类中
THROTTLE_RATES = {'ip_m_3': '3/m'}
# 写一个频率限制类,根据ip地址 一分钟只能访问三次
from rest_framework.throttling import SimpleRateThrottle
class MyThrotting(SimpleRateThrottle):
scope = 'ip_m_3' # 配置 限制值的key
THROTTLE_RATES = {'ip_m_3': '3/m'} # 配置 限制的值
def get_cache_key(self, request, view):
# return request.META.get('REMOTE_ADDR')
return self.get_ident(request) # 内置的get_ident()就是返回访问者的ip,是父类BaseThrottle的方法
1.2 频率类使用
# 局部使用
-配置在视图类中
class IndexView(APIView):
throttle_classes = [MyThrotting, ]
# 全局使用
-配置在settings.py中
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': ['app01.auth.MyThrotting',],
}
1.3 自定义频率类(继承BaseThrottle) (了解逻辑 以后不会用这个)
# 自定制频率类(继承BaseThrottle),限制ip一分钟只能访问三次
实际可以不继承BaseThrottle类,只要你有该类里面的allow_request方法,你就是该类,原理是鸭子类型
# 需要写两个方法
判断是否限次:没有限次可以请求True,限次了不可以请求False
def allow_request(self, request, view):
限次后调用,显示还需等待多长时间才能再访问,返回等待的时间seconds
def wait(self):
# 自定义实现频率类功能的逻辑:
1)取出访问者ip
2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
# 代码
import time
class IPThrottle():
# 定义成类属性,所有对象用的都是这一个
VISIT_RECORD = {}
def __init__(self):
self.history=[]
def allow_request(self, request, view):
ip=request.META.get('REMOTE_ADDR')
ctime=time.time()
if ip not in self.VISIT_RECORD:
self.VISIT_DIC[ip]=[ctime]
return True
self.historyt=self.VISIT_VISIT_RECORD.get(ip) # 当前访问者时间列表拿出来
while self.history and ctime-self.history_list[-1]>60:
self.history.pop() # 把最后一个移除
if len(self.history)<3:
self.history.insert(0,ctime)
return True
else:
return False
def wait(self):
# 当前时间,减去列表中最后一个时间
ctime=time.time()
return 60-(ctime-self.history_list[-1])
1.4 内置的频率类
from rest_framework.throttling import AnonRateThrottle, UserRateThrottle, ScopeRateThrottle
# AnonRateThrottle
限制所有匿名未认证用户,使用IP区分用户
setting中 'DEFAULT_THROTTLE_RATES': {'anon': '5/m',} 设置频次
# UserRateThrottle
限制认证用户,使用User id 来区分 # 但是认证用户,也必须使用内置的认证类:SessionAuthentication
setting中 'DEFAULT_THROTTLE_RATES': {'user': '10/m',} 设置频次
# ScopeRateThrottle
限制用户对于每个视图的访问频次,使用IP或User id
# 例1:限制未登录用户1分钟访问5次
from rest_framework.throttling import AnonRateThrottle
class TestView1(APIView):
# 配置内置频率类 (局部配置)
throttle_classes = [AnonRateThrottle]
def get(self,request,*args,**kwargs):
return Response('我是未登录用户')
-配置在settings.py中
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {'anon': '5/m',}
}
# 例2:限制登录用户1分钟访问10次
from rest_framework.authentication import SessionAuthentication
from rest_framework.throttling import UserRateThrottle
class TestView2(APIView):
# 配置内置认证类 (局部配置)
authentication_classes=[SessionAuthentication]
# 配置内置频率类 (局部配置)
throttle_classes = [UserRateThrottle]
def get(self,request,*args,**kwargs):
return Response('我是登录用户')
-配置在settings.py中
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {'user': '10/m',}
}
# 或直接例1和例2 采用全局配置
全局:在setting中
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
),
'DEFAULT_THROTTLE_RATES': {
'user': '10/m',
'anon': '5/m',
}
2. drf之过滤与排序
# 请求地址中带过滤条件 (条件过滤、排序过滤)
# 配置使用:
# 1.局部配置
在视图函数中配置: filter_backends= [内置,第三方,自己写]
# 2.全局配置
在setting中: 'DEFAULT_FILTER_BACKENDS': (内置,第三方,自己写)
# 注意:
1.获取所有的数据 才需要过滤条件
2.必须继承GenericAPIView++ListModelMixin及其子类,才能使用过滤、排序和分页
(只有获取所有 list()方法 才有过滤、排序和分页功能, 因为过滤、排序、分页都需要queryset对象)
3.因内置的过滤和排序类,使用时很固定,必须是search=值 、ordering=值
故:通常过滤类 不使用内置的,使用第三方(django-filter)或者自定义过滤类,排序类使用内置的就可以了
# 自己读源码发现:第三条的原因是错误的,是可以修改查询条件的参数名,且有一定的查询条件(但还是太少,且不方便)
思路:继承内置的SearchFilter类,重写父类的 search_param参数,就可以按照自己给的参数进行查询
from rest_framework.filters import SearchFilter
class FilterByName(SearchFilter):
search_param = 'name'
2.1 内置过滤类使用
from .models import Book
from .serializer import BookSerializer
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
# 过滤功能的使用
from rest_framework.filters import SearchFilter,OrderingFilter # 条件过滤、排序过滤
class BookView(GenericViewSet, ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 配置过滤类
filter_backends = [SearchFilter,]
# 配置要过滤的字段
search_fields=['name',] # books/?search=红 name中 带红都会查出来
search_fields=['name','price'] # books/?search=红 name中或者price中 带红都会查出来
# 特殊字符+配置过滤的字段 ===> 起到额外的筛选作用 自己读源码获知(了解即可)
# 默认是'icontains' 忽略大小写的包含
lookup_prefixes = { # 查询特殊符 做前缀
'^': 'istartswith', # 以什么开始
'=': 'iexact', # 完整的查
'@': 'search', # 搜索 (貌似使用有点问题)
'$': 'iregex', # 以正则查
}
# 例:
search_fields=['^name',] # 查找name字段中 以搜索值为开头的
# 使用:
http://127.0.0.1:8000/books/?search=红
2.2 内置排序类使用
from rest_framework.filters import SearchFilter, OrderingFilter
class BookView(GenericViewSet, ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 配置排序类
filter_backends = [OrderingFilter, ]
# 配置要排序的字段
# ordering_fields = ['price'] # books/?ordering=-price 按price降序排列
ordering_fields = ['price','id'] # books/?ordering=-price,-id 按price降序排列,如果price一样,再按id的降序排列
# 使用:
http://127.0.0.1:8000/books/?ordering=-price
2.3 排序和过滤同时使用
# 既有排序,又有过滤
from rest_framework.filters import SearchFilter, OrderingFilter
class BookView(GenericViewSet, ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 配置排序类
filter_backends = [SearchFilter,OrderingFilter, ] # 先过滤,再排序 (减少排序的数据量)
# 配置要过滤的字段
search_fields=['name',]
# 配置要排序的字段
ordering_fields = ['price'] # books/?search=记&ordering=-price 查询名字中带记的并且按价格降序排列
2.4 第三方过滤类使用
# 实现:http://127.0.0.1:8000/books/?name=红楼梦&price=12 过滤查询
# django-filter--基本使用 (直接配置过滤字段)
# pip install django-filter
# 第一步:安装 django-filter 模块
# 第二步:setting中注册: INSTALLED_APPS=['django_filters']
# 第三步:使用第三方的过滤类
from django_filters.rest_framework import DjangoFilterBackend
class BookView(GenericViewSet, ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 配置第三方过滤类
filter_backends = [DjangoFilterBackend]
# 配置要过滤的字段
filter_fields=['name','price']
filterset_fields=['name','price'] # 同上
# 使用
http://127.0.0.1:8000/books/?name=红楼梦&price=12 名字是红楼梦并且价格为12的
# django-filter--高级使用 (以类的形式 来配置过滤字段等信息)
# 借助django-filter实现区间过滤
###### 1 filters.py 自定义过滤类 (按照第三方过滤类的规则)
from django_filters.filterset import FilterSet
class CourseFilterSet(FilterSet):
# 课程的价格范围要大于min_price,小于max_price
min_price = filters.NumberFilter(field_name='price', lookup_expr='gt')
max_price = filters.NumberFilter(field_name='price', lookup_expr='lt')
class Meta:
model=models.Course
fields=['course_category']
###### 2 视图类中配置
# 配置第三方过滤类
filter_backends = [DjangoFilterBackend]
# 配置自定义过滤类
filter_class = CourseFilterSet
# 使用
http://127.0.0.1:8000/course/?course_category=1&min_price=10&max_price=50 分类为1并且价格为10-50之间的
2.5 自定义过滤类及使用
# 1.写一个类,继承BaseFilterBackend,重写filter_queryset(),返回过滤完的数据 queryset
# 自定义过滤类:
http://127.0.0.1:8000/books/?price_gt=12 (价格大于12)
from rest_framework.filters import BaseFilterBackend
class MyFilterByPrice(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
# queryset就是要过滤的数据
price = request.query_params.get('price_gt') # 获取条件值
if price:
queryset = queryset.filter(price__gt=price)
return queryset # 返回过滤完的数据
# 自定义过滤类的使用
from .auth import MyFilterByPrice
class BookView(GenericViewSet, ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 配置自定义过滤类
filter_backends = [MyFilterByPrice]
# 不用配置要过滤的字段,因为自己写在自定义过滤类中了
3. drf之分页
分页功能
# 内置了三种分页类:不能直接使用,需要自定义继承,再修改一下几个类属性
3.1 自定义分页类
# 自定义分页类
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination
# 基本分页 (页数分页) --常用
class MyPageNumberPagination(PageNumberPagination):
# 重写4个类属性即可
page_size = 2 # 每页默认显示两条
page_query_param = 'page' # 设置查询条件--页数的参数名,其值对应的是第几页
# http://127.0.0.1:8000/books/?page=2
page_size_query_param = 'size' # 设置查询条件--条数的参数名,其值对应的是显示多少条(最大不超过max_page_size)
# http://127.0.0.1:8000/books/?page=2&size=4 获取第二页数据,返回4条数据
max_page_size = 5 # 设置每页最大显示多少条
# http://127.0.0.1:8000/books/?page=2&size=400 获取第二页数据,最多返回5条
# 偏移分页
class MyLimitOffsetPagination(LimitOffsetPagination):
# 4个类属性
default_limit = 2 # 每页默认显示多少条
limit_query_param = 'limit' # 设置偏移条数的参数名,其值对应的是向后偏移多少条 (最大不超过max_limit)
# http://127.0.0.1:8000/books/?limit=3
offset_query_param = 'offset' # 设置偏移开始位置的参数名,其值对应的是从第几条开始偏移
# http://127.0.0.1:8000/books/?limit=3&offset=2 # 从第2条数据位置向后取3条数据
max_limit = 5 # 限制limit最大条数
# 游标分页:针对大数据量,只能上一页或下一页,不能跳到具体某一页
# 优势是速度快,但是不能直接跳到某一页
class MyCursorPagination(CursorPagination):
page_size = 2 # 每页显示多少条
cursor_query_param = 'cursor' # 查询条件,无用 (因为没办法确认其具体的值)
ordering = 'id' # 按谁排序
3.2 分页类使用
from app01.auth import MyPageNumberPagination as PageNumberPagination
class BookView(GenericViewSet, ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 局部配置分页类
pagination_class = PageNumberPagination
3.3 自定义实现:继承APIView的视图,实现分页功能
# 原理:根据rest_framework的分页源码思路+自定义分页类
# page.py
from rest_framework.pagination import PageNumberPagination
class MyPageNumberPagination(PageNumberPagination):
# 重写4个类属性即可
page_size = 2
page_query_param = 'page'
page_size_query_param = 'size'
max_page_size = 5
# view.py:
class BookView(APIView):
def get(self, request):
# 1.先获取所有需要序列化的对象
qs = Book.objects.all()
# 2.分页,通过分页类分页
# 2.1 实例化得到分页类对象,不需要传参数
page = MyPageNumberPagination()
# 2.2 对qs进行分页,分页类对象page的某个方法来实现对qs分页
res = page.paginate_queryset(qs, request, view=self) # 返回的res是列表,是当前页码的所有数据
# 3.对当前页码的数据,进行序列化
ser = BookSerializer(instance=res, many=True)
# 4.返回分页后的数据
# return Response(ser.data)
return page.get_paginated_response(ser.data) # 使用过滤类的返回方法,带有上/下一页、总页的格式
补充
1.BaseThrottle的get_ident()方法中
xff = requeset.META.get('HTTP_X_FORWARDED_FOR')
# 'X_FORWARDED_FOR' 是http的请求头,它的作用是拿出所有ip,包括代理
2. 变量后直接加逗号
a=(3,)
a=3,
print(type(a)) # a是元组
作业
1 写一个频率类,限制books接口一分钟只能访问5次 (带基本分页功能)
# 自定义频率类:一分钟只能访问5次
class AccessThrottle(SimpleRateThrottle):
scope = '5_m'
def get_cache_key(self, request, view):
return self.get_ident(request)
# 自定义分页类:基本分页(页数分页)
class PagePagination(PageNumberPagination):
page_size = 2
page_query_param = 'page'
page_size_query_param = 'size'
max_page_size = 3
2 写Books接口,实现按图书名字查询和按价格排序 (带基本分页功能)
# 重写内置SearchFilter过滤类的 search_param参数,就可以按照自己给的参数名进行查询
class FilterByName(SearchFilter):
search_param = 'name'
# 接口中局部配置:
# 配置过滤和排序类
filter_backends = [FilterByName, OrderingFilter]
# 配置过滤类的字段
search_fields = ['title']
# 配置排序类的字段
ordering_fields = ['price']
3 写一个功能,限制年龄小于18岁的用户,每天只能8:00--9:00登录
"""
思路1: 写在自定义认证类中
这种是都可以登录,但登录之后,校验失败,还是当做没有登录处理
1.根据获取的token,去user_token查找对应的用户
2.再判断用户的年龄字段,和登录时间(在UserToken表中,每次登录会刷新token,也会刷新登录时间)
3.成功就返回用户和token
4.失败,则抛出响应的异常
"""
class LoginAuth(BaseAuthentication):
# 限制年龄小于18岁的用户,每天只能8:00--9:00登录
def authenticate(self, request):
token = request.query_params.get('token')
user_token_obj = UserToken.objects.filter(token=token).first()
if not user_token_obj:
raise AuthenticationFailed('token未传入或不合法')
if user_token_obj.user.age < 18 and user_token_obj.login_time.hour in (8, 9):
raise AuthenticationFailed('小于18岁的用户,只能在每天8-9点登录')
return user_token_obj.user, token
"""
思路2:直接写在登录接口中 (这种是登录接口,再次验证判断)
1.密码验证成功之后
2.再判断用户的年龄字段,和当前登录时间(time.time().year)
3.成功就返回token
4.失败,则返回'小于18岁的用户,只能在每天8-9点登录'
"""
4 三次密码失败之后,账户被禁用 (管理员用户解锁)
# 拓展:若是连续输错三次,那么就加一个登录时间字段
"""
思路:
登录接口中:
1.先验证用户名
2.判断该用户名的 登录失败次数字段是否大于等于三次,是则返回该用户被锁,不是则验证密码
3.验证成功,登录成功 返回token;
4.验证失败,给该用户表 登录失败次数字段加1 login_fail_number += 1
"""
class LoginView(ViewSet):
@action(methods=['POST'], detail=False)
def login(self, request):
name = request.data.get('name')
# 1.先验证用户名 是否存在
name_user = models.LoginUser.objects.filter(name=name).first()
if not name_user:
return Response({'code': 101, 'msg': '用户名不存在'})
# 2.判断该用户名的 登录失败次数字段是否大于等于三次,是则返回该用户被锁,不是则验证密码
if name_user.login_fail_number >= 3:
return Response({'code': 101, 'msg': '该用户密码输错已超过三次,被锁定'})
password = request.data.get('password')
user = models.LoginUser.objects.filter(name=name, password=password).first()
# 3. 验证密码,成功就返回token
if user:
token = str(uuid4())
models.UserToken.objects.update_or_create(defaults={'token': token}, user=user)
return Response({'code': 100, 'msg': '登录成功', 'token': token})
else:
# 4. 验证密码,失败就给该用户名的登录失败次数字段加1
name_user.login_fail_number += 1
name_user.save()
return Response({'code': 101, 'msg': '密码错误'})