drf之三大认证、过滤、排序、分页组件

drf之三大认证、过滤、排序、分页组件

本文所介绍的组件,都有着很相似的配置方式,继承组件类,类体中配置参数,视图类中配置参数添加对应的组件类或者全局配置,我们就可以方便的使用drf提供的组件了。这些组件也足够常用。

认证组件

对于接口而言,有些接口应该是需要登录认证后才能访问,我们需要先写一个登录的功能:

登录接口

登录功能我们首先要简单的准备用户模型表:

  • 用户表存储用户信息,这里就只用用户和密码两个字段了
  • token表相当于我们自己建立的session表,在服务端存一个随机字符串,用于两侧比对验证登录。

登录的路由和视图类一并配好,因为login功能不是五个常用接口之一,所以需要加action装饰器,书写路由时则按/api/v1/user/login的 post 请求。

login接口的逻辑也是需要自己重写的:

  1. 验证用户名和密码,查看是否有这个用户
  2. 有相应用户的话拿到token表中比对,如果有的话更新token值即可,如果没有则新建一条
#### 表模型

class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)

class UserToken(models.Model):  # 跟User是一对一
    token = models.CharField(max_length=32)
    user = models.OneToOneField(to='User', on_delete=models.CASCADE, null=True)
    # user :反向,表名小写,所有有user字段

### 路由
router.register('user', views.UserView, 'user')  # /api/v1/user/login     post 请求

# 视图类
####  登录接口  自动生成路由+由于登录功能,不用序列化,继承ViewSet
from .models import User, UserToken
import uuid


class UserView(ViewSet):
    @action(methods=['POST'], detail=False)
    def login(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user = User.objects.filter(username=username, password=password).first()
        if user:
            # 用户存在,登录成功
            # 生成一个随机字符串--uuid
            token = str(uuid.uuid4())  # 生成一个永不重复的随机字符串
            # 在userToken表中存储一下:1 从来没有登录过,插入一条,     2 登录过,修改记录
            UserToken.objects.update_or_create(user=user, defaults={'token': token})
            return Response({'code': '100', 'msg': '登录成功', 'token': token})
        else:
            return Response({'code': '101', 'msg': '用户名或密码错误'})

update_or_create的用法,按照defaults外的字段判断是更新还是新增,defaults的内容一定会更新到表中。

认证功能实现

需求:查询所有登不登录都可以,查询单个需要登录

认证功能流程:

  1. 继承BaseAuthentication类,产生认证子类

  2. 在子类中必须覆写authenticate(self, request)方法

    • 方法中必须固定的返回(用户数据对象,token值)

      这里的用户数据对象会保存在request.user中

    • 如果认证不成功则需要抛出指定的异常rest_framework.exceptions.AuthenticationFailed

# 认证类
from rest_framework import authentication
from .models import UserToken
from rest_framework.exceptions import AuthenticationFailed

class CommonAuthentication(authentication.BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get('token')
        if not token:
            raise AuthenticationFailed('没有传token')
        user_token = UserToken.objects.filter(token=token).first()
        if not user_token:
            raise AuthenticationFailed('你还未登录,无法使用接口权限')
        return user_token.user, token
  1. 将认证子类注册到局部或全局

    • 局部注册--在继承自GenericAPIView的视图类内部配置参数

      from .authentication import CommonAuthentication
      
      class BookDetailView(GenericViewSet, mixins.RetrieveModelMixin):
          queryset = Book.objects.all()
          serializer_class = BookSerializer
          # 还可以配置多个认证类,从左到右执行
          authentication_classes = [CommonAuthentication]
      
    • 全局注册

      # 项目的settings.py中
      REST_FRAMEWORK = {
          'DEFAULT_AUTHENTICATION_CLASSES': [
              'app01.authentication.CommonAuthentication'
          ],
      }
      
    • 局部禁用--在继承自GenericAPIView的视图类内部的认证类配置为空列表

      class UserView(GenericViewSet):
          authentication_classes = []
      

权限组件

需求:用户为超级管理员则可以新增,修改等,普通用户只能查看单个,没有登录同上

这就涉及了权限问题,认证后也不一定拥有相应的权限,drf也提供了权限组件。

数据准备

在用户模型表增加一个权限字段,这里就设置为用户类型好了:

user_type = models.IntegerField(choices=((1, '超级管理员'), (2, '普通用户')), default=2)

迁移好后,那么在后续判断权限时就可以通过这个字段来判断。

这里用到了choices列举此字段的所有可能,存储和正常取出都是1,2数字,但是通过get_字段_display可以拿到超级管理员、普通用户数据。

权限功能实现

权限功能流程:

  1. 继承BasePermission类,产生子类

  2. 在类中改写has_permission方法

    在得到有权限的结论时返回True,没有则返回False,可以使用self.message更改无权限的提示信息

    class CommonPermission(BasePermission):
        def has_permission(self, request, view):
            if request.user.user_type == 1:
                return True
            self.message = f'你是{request.user.get_user_type_display()},你的权限不够'
            return False
    
  3. 将权限类校验注册到局部或全局生效。

    # 在视图类中局部配置
    class BookDetailView(GenericViewSet, mixins.RetrieveModelMixin):
        queryset = Book.objects.all()
        serializer_class = BookSerializer
        permission_classes = [CommonPermission]  # 需要登录认证后再进行权限认证
        
    # 全局配置
    REST_FRAMEWORK = {
        'DEFAULT_PERMISSION_CLASSES': [
            'app01.permission.CommonPermission',
        ],
    }
    

频率组件

用于控制某接口的访问频率,如查询所有对于同一个ip,限制它只能一分钟访问5次。

限制频率功能的简单实现

  1. 继承SimpleRateThrottle类写一个频率类

  2. 重写get_cache_key方法,返回什么,就以什么做限制(可以是ip或者用户id)

  3. 配置一个类属性:scope='book_5_m'

    from rest_framework.throttling import SimpleRateThrottle
    
    class CommonThrottle(SimpleRateThrottle):
        scope = 'book_5_m'
    
        def get_cache_key(self, request, view):
            return request.META.get('REMOTE_ADDR')
    
  4. 在配置文件中配置 'DEFAULT_THROTTLE_RATES': {'book_5_m': '5/m',},

    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_RATES': {
            'book_5_m': '5/m',
        },
    }
    
  5. 局部使用和全局使用

    • 局部使用--直接在视图类中添加throttle_classes列表参数,将频率类放入。

      class BookView(GenericViewSet, mixins.ListModelMixin):
          queryset = Book.objects.all()
          serializer_class = BookSerializer
          authentication_classes = []
          permission_classes = [CommonPermission]
          throttle_classes = [CommonThrottle]
      
    • 全局使用--配置settings

      REST_FRAMEWORK = {
          'DEFAULT_THROTTLE_CLASSES': [
              'app01.throttle.CommonThrottle',
          ],
      }
      

    三大认证执行顺序:

    每一种认证都按局部先生效,再是项目的settings.REST_FRAMEWORK,最后是drf的settings.DEFAULTS

    三大认证按authentication--》permission--》throttle的顺序执行。

过滤排序组件

在restful规范中,要求请求地址携带过滤条件

而5大接口中,只有查询所有需要携带过滤条件。

内置过滤类SearchFilter的使用

SearchFilter是固定用法,会模糊匹配所有字段。

from rest_framework.filters import SearchFilter

class BookView(GenericViewSet, mixins.ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    
    filter_backends = SearchFilter

http://127.0.0.1:8001/api/v1/book/?search=红会模糊搜索所有序列化字段,包括name、price、publisher。

使用第三方djagno-filter实现过滤

首先需要安装djagno-filter,它的规则是完整的匹配字段的内容,而且搜索携带的数据必须按照:?字段=内容&字段=内容

class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['name','price']  # 定制了可以通过name和price字段进行完整匹配查询

自定制过滤类实现过滤

这种能够自定义通过什么样的字段进行匹配查询

定义过滤类,继承BaseFilterBackend,重新filter_queryset方法

然后将过滤类,配置到视图函数中,还是通过filter_backends = []的方式

# 过滤类
from rest_framework.filters import BaseFilterBackend
from django.db.models import Q


class CommonFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        q_obj = Q()
        # 遍历查询参数,将参数加到q条件中,进行filter查询
        for field, condition in request.query_params.items():
            if field in view.filter_fields:  # 支持视图类中对查询字段进行编辑
                q_obj.children.append((field,condition))
        return queryset.filter(q_obj)
    
# 视图类
class BookView(GenericViewSet, mixins.ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    authentication_classes = []
    throttle_classes = [CommonThrottle]
    filter_backends = [CommonFilter]
    filter_fields = ['price__contains', 'name__contains']

排序类

排序使用内置的即可,它也属于过滤类的一种,配置在filter_backends列表中即可。

class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [OrderingFilter]  
    ordering_fields = ['price']   # 支持排序的字段

配置好后前端可以通过以下方式进行查询:

http://127.0.0.1:8001/api/v1/book/?ordering=price   # 正常按价格排序
http://127.0.0.1:8001/api/v1/book/?ordering=-price   # 倒序排序
http://127.0.0.1:8001/api/v1/book/?ordering=-id,price   # 先按一个字段排序,如果有相同的再按另外一个字段排序

分页类

分页只有查询所有接口需要。drf内置了三个分页器,对应三种分页方式。

继承PageNumberPagination

这种最贴合人对分页的习惯,但是效率并不算高,它只需要设置以下参数:

from rest_framework.pagination import PageNumberPagination

class CommonPageNumberPagination(PageNumberPagination):
    page_size = 2  # 每页显示2条
    page_query_param = 'page'  # 前端分页键的名字,?page=10 查询第十页
    page_size_query_param = 'size'  # ?page=10&size=5    查询第10页,每页显示5条
    max_page_size = 5  # 每页最大显示5条,size的值大于这个值无效,只取5

而在前端拿到了下面的数据:

{
    "count": 3,
    "next": "http://127.0.0.1:8001/api/v1/book/?page=2",
    "previous": null,
    "results": [...]
}

分别对应总数据条数,下页的数据网址,上页的数据网址,数据结果。

前端的网址可以通过携带?page=页数来拿到指定页的数据。

继承LimitOffsetPagination

这种则是让前端通过limit每页条数和offset偏移条数两个参数进行分页,即开始的条数和此页有几条数据的方式取。

网址方式:http://127.0.0.1:8001/api/v1/book/?offset=2&limit3

class CommonLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 3  # 每页显示2条
    limit_query_param = 'limit'  # limit=3   取3条
    offset_query_param = 'offset'  # offset=1  从第一个位置开始,取limit条
    max_limit = 5

继承CursorPagination

这种分页方式只支持上下翻页,但是面对大量数据,其效率会有很大的提升。

class CommonCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'  # 查询参数
    page_size = 2  # 每页多少条
    ordering = 'id'  # 排序字段
posted @ 2023-02-07 21:28  leethon  阅读(21)  评论(0编辑  收藏  举报