zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Python进阶43-drf框架(五)

Python框架 进阶 43 drf
2023-06-13 09:11:06 时间

-曾老湿, 江湖人称曾老大。


-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。


JWT认证


什么是JWT

全称:Json Web Token

为了减轻数据库的压力,从而使用JWT认证

"""
1) jwt = base64(头部).base(载荷).hash256(base64(头部).base(载荷).密钥)
2) base64是可逆的算法、hash256是不可逆的算法
3) 密钥是固定的字符串,保存在服务器
"""

优点

"""
1) 服务器不要存储token,token交给每一个客户端自己存储,服务器压力小
2)服务器存储的是 签发和校验token 两段算法,签发认证的效率高
3)算法完成各集群服务器同步成本低,路由项目完成集群部署(适应高并发)
"""

格式

"""
1) jwt token采用三段式:头部.载荷.签名
2)每一部分都是一个json字典加密形参的字符串
3)头部和载荷采用的是base64可逆加密(前台后台都可以解密)
4)签名采用hash256不可逆加密(后台校验采用碰撞校验)
5)各部分字典的内容:
    头部:基础信息 - 公司信息、项目组信息、可逆加密采用的算法
    载荷:有用但非私密的信息 - 用户可公开信息、过期时间
    签名:头部+载荷+秘钥 不可逆加密后的结果
    注:服务器jwt签名加密秘钥一定不能泄露
    
签发token:固定的头部信息加密.当前的登陆用户与过期时间加密.头部+载荷+秘钥生成不可逆加密
校验token:头部可校验也可以不校验,载荷校验出用户与过期时间,头部+载荷+秘钥完成碰撞检测校验token是否被篡改
"""

JWT认证签发token


安装jwt

官网:TP

MacBook-pro:dg_proj_n driverzeng$ pip3 install djangorestframework-jwt -i https://mirrors.aliyun.com/pypi/simple/

路由

登录接口,就直接使用

使用:user/urls.py

from django.conf.urls import url
from rest_framework_jwt.views import ObtainJSONWebToken, obtain_jwt_token

urlpatterns = [
    ## 签发token方法一 :
    url(r'^jwtlogin1/$', ObtainJSONWebToken.as_view()),
    ## 签发token方法二:
    url(r'^jwtlogin2/$', obtain_jwt_token),
]

JWT认证校验token


视图

from rest_framework_jwt.views import APIView
from utils.response import APIResponse

##必须 登录后 才能访问,通过了认证权限组件
from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.authentication import JSONWebTokenAuthentication


class UserDetail(APIView):
    authentication_classes = [JSONWebTokenAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request, *args, **kwargs):
        return APIResponse(results={'username': request.user.username})

路由

from django.conf.urls import url
from rest_framework_jwt.views import ObtainJSONWebToken, obtain_jwt_token
from . import views
urlpatterns = [
    ## 签发token方法一 :
    url(r'^jwtlogin1/$', ObtainJSONWebToken.as_view()),
    ## 签发token方法二:
    url(r'^jwtlogin2/$', obtain_jwt_token),
    url(r'^user/detail/$', views.UserDetail.as_view()),
]

先登录,拿到token信息

{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTkzMzQxODM2LCJlbWFpbCI6IiJ9.ZkLlvwwGxZNW6yQCB3SLMiv3LNgk4-Jkajp7Nc5PODQ"
}

通过get方法,提供token登录

还是不行,为啥呢?因为jwt做了反爬,需要在token前面加个'jwt ' 注意:有空格

签发token源码分析


入口

# 前提:给一个局部禁用了所有 认证与权限 的视图类发送用户信息得到token,其实就是登录接口

# 1)rest_framework_jwt.views.ObtainJSONWebToken 的 父类 JSONWebTokenAPIView 的 post 方法
#       接受有username、password的post请求
# 2)post方法将请求数据交给 rest_framework_jwt.serializer.JSONWebTokenSerializer 处理
#       完成数据的校验,会走序列化类的 全局钩子校验规则,校验得到登录用户并签发token存储在序列化对象中

核心源码:rest_framework_jwt.serializer.JSONWebTokenSerializer的validate(self, attrs)方法

def validate(self, attrs):
    # 账号密码字典
    credentials = {
        self.username_field: attrs.get(self.username_field),
        'password': attrs.get('password')
    }
    if all(credentials.values()):
        # 签发token第1步:用账号密码得到user对象
        user = authenticate(**credentials)
        if user:
            if not user.is_active:
                msg = _('User account is disabled.')
                raise serializers.ValidationError(msg)
            # 签发token第2步:通过user得到payload,payload包含着用户信息与过期时间
            payload = jwt_payload_handler(user)
            # 在视图类中,可以通过 序列化对象.object.get('user'或者'token') 拿到user和token 
            return {
                # 签发token第3步:通过payload签发出token
                'token': jwt_encode_handler(payload),
                'user': user
            }
        else:
            msg = _('Unable to log in with provided credentials.')
            raise serializers.ValidationError(msg)
    else:
        msg = _('Must include "{username_field}" and "password".')
        msg = msg.format(username_field=self.username_field)
        raise serializers.ValidationError(msg)

手动签发token

# 1)通过username、password得到user对象
# 2)通过user对象生成payload:jwt_payload_handler(user) => payload
#       from rest_framework_jwt.serializers import jwt_payload_handler
# 3)通过payload签发token:jwt_encode_handler(payload) => token
#       from rest_framework_jwt.serializers import jwt_encode_handler

校验token源码分析


源码入口

# 前提:访问一个配置了jwt认证规则的视图类,就需要提交认证字符串token,在认证类中完成token的校验

# 1)rest_framework_jwt.authentication.JSONWebTokenAuthentication 的 父类 BaseJSONWebTokenAuthentication 的 authenticate 方法
#       请求头拿认证信息jwt-token => 通过反爬小规则确定有用的token => payload => user

核心源码:rest_framework_jwt.authentication.BaseJSONWebTokenAuthentication的authenticate(self, request)方法

def authenticate(self, request):
    """
    Returns a two-tuple of `User` and token if a valid signature has been
    supplied using JWT-based authentication.  Otherwise returns `None`.
    """
    # 带有反爬小规则的获取token:前台必须按 "jwt token字符串" 方式提交
    # 校验user第1步:从请求头 HTTP_AUTHORIZATION 中拿token,并提取
    jwt_value = self.get_jwt_value(request)
    # 游客
    if jwt_value is None:
        return None
    # 校验
    try:
        # 校验user第2步:token => payload
        payload = jwt_decode_handler(jwt_value)
    except jwt.ExpiredSignature:
        msg = _('Signature has expired.')
        raise exceptions.AuthenticationFailed(msg)
    except jwt.DecodeError:
        msg = _('Error decoding signature.')
        raise exceptions.AuthenticationFailed(msg)
    except jwt.InvalidTokenError:
        raise exceptions.AuthenticationFailed()
    # 校验user第3步:token => payload
    user = self.authenticate_credentials(payload)

    return (user, jwt_value)

手动校验token

# 1)从请求头中获取token
# 2)根据token解析出payload:jwt_decode_handler(token) => payloay
#       from rest_framework_jwt.authentication import jwt_decode_handler
# 3)根据payload解析出user:self.authenticate_credentials(payload) => user
#       继承drf-jwt的BaseJSONWebTokenAuthentication,拿到父级的authenticate_credentials方法

自定义jwt的配置


settings文件配置

# 自定义 drf-jwt 配置
import datetime

JWT_AUTH = {
    # user => payload
    'JWT_PAYLOAD_HANDLER':
        'rest_framework_jwt.utils.jwt_payload_handler',
    # payload => token
    'JWT_ENCODE_HANDLER':
        'rest_framework_jwt.utils.jwt_encode_handler',
    # token => payload
    'JWT_DECODE_HANDLER':
        'rest_framework_jwt.utils.jwt_decode_handler',
    # token过期时间
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
    # token刷新的过期时间
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
    # 反爬小措施前缀
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
}

多方式登录签发token

手机号登录 邮箱登录 用户密码登录


路由

from django.conf.urls import url
from rest_framework_jwt.views import ObtainJSONWebToken, obtain_jwt_token
from . import views
urlpatterns = [
    ## 签发token方法一 :
    url(r'^jwtlogin1/$', ObtainJSONWebToken.as_view()),
    ## 签发token方法二:
    url(r'^jwtlogin2/$', obtain_jwt_token),
    url(r'^user/detail/$', views.UserDetail.as_view()),
    url(r'^login/$', views.LoginAPIView.as_view()),
]

修改APIResponse

from rest_framework.response import Response

class APIResponse(Response):
    def __init__(self, data_status=0, data_msg='ok', results=None, http_status=None, headers=None, exception=False, **kwargs):
        # data的初始状态:状态码与状态信息
        data = {
            'status': data_status,
            'msg': data_msg,
        }
        # data的响应数据体
        # results可能是False、0等数据,这些数据某些情况下也会作为合法数据返回
        if results is not None:
            data['results'] = results
        # data响应的其他内容
        # if kwargs is not None:
        #     for k, v in kwargs.items():
        #         setattr(data, k, v)
        data.update(kwargs)

        super().__init__(data=data, status=http_status, headers=headers, exception=exception)


"""
Response({
    'status': 0,
    'msg': 'ok',
    'results': [],
    'token': ''  # 有这样的额外的key-value数据结果
},status=http_status,headers=headers,exception=True|False)

APIResponse() => Response({'status': 0,'msg': 'ok'})
"""

封装一个序列化类

from rest_framework import serializers
from . import models
import re
from rest_framework_jwt.settings import api_settings

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER


class UserModelSerializer(serializers.ModelSerializer):
    ## 自定义反序列化字段
    usr = serializers.CharField(write_only=True)
    pwd = serializers.CharField(write_only=True)

    class Meta:
        model = models.User
        fields = ['usr', 'pwd']
        extra_kwargs = {
            # 系统校验规则
        }

    def validate_usr(self, value):
        return value

    def validate_pwd(self, value):
        return value

    def validate(self, attrs):
        usr = attrs.get('usr')
        pwd = attrs.get('pwd')
        if re.match(r'.+@.+', usr, re.I):
            user_query = models.User.objects.filter(email=usr)
        elif re.match(r'1[3-9][0-9]{9}', usr):
            user_query = models.User.objects.filter(mobile=usr)
        else:
            user_query = models.User.objects.filter(username=usr)

        user_obj = user_query.first()

        if user_obj and user_obj.check_password(pwd):
            ## 签发token,将token存放到序列化对象的token名字中

            payload = jwt_payload_handler(user_obj)
            self.token = jwt_encode_handler(payload)

            return attrs

        raise serializers.ValidationError({'data': '数据有误'})

视图

from rest_framework_jwt.views import APIView
from utils.response import APIResponse
from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.authentication import JSONWebTokenAuthentication

"""
#多方式登录签发token

1.手机号登录
2.邮箱登录
3.用户密码登录
"""
from . import serializers
from utils.response import APIResponse


class LoginAPIView(APIView):
    ## 1.禁用认证与权限组件
    authentication_classes = []
    permission_classes = []

    ## 2.拿到前台登录信息
    def post(self, request, *args, **kwargs):
        ## 3.校验得到登录用户
        # 前台账号用usr传,密码用pwd传
        user_ser = serializers.UserModelSerializer(data=request.data)
        user_ser.is_valid(raise_exception=True)
        ## 4.签发token并返回
        return APIResponse(token=user_ser.token)


返回登录用户信息

serializers.py

from rest_framework import serializers
from . import models
import re
from rest_framework_jwt.settings import api_settings

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER


class UserModelSerializer(serializers.ModelSerializer):
    ## 自定义反序列化字段
    usr = serializers.CharField(write_only=True)
    pwd = serializers.CharField(write_only=True)

    class Meta:
        model = models.User
        fields = ['usr', 'pwd', 'username', 'mobile', 'email']
        extra_kwargs = {
            # 系统校验规则
            'username': {
                'read_only': True
            },
            'mobile': {
                'read_only': True
            },
            'email': {
                'read_only': True
            },
        }

    def validate_usr(self, value):
        return value

    def validate_pwd(self, value):
        return value

    def validate(self, attrs):
        usr = attrs.get('usr')
        pwd = attrs.get('pwd')
        if re.match(r'.+@.+', usr, re.I):
            user_query = models.User.objects.filter(email=usr)
        elif re.match(r'1[3-9][0-9]{9}', usr):
            user_query = models.User.objects.filter(mobile=usr)
        else:
            user_query = models.User.objects.filter(username=usr)

        user_obj = user_query.first()

        if user_obj and user_obj.check_password(pwd):
            ## 签发token,将token存放到序列化对象的token名字中

            payload = jwt_payload_handler(user_obj)
            token = jwt_encode_handler(payload)

            ## 将当前用户和签发的token都保存在序列化对象中
            self.user = user_obj
            self.token = token

            return attrs

        raise serializers.ValidationError({'data': '数据有误'})

views.py

from rest_framework_jwt.views import APIView
from utils.response import APIResponse
from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.authentication import JSONWebTokenAuthentication

"""
#多方式登录签发token

1.手机号登录
2.邮箱登录
3.用户密码登录
"""
from . import serializers
from utils.response import APIResponse


class LoginAPIView(APIView):
    ## 1.禁用认证与权限组件
    authentication_classes = []
    permission_classes = []

    ## 2.拿到前台登录信息
    def post(self, request, *args, **kwargs):
        ## 3.校验得到登录用户
        # 前台账号用usr传,密码用pwd传
        user_ser = serializers.UserModelSerializer(data=request.data)
        user_ser.is_valid(raise_exception=True)
        ## 4.签发token并返回
        return APIResponse(token=user_ser.token, results=serializers.UserModelSerializer(user_ser.user).data)

用户名密码登录

邮箱登录

因为没有手机号,所以无法使用手机登录

群查数据准备


模型层

from django.db import models

## 用户表:角色groups     权限user_permissions
## 角色表:用户user_set   权限permissions
## 权限表:用户user_set   角色group_set

from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    mobile = models.CharField(max_length=11, unique=True)

    class Meta:
        db_table = 'api_user'
        verbose_name = '用户表'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.username


class Car(models.Model):
    name = models.CharField(max_length=16, unique=True, verbose_name='车名')
    price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='价格')
    brand = models.CharField(max_length=16, verbose_name='品牌')

    class Meta:
        db_table = 'api_car'
        verbose_name = '汽车表'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

注册admin

from django.contrib import admin
from . import models
# Register your models here.
from django.contrib.auth.admin import UserAdmin

admin.site.register(models.User,UserAdmin)
admin.site.register(models.Car)

数据库迁移

MacBook-pro:dg_proj_n driverzeng$ python3 manage.py makemigrations
MacBook-pro:dg_proj_n driverzeng$ python3 manage.py migrate

 ... 加了一万条,这里就不一一截图了。

群查过滤组件前戏


路由层

from django.conf.urls import url
from rest_framework_jwt.views import ObtainJSONWebToken, obtain_jwt_token
from . import views
urlpatterns = [
    ## 签发token方法一 :
    url(r'^jwtlogin1/$', ObtainJSONWebToken.as_view()),
    ## 签发token方法二:
    url(r'^jwtlogin2/$', obtain_jwt_token),
    url(r'^user/detail/$', views.UserDetail.as_view()),
    url(r'^login/$', views.LoginAPIView.as_view()),
    url(r'^cars/$', views.CarListAPIView.as_view()),
]

视图层

## Car群查接口
from rest_framework.generics import ListAPIView
from . import models


class CarListAPIView(ListAPIView):
    queryset = models.Car.objects
    serializer_class = serializers.CarModelSerializer

序列化

class CarModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Car
        fields = ['name', 'price', 'brand']

群查之搜索过滤组件


视图层

## Car群查接口
from rest_framework.generics import ListAPIView
from . import models
# 1)drf的SearchFilter
from rest_framework.filters import SearchFilter
class CarListAPIView(ListAPIView):
    queryset = models.Car.objects
    serializer_class = serializers.CarModelSerializer

    ## 局部配置过滤类们(全局配置DEFAULT_FILTER_BACKENDS)
    filter_backends = [SearchFilter]
    ## SearchFilter过滤类,依赖的过滤条件 => 接口:/cars/?search=...
    search_fields = ['name']

/cars/?search=1 ,指定的字段中,如果都包含1,都会被查出来,如果search_fields = ['name','price']有两个字段的时候,那么价格里带1的都会出来。

## Car群查接口
from rest_framework.generics import ListAPIView
from . import models
# 1)drf的SearchFilter
from rest_framework.filters import SearchFilter


class CarListAPIView(ListAPIView):
    queryset = models.Car.objects
    serializer_class = serializers.CarModelSerializer

    ## 局部配置过滤类们(全局配置DEFAULT_FILTER_BACKENDS)
    filter_backends = [SearchFilter]
    ## SearchFilter过滤类,依赖的过滤条件 => 接口:/cars/?search=...
    search_fields = ['name', 'price']

群查之排序过滤组件


视图层

## Car群查接口
from rest_framework.generics import ListAPIView
from . import models
# 1)drf的SearchFilter
from rest_framework.filters import SearchFilter
# 2)drf的OrderingFilter
from rest_framework.filters import OrderingFilter

class CarListAPIView(ListAPIView):
    queryset = models.Car.objects
    serializer_class = serializers.CarModelSerializer

    ## 局部配置过滤类们(全局配置DEFAULT_FILTER_BACKENDS)
    filter_backends = [SearchFilter,OrderingFilter]
    ## SearchFilter过滤类,依赖的过滤条件 => 接口:/cars/?search=...
    search_fields = ['name', 'price']

    ## ordering 排序类,依赖排序条件 => 接口:/cars/?ordering=
    ordering_fields = ['price']

多个条件,就在ordering_fields = ['price','pk']中添加,url:http://127.0.0.1:8000/api/cars/?search=1&ordering=-price,pk

基础分页组件


分页组件

在api目录下创建一个pagenations.py文件

from rest_framework.pagination import PageNumberPagination

class MyPageNumberPagination(PageNumberPagination):
    ## 默认一页显示3条
    page_size = 3
    ## url中传递参数的名字,默认是配置,自己可以修改
    page_query_param = 'page'
    ## 用户可以自定义显示多少条
    page_size_query_param = 'page_size'
    ## 用户如果自定义,但是最大只能显示5条
    max_page_size = 5

视图层

一个视图类只能配一个分页器。

## Car群查接口
from rest_framework.generics import ListAPIView
from . import models
# 1)drf的SearchFilter
from rest_framework.filters import SearchFilter
# 2)drf的OrderingFilter
from rest_framework.filters import OrderingFilter
# 3)drf的分页类 - 自定义
from . import pagenations

class CarListAPIView(ListAPIView):
    queryset = models.Car.objects.all()
    serializer_class = serializers.CarModelSerializer

    ## 局部配置过滤类们(全局配置DEFAULT_FILTER_BACKENDS)
    filter_backends = [SearchFilter, OrderingFilter]
    ## SearchFilter过滤类,依赖的过滤条件 => 接口:/cars/?search=...
    search_fields = ['name', 'price']

    ## ordering 排序类,依赖排序条件 => 接口:/cars/?ordering=
    ordering_fields = ['price']

    ## 分页组件 - 给视图类配置分页类即可
    pagination_class = pagenations.MyPageNumberPagination

偏移分页器


分页组件

pagenations.py

from rest_framework.pagination import LimitOffsetPagination


class MyLimitOffsetPagination(LimitOffsetPagination):
    ## 默认显示3条数据
    default_limit = 3
    ## 查询关键字
    limit_query_param = 'limit'
    ## 偏移关键字
    offset_query_param = 'offset'
    ## 最大显示5条
    max_limit = 5

视图层

## Car群查接口
from rest_framework.generics import ListAPIView
from . import models
# 1)drf的SearchFilter
from rest_framework.filters import SearchFilter
# 2)drf的OrderingFilter
from rest_framework.filters import OrderingFilter
# 3)drf的分页类 - 自定义
from . import pagenations

class CarListAPIView(ListAPIView):
    queryset = models.Car.objects.all()
    serializer_class = serializers.CarModelSerializer

    ## 局部配置过滤类们(全局配置DEFAULT_FILTER_BACKENDS)
    filter_backends = [SearchFilter, OrderingFilter]
    ## SearchFilter过滤类,依赖的过滤条件 => 接口:/cars/?search=...
    search_fields = ['name', 'price']

    ## ordering 排序类,依赖排序条件 => 接口:/cars/?ordering=
    ordering_fields = ['price']

    ## 分页组件 - 给视图类配置分页类即可
    # pagination_class = pagenations.MyPageNumberPagination

    pagination_class = pagenations.MyLimitOffsetPagination

取出价格最高的前4辆车

自定义过滤器


过滤组件

在api目录下创建filters.py

class LimitFilter:
    def filter_queryset(self, request, queryset, view):
       limit = request.query_params.get('limit')
       print(limit)
       return queryset

视图层

## Car群查接口
from rest_framework.generics import ListAPIView
from . import models
# 1)drf的SearchFilter
from rest_framework.filters import SearchFilter
# 2)drf的OrderingFilter
from rest_framework.filters import OrderingFilter
# 3)drf的分页类 - 自定义
from . import pagenations

## 自定义过滤器
from api.filters import LimitFilter


class CarListAPIView(ListAPIView):
    queryset = models.Car.objects.all()
    serializer_class = serializers.CarModelSerializer

    ## 局部配置过滤类们(全局配置DEFAULT_FILTER_BACKENDS)
    filter_backends = [SearchFilter, OrderingFilter, LimitFilter]
    ## SearchFilter过滤类,依赖的过滤条件 => 接口:/cars/?search=...
    search_fields = ['name', 'price']

    ## ordering 排序类,依赖排序条件 => 接口:/cars/?ordering=
    ordering_fields = ['price']

    ## 分页组件 - 给视图类配置分页类即可
    # pagination_class = pagenations.MyPageNumberPagination

    pagination_class = pagenations.MyLimitOffsetPagination

修改自定义过滤器

class LimitFilter:
    def filter_queryset(self, request, queryset, view):
        limit = request.query_params.get('limit')
        if limit:
            limit = int(limit)
            return queryset[:limit]
        return queryset

游标分页器(了解)


分页组件

from rest_framework.pagination import CursorPagination


class MyCursorPagination(CursorPagination):
    ## 关键字
    cursor_query_param = 'cursor'
    ## 默认页码数
    page_size = 3
    ## 用户可以自定义的页数的关键字
    page_size_query_param = 'page_size'
    ## 最大页码数
    max_page_size = 10
    ## 排序规则,默认按照主键,前提条件,视图层得把OrderingFilter注释掉,否则会报错
    ordering = '-pk'

视图层

## Car群查接口
from rest_framework.generics import ListAPIView
from . import models
# 1)drf的SearchFilter
from rest_framework.filters import SearchFilter
# 2)drf的OrderingFilter
from rest_framework.filters import OrderingFilter
# 3)drf的分页类 - 自定义
from . import pagenations

## 自定义过滤器
from api.filters import LimitFilter


class CarListAPIView(ListAPIView):
    queryset = models.Car.objects.all()
    serializer_class = serializers.CarModelSerializer

    ## 局部配置过滤类们(全局配置DEFAULT_FILTER_BACKENDS)
    filter_backends = [SearchFilter, OrderingFilter, LimitFilter]
    ## SearchFilter过滤类,依赖的过滤条件 => 接口:/cars/?search=...
    search_fields = ['name', 'price']

    ## ordering 排序类,依赖排序条件 => 接口:/cars/?ordering=
    ordering_fields = ['price']

    ## 分页组件 - 给视图类配置分页类即可
    # pagination_class = pagenations.MyPageNumberPagination
    ## 分页组件 - 偏移分页器
    # pagination_class = pagenations.MyLimitOffsetPagination
    ## 分页组件 - 游标分页器
    pagination_class = pagenations.MyCursorPagination

Django过滤器插件

主要来做分类查询,上面所写的都是模糊查询,没有精确到分类。


django-filter安装

MacBook-pro:~ driverzeng$ pip3 install django-filter -i https://mirrors.aliyun.com/pypi/simple/

视图层

## Car群查接口
from rest_framework.generics import ListAPIView
from . import models
# 1)drf的SearchFilter
from rest_framework.filters import SearchFilter
# 2)drf的OrderingFilter
from rest_framework.filters import OrderingFilter
# 3)drf的分页类 - 自定义
from . import pagenations

## 自定义过滤器
from api.filters import LimitFilter

## django-filter过滤器插件
from django_filters.rest_framework import DjangoFilterBackend


class CarListAPIView(ListAPIView):
    queryset = models.Car.objects.all()
    serializer_class = serializers.CarModelSerializer

    ## 局部配置过滤类们(全局配置DEFAULT_FILTER_BACKENDS)
    filter_backends = [SearchFilter, OrderingFilter, LimitFilter, DjangoFilterBackend]
    ## SearchFilter过滤类,依赖的过滤条件 => 接口:/cars/?search=...
    search_fields = ['name', 'price']

    ## ordering 排序类,依赖排序条件 => 接口:/cars/?ordering=
    ordering_fields = ['price']

如何使用django-filter?

先在api目录下的filter.py文件中中自定义一个类

## django-filter插件过滤器类
from django_filters.rest_framework import FilterSet
from . import models


class CarFilterSet(FilterSet):
    class Meta:
        model = models.Car
        fields = ['brand']

然后在视图中导入类,并且在filter_class中配置

## Car群查接口
from rest_framework.generics import ListAPIView
from . import models
# 1)drf的SearchFilter
from rest_framework.filters import SearchFilter
# 2)drf的OrderingFilter
from rest_framework.filters import OrderingFilter
# 3)drf的分页类 - 自定义
from . import pagenations

## 自定义过滤器
from api.filters import LimitFilter

## django-filter过滤器插件
from django_filters.rest_framework import DjangoFilterBackend
## 导入自定义的类
from api.filters import CarFilterSet

class CarListAPIView(ListAPIView):
    queryset = models.Car.objects.all()
    serializer_class = serializers.CarModelSerializer

    ## 局部配置过滤类们(全局配置DEFAULT_FILTER_BACKENDS)
    filter_backends = [SearchFilter, OrderingFilter, LimitFilter, DjangoFilterBackend]
    ## SearchFilter过滤类,依赖的过滤条件 => 接口:/cars/?search=...
    search_fields = ['name', 'price']

    ## ordering 排序类,依赖排序条件 => 接口:/cars/?ordering=
    ordering_fields = ['price']

    ## 分页组件 - 给视图类配置分页类即可
    # pagination_class = pagenations.MyPageNumberPagination
    ## 分页组件 - 偏移分页器
    # pagination_class = pagenations.MyLimitOffsetPagination
    ## 分页组件 - 游标分页器
    # pagination_class = pagenations.MyCursorPagination

    ## django-filter过滤器插件使用,一下两个配置都可以,一般使用filter_class
    filter_class = CarFilterSet
    # filter_fields = []

django-filter重点使用方法


自定义过滤字段

api目录下的filter.py文件中

## django-filter插件过滤器类
from django_filters.rest_framework import FilterSet
from . import models

from django_filters import filters


class CarFilterSet(FilterSet):
    min_price = filters.NumberFilter(field_name='price', lookup_expr='gte')

    class Meta:
        model = models.Car
        fields = ['brand', 'min_price']

再来个最高价格

## django-filter插件过滤器类
from django_filters.rest_framework import FilterSet
from . import models

from django_filters import filters


class CarFilterSet(FilterSet):
    min_price = filters.NumberFilter(field_name='price', lookup_expr='gte')
    max_price = filters.NumberFilter(field_name='price', lookup_expr='lte')

    class Meta:
        model = models.Car
        fields = ['brand', 'min_price', 'max_price']

drf大总结

接口

"""
1、什么是接口:url+请求参数+响应数据 | 接口文档

2、接口规范:
    url:https,api,资源(名词复数),v1,get|post表示操作资源的方式,?过滤条件
    响应数据:status,msg,results|data,url资源数据
"""

DRF入门

"""
1、封装风格

2、请求生命周期:走APIView的as_view => View的as_view调用APIView的dispatch => 分发给视图类的请求函数 => 响应

3、请求模块:二次封装request

4、解析模块:局部全局配置,form-data、urlencoding、json

5、渲染模块:局部全局配置,html、json

6、响应模块:Response(data, http_status, header)
data={'detail': serializer_obj.errors} => http_status=4..|5..
# 4,5开头字段状态码,前台axios请求响应会解析到catch分支
axios({}).then().catch()

7、二次封装Response:
    i) 继承Response
    ii) 自定义参数格式化成data,传给 super().__init__(data=data)
    iii) 网络状态码、响应头等信息带给super().__init__()
    
8、异常模块:在请求生命周期中,分发请求任务执行时,被一个超大的try except包裹,一旦出现任何异常,都会交给exception_handler函数处理
    自定义原因:系统只处理了部分,所以手动还需要处理;后期异常信息需要记录到日志文件
    工作流程:先交给drf处理异常,如果有响应结果,代表drf已处理,如果是None,自己处理
"""

序列化组件

# 1)可以将model对象序列化成可以返回给前台的json数据
# 2)可以校验前台传来的数据,完成model的增与改

模型层

class User(models.Model):
    name = models.CharField(max_length=16)
    
    # 自定义插拔序列化字段 - 完成连表深度查询
    @property
    def my_name(self):
        return self.name

序列化层

class UserListSerializer(serializers.ListSerializer):
    def update(self, instance, validated_data):
        for index, obj in enumerate(instance):
            self.child.update(obj, validated_data[index])
        return instance

class UserModelSerializer(serializers.ModelSerializer):
    # 自定义反序列化不入库字段 - 要在钩子函数校验后移除
    check_name = serializers.CharFiled(write_only=True)
    class Meta:
        model = User
        fields = ['name', 'my_name', 'check_name']
        extra_kwargs = {
            'my_name': {
                'read_only': True
            }
        }
        # 群改
        list_serializer_class = UserListSerializer
    # 局部、全局钩子
    # create、update方法不需要重写,可以完成单增、群增、单改

视图层

class UserAPIView(APIView):
    def get(self, request, *args, **kwargs):
        # 先orm得到 user_query | user_obj
        
        # 在得到序列化对象
        user_ser = UserModelSerializer(user_query, many=True)
        user_ser = UserModelSerializer(user_query, many=False)
        
        # 返回序列化结果 user_ser.data
    def post(self, request, *args, **kwargs):
        # 数据给序列化类
        user_ser = UserModelSerializer(data=user_list, many=True)
        user_ser = UserModelSerializer(data=user_dic, many=False)
        # 校验数据
        user_ser.is_valid(raise_exception=True)
        # 数据库操作
        user_query|user_obj = user_ser.save()
        
    def patch(self, request, *args, **kwargs):
        # 根据主键或主键们确定要修改的 对象 或 对象们
        
        # 数据给序列化类
        user_ser = UserModelSerializer(data=user_list, many=True, instance=对象们, partial=True|False)
        user_ser = UserModelSerializer(data=user_dic, many=False, instance=对象, partial=True|False)
        # 校验数据
        user_ser.is_valid(raise_exception=True)
        # 数据库操作
        user_query|user_obj = user_ser.save()

三大认证

"""
1、认证组件:
    i)自定义认证类继承BaseAuthentication,重写authenticate方法
    ii)规则:游客返回None,非法用户抛异常,合法用户返回(user, auth)
    iii)全局局部配置
    
2、权限组件:
    i)自定义权限类继承BasePermission,重写has_permission方法
    ii)规则:有权限返回True,无权限返回False - 校验对象:request.user或请求方式
    iii)全局局部配置
    
3、评论组件:
    i)自定义频率类继承SimpleRateThrottle,重写get_cache_key方法,设置scope类属性
    ii)全局配置scope对应的访问频率:次数/时间
    iii)get_cache_key返回的是与限制条件有关的动态字符串 - 限制条件来源于请求参数或请求头
    iv)全局局部配置
    
4、jwt认证:
    i)客户端存token,服务器存签发算法、校验算法、秘钥
    ii)基本信息(头base64).用户信息过期时间(载荷base64).全信息+秘钥(签名HS256)
    iii)drf-jwt组件使用
    iv)登录接口签发token
    v)认证组件校验token
"""

群查过滤组件

"""
1、搜索组件:search_fields = ['name', 'price']

2、排序组件:ordering_fields = ['pk', 'price']

3、分页组件:pagination_class = pagenations.MyPageNumberPagination

4、django-filter过滤插件:filter_class = CarFilterSet

5、自定义过滤器:自定义过滤类实现filter_queryset即可,根据筛选条件筛选后再返回queryset即可

使用:
i)视图类添加过滤类:filter_backends = [SearchFilter, OrderingFilter, LimitFilter, DjangoFilterBackend]
ii)为不同的过滤类配置过滤条件
"""