DRF之实战总结


前言

DRF概念:Django REST framework框架是一个用于构建Web API的强大而又灵活的工具. 通常简称为DRF框架 或 REST framework框架

特点:

  • 提供了定义序列化器serializer的方法,可以快速根据Django ORM或者其他库自动序列化/反序列化;
  • 提供了丰富的类视图、Mixin扩展类、简化视图的编写;
  • 丰富的定制层级: 函数视图、类视图、视图集合到自动生成API,满足各种需求;
  • 多种身份认证和权限认证方式的支持;
  • 内置了限流系统;
  • 直观的API web界面;
  • 可扩展性, 插件丰富

一、序列化

1、序列化定义

        1)序列化:把对象转化为可传输的字节序列过程称为序列化。

        2)反序列化:把字节序列还原为对象的过程称为反序列化。

2、序列化的 目的 及 作用

        1、跨平台存:序列化最终的目的是为了对象可以跨平台存储,和进行网络传输与数据返回。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组。因为我们单方面的只把对象转成字节数组还不行,因为没有规则的字节数组我们是没办法把对象的本来面目还原回来的,所以我们必须在把对象转成字节数组的时候就制定一种规则(序列化),那么我们从IO流里面读出数据的时候再以这种规则把对象还原回来(反序列化)。

        2、序列化验证:前后端分离的时候,我们都会校验前端的参数时候合法,是否满足需要。如果我们ModelSerializer话,因为它本身已经帮我们写好create方法,所以我们基本不需要再写验证。但是一些特殊的我们就需要重写validate(self,attrs)或者自己写

        3、重写创建、更新方法:数据操作可用于数据更新、数据创建、数据保存等

3、序列化需求场景

        凡是需要进行“跨平台存储”和”网络传输”的数据,都需要进行序列化。

        本质上存储和网络传输 都需要经过 把一个对象状态保存成一种跨平台识别的字节格式,然后其他的平台才可以通过字节信息解析还原对象信息。

二、认证与权限

from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        return obj.user == request.user

三、JWT

1、优点

        1、无状态

        2、避免csrf

        3、适合移动端

2、缺点

        1、注销登录后Token时效问题

四、限制频率问题

        1、对于未登录的用户根据IP来限制,对于已经登录的用户可以根据用户的唯一标识。

from rest_framework.throttling import SimpleRateThrottle

class VisitThrottle(SimpleRateThrottle):
    scope = "未认证用户"
    
    def get_cache_key(self, request, view):
        return  self.get_ident(request)
        
 
class UserThrottle(SimpleRateThrottle):
    scope = "已认证用户"
  
    def get_cache_key(self, request, view):
        return  request.user 

        2、对于未登录的用户根据IP来限制,对于已经登录的用户可以根据用户的唯一标识。

  'DEFAULT_THROTTLE_CLASSES': ['app06.mythrottle.UserThrottle', ],
    'DEFAULT_THROTTLE_RATES': {
        '未认证用户': '3/m',
        '已认证用户': '10/m',
    },

五、版本控制

API 版本控制允许我们在不同的客户端之间更改行为(同一个接口的不同版本会返回不同的数据)。 DRF提供了许多不同的版本控制方案。

可能会有一些客户端因为某些原因不再维护了,但是我们后端的接口还要不断的更新迭代,这个时候通过版本控制返回不同的内容就是一种不错的解决方案。

五种方案

 1、编写视图

class VersionView(APIView):

    def get(self, request, *args, **kwargs):
        # 获取版本
        print(request.version)
        # 获取版本管理的类
        print(request.versioning_scheme)
        # 反向生成URL
        reverse_url = request.versioning_scheme.reverse('app06:version-view', request=request)
        print(reverse_url)
        return Response('测试版本')

 2、编写路由

path('<str:version>/version/', views.VersionView.as_view(), name='version-view'),

 3、配置文件

REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
    'DEFAULT_VERSION': 'v1',  # 默认的版本
    'ALLOWED_VERSIONS': ['v1'],  # 有效的版本
    'VERSION_PARAM': 'version',  # 版本的参数名与URL conf中一致
}
#局部配置
versioning_class = "注意不是列表"

六、分页 

1、普通分页,看第n页,每页显示m条数据

        新建一个自定义分页类mypagenumberpagination

from rest_framework.pagination import PageNumberPagination


class MyPageNumberPagination(PageNumberPagination):
    page_size = 2
    max_page_size = 5
    page_size_query_param = 'size'
    page_query_param = 'page'
    
    '''
    age_query_param:表示url中的页码参数
        page_size_query_param:表示url中每页数量参数
        page_size:表示每页的默认显示数量
        max_page_size:表示每页最大显示数量,做限制使用,避免突然大量的查询数据,数据库崩溃

2、切割分页,在n个位置,向后查看m条数据

class MyPageNumberPagination(LimitOffsetPagination):
    default_limit = 2
    limit_query_param = 'limit'
    offset_query_param = 'offset'
    max_limit = 5
    
    '''
    default_limit:表示默认每页显示几条数据
        limit_query_param:表示url中本页需要显示数量参数
        offset_query_param:表示从数据库中的第几条数据开始显示参数
        max_limit:表示每页最大显示数量,做限制使用,避免突然大量的查询数据,数据库崩溃

3、加密分页,这与普通分页方式相似,不过对url中的请求页码进行加密

class MyPageNumberPagination(CursorPagination):
    cursor_query_param = 'cursor'
    page_size = 1
    ordering = 'id'
    page_size_query_param = 'size'
    max_page_size = 1

    '''
    cursor_query_param:表示url中页码的参数
    page_size_query_param:表示每页显示数据量的参数
    max_page_size:表示每页最大显示数量,做限制使用,避免突然大量的查询数据,数据库崩溃
    ordering:表示返回数据的排序方式

4、配置文件

'DEFAULT_PAGINATION_CLASS': 'app06.mypagenumberpagination.MyPageNumberPagination'

七、自定义补货异常

        通常捕获的异常如下

{
    "detail": "Authentication credentials were not provided."
}

        这样结构,对于移动端程序员是极其不友好的,所以我们一般给对方返回这样的数据结构

{
    "code": 401,
    "message": "Authentication credentials were not provided.",
    "data": []
}

        那我们就需要自己异常来捕获DRF里面的异常信息

       1、 创建custom_exception.py文件

from rest_framework.views import exception_handler


def custom_exception_handler(exc, context):
    response = exception_handler(exc, context)

    if response is not None:
        response.data.clear()
        response.data['code'] = response.status_code
        response.data['data'] = []

        if response.status_code == 404:
            try:
                response.data['message'] = response.data.pop('detail')
                response.data['message'] = "未找到"
            except KeyError:
                response.data['message'] = "未找到"

        if response.status_code == 400:
            response.data['message'] = '输入错误'

        elif response.status_code == 401:
            response.data['message'] = "未认证"

        elif response.status_code >= 500:
            response.data['message'] = "服务器错误"

        elif response.status_code == 403:
            response.data['message'] = "权限不允许"

        elif response.status_code == 405:
            response.data['message'] = '请求不允许'
        else:
            response.data['message'] = '未知错误'
    return response

        2、配置信息

'EXCEPTION_HANDLER': 'app06.custom_exception.custom_exception_handler'

        3、报具体错误

from rest_framework.views import exception_handler
from rest_framework.exceptions import ValidationError


def custom_exception_handler(exc, context):
    response = exception_handler(exc, context)

    if isinstance(exc, ValidationError):
        response.data['code'] = response.status_code
        response.data['data'] = []
        if isinstance(response.data, dict):
            response.data['message'] = list(dict(response.data).values())[0][0]

            for kaey in dict(response.data).keys():
                if key not in ['code', 'data', 'message']:
                    response.data.pop(key)
        else:
            response.data['message'] = '输入有误'
        return response

    if response is not None:
        response.data.clear()
        response.data['code'] = response.status_code
        response.data['data'] = []

        if response.status_code == 404:
            try:
                response.data['message'] = response.data.pop('detail')
                response.data['message'] = "未找到"
            except KeyError:
                response.data['message'] = "未找到"

        if response.status_code == 400:

            response.data['message'] = '输入错误'

        elif response.status_code == 401:
            response.data['message'] = '未认证'

        elif response.status_code >= 500:
            response.data['message'] = "服务器错误"

        elif response.status_code == 403:
            response.data['message'] = "权限不允许"

        elif response.status_code == 405:
            response.data['message'] = '请求不允许'
        else:
            response.data['message'] = '未知错误'
    return response

八、自定义返回

        1、自定义Jsonresponse

        创建一个custom_json_response.py文件

from django.utils import six
from rest_framework.response import Response
from rest_framework.serializers import Serializer

class JsonResponse(Response):
    """
    An HttpResponse that allows its data to be rendered into
    arbitrary media types.
    """

    def __init__(self, data=None, code=None, msg=None,
                 status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None, **kwargs):
        """
        Alters the init arguments slightly.
        For example, drop 'template_name', and instead use 'data'.
        Setting 'renderer' and 'media_type' will typically be deferred,
        For example being set automatically by the `APIView`.
        """
        super(Response, self).__init__(None, status=status)

        if isinstance(data, Serializer):
            msg = (
                'You passed a Serializer instance as data, but '
                'probably meant to pass serialized `.data` or '
                '`.error`. representation.'
            )
            raise AssertionError(msg)

        self.data = {"code": code, "message": msg, "data": data}
        self.data.update(kwargs) 
        self.template_name = template_name
        self.exception = exception
        self.content_type = content_type

        if headers:
            for name, value in six.iteritems(headers):
                self[name] = value

        2、自定义ModelViewSet

        创建custom_model_view_set.py文件

from rest_framework import status
from rest_framework import viewsets
from .custom_json_response import JsonResponse


class CustomModelViewSet(viewsets.ModelViewSet):

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return JsonResponse(data=serializer.data, msg="success", code=201, status=status.HTTP_201_CREATED,
                            headers=headers)

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return JsonResponse(data=serializer.data, code=200, msg="success", status=status.HTTP_200_OK)

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return JsonResponse(data=serializer.data, code=200, msg="success", status=status.HTTP_200_OK)

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return JsonResponse(data=serializer.data, msg="success", code=200, status=status.HTTP_200_OK)

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return JsonResponse(data=[], code=204, msg="delete resource success", status=status.HTTP_204_NO_CONTENT)

         3、自定义分页类需要修改

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination
from .custom_json_response import JsonResponse
from rest_framework import status


class MyPageNumberPagination(PageNumberPagination):
    page_size = 1
    max_page_size = 1
    page_size_query_param = 'size'
    page_query_param = 'page'

    def get_paginated_response(self, data):
        return JsonResponse(data=data, code=200, msg="success", status=status.HTTP_200_OK, next=self.get_next_link(),
                            previous=self.get_previous_link(), count=self.page.paginator.count)


class MyPageNumberPagination(LimitOffsetPagination):
    default_limit = 1
    limit_query_param = 'limit'
    offset_query_param = 'offset'
    max_limit = 2

    def get_paginated_response(self, data):
        return JsonResponse(data=data, code=200, msg="success", status=status.HTTP_200_OK, next=self.get_next_link(),
                            previous=self.get_previous_link(), count=self.count)


class MyPageNumberPagination(CursorPagination):
    cursor_query_param = 'cursor'
    page_size = 1
    ordering = 'id'
    page_size_query_param = 'size'
    max_page_size = 1

    def get_paginated_response(self, data):
        return JsonResponse(data=data, code=200, msg="success", status=status.HTTP_200_OK, next=self.get_next_link(),
                            previous=self.get_previous_link())

         Generics

from rest_framework import status
from rest_framework import viewsets
from .custom_json_response import JsonResponse
from rest_framework import generics


class ListCreateAPIView(generics.ListCreateAPIView):

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return JsonResponse(data=serializer.data, msg="success", code=201, status=status.HTTP_201_CREATED,
                            headers=headers)

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return JsonResponse(data=serializer.data, code=200, msg="success", status=status.HTTP_200_OK)


class RetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return JsonResponse(data=serializer.data, code=200, msg="success", status=status.HTTP_200_OK)

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return JsonResponse(data=serializer.data, msg="success", code=200, status=status.HTTP_200_OK)

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return JsonResponse(data=[], code=204, msg="delete resource success", status=status.HTTP_204_NO_CONTENT)

九、自动生成api文档

        1、DRF给我提供了自动生成API文档的功能,大大省去了写开发文档的时间

pip install coreapi

        2、路由

from rest_framework.documentation import include_docs_urls
path('docs/', include_docs_urls(title='测试平台接口文档'))

总结

细节后续待完善

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值