DRF之实战总结

news2024/11/24 18:51:59


前言

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='测试平台接口文档'))

总结

细节后续待完善

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/346480.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决rimraf使用时提示unexpected token “.”

解决rimraf使用时提示unexpected token “.” 前言 最近运行一个Cordova项目时&#xff0c;npm install后打包&#xff0c;命令栏提示了下面这个问题&#xff1a; 很奇怪啊&#xff0c;就我这里有问题&#xff0c;别人之前都没事&#xff0c;很头疼。 问题原因 经过一番摸索…

centos安装FastDFS,集成到SpringBoot中

前言 本教程采用centos7 实测 安装fastdfs&#xff0c;每一步都存在截图&#xff0c;安装不成功你就我 最关键的是采用springboot 集成 fastdfs&#xff0c;上传保存文件信息 小序 FastDFS是一个开源的分布式文件系统&#xff0c;她对文件进行管理&#xff0c;功能包括&…

为什么时间序列预测这么难?本文将给你答案

机器学习和深度学习已越来越多应用在时序预测中。ARIMA 或指数平滑等经典预测方法正在被 XGBoost、高斯过程或深度学习等机器学习回归算法所取代。 尽管时序模型越来越复杂&#xff0c;但人们对时序模型的性能表示怀疑。有研究表明&#xff0c;复杂的时序模型并不一定会比时序…

如果网站的 Cookie 特别多特别大,会发生什么(二)

协议 仔细回顾一遍 Cookie 属性&#xff0c;除了 secure&#xff0c;再没和 URL Scheme 相关的属性了。 HTTPS是在HTTP上建立SSL加密层&#xff0c;并对传输数据进行加密&#xff0c;是HTTP协议的安全版。现在它被广泛用于万维网上安全敏感的通讯&#xff0c;例如交易支付方面。…

C++类基础(十四)

类的继承——虚函数 ● 通过虚函数与引用&#xff08;指针&#xff09;实现动态绑定 – 使用关键字 virtual 引入 – 非静态、非构造函数可声明为虚函数 – 虚函数会引入vtable结构 ● dynamic_cast static_cast, reinterpret_cast, const_cast在编译期发生。 dynamic_cast在…

Linux常用命令--目录文件管理

Linux常用命令一、目录与文件管理场景一&#xff1a;我是谁场景二&#xff1a;我在哪&#xff1f;跳转目录场景三&#xff1a;查看目录内容场景四&#xff1a;创建目录场景五&#xff1a;如何在目录中创建一个文件场景六&#xff1a;如何编辑一个简单文本文件&#xff1f;场景七…

用Python实现自动发送周报给老板,强制周报不用愁

前言 老板每周要求写周报上交&#xff1f; 像我这种记性不好的&#xff0c;一个月四周忘记三次 只能用点小技术&#xff0c;用Python写个小工具&#xff0c;让它每周帮我给老板发周报~ Github: Weekday 小工具 目标细化 SMTP发送邮件, 用smtplib 读取配置文件 发件人 收件…

uml图六种箭头的含义

在看uml图忘记箭头含义记录一下 泛化 概念&#xff1a;泛化是一种一般与特殊、一般与具体之间关系的描述&#xff0c;具体描述建立在一般描述的基础之上&#xff0c;并对其进行了扩展。在java中用来表示继承的关系。 表示方法&#xff1a;用实线空心三角箭头表示。 实现 概念…

Postman中cookie的操作

在接口测试中&#xff0c;某些接口的调用&#xff0c;需要带入已有Cookie&#xff0c;比如有些接口需要登陆后才能访问。 Postman接口请求使用Cookie有如下两种方式&#xff1a; 1、直接在头域中添加Cookie头域&#xff0c;适用于已经知道请求所用Cookie数据的情况。 2、使用…

Vue3.0学习笔记

文章目录一. Vue开始1.1 渐进式框架1.2 单文件组件1.3 API 风格1.3.1 选项式 API1.3.2 组合式 API二. Vue基础2.1 创建一个 Vue 应用2.1.1 应用实例2.1.2 根组件2.1.3 挂载应用2.1.4 DOM 中的根组件模板2.1.5 应用配置2.1.6 多个应用实例2.2 模板语法2.2.1 文本插值2.2.2 原始 …

4K、高清、无水印视频素材库

推荐5个可以免费下载视频素材的网站&#xff0c;建议收起来&#xff01; 1、菜鸟图库 视频素材下载_mp4视频大全 - 菜鸟图库 菜鸟图库主要提供设计素材为主&#xff0c;自媒体等相关素材也很多&#xff0c;像商用图片、背景图、视频素材、音频素材都很齐全。视频素材全部都是…

不同接口的LCD硬件操作原理

不同接口的LCD硬件操作原理 文章目录不同接口的LCD硬件操作原理参考资料&#xff1a;一、 应用工程师眼里看到的LCD1.1 像素的颜色怎么表示二、 驱动工程师眼里看到的LCD2.1 统一的LCD硬件模型2.2 MCU常用的8080接口LCD模组2.3 MPU常用的TFT RGB接口2.4 有一个MIPI标准参考资料…

大专生现在转行IT可行吗?

抛开其他具体情况不说&#xff0c;大专是行业基本要求&#xff0c;大专学习转IT完全没有问题&#xff01; 关于IT行业的学历要求 大专学历是满足基本入行要求的&#xff0c;并且大专可以选择的方向也很多&#xff0c;比如开发、云计算、大数据、数据分析等方向&#xff0c;这…

云计算ACP云服务器ECS实例题库(二)

&#x1f618;作者简介&#xff1a;一名99年软件运维应届毕业生&#xff0c;正在自学云计算课程。&#x1f44a;宣言&#xff1a;人生就是B&#xff08;birth&#xff09;和D&#xff08;death&#xff09;之间的C&#xff08;choise&#xff09;&#xff0c;做好每一个选择。&…

java面试题-容器

目录 1、Java 容器都有哪些&#xff1f; 2.、Collection 和 Collections 有什么区别&#xff1f; 3、 List、Set、Map 之间的区别是什么&#xff1f; 4、HashMap 和 Hashtable 有什么区别&#xff1f; 5、 如何决定使用 HashMap 还是 TreeMap&#xff1f; 6、 说一下 Has…

融资、量产和一栈式布局,这家Tier 1如此备战高阶智驾决赛圈

作者 | Bruce 编辑 | 于婷 从早期的ADAS&#xff0c;到高速/城市NOA&#xff0c;智能驾驶的竞争正逐渐升级&#xff0c;这对于车企和供应商的核心技术和产品布局都是一个重要的考验。 部分智驾供应商已经在囤积粮草&#xff0c;响应变化。 2023刚一开年&#xff0c;智能驾驶…

C语言消消乐游戏代码

C和C游戏趣味编程》一书各个章节的案例代码&#xff0c;每章案例逐步利用学到的语法知识。 本章我们将编写十字消除游戏&#xff0c;用户点击空白方块&#xff0c;沿其上下左右方向寻找第一个彩色方块&#xff0c;如果有两个或两个以上颜色一致&#xff0c;就将其消除。在进度…

Spark 3.1.1 shuffle fetch 导致shuffle错位的问题

背景 最近从数据仓库小组那边反馈了一个问题,一个SQL任务出来的结果不正确&#xff0c;重新运行一次之后就没问题了&#xff0c;具体的SQL如下&#xff1a; select col1,count(1) as cnt from table1 where dt 20230202 group by col1 having count(1) > 1这个问题是偶发…

SpringBoot 整合JWT实现基于自定义注解的-登录请求验证拦截(保姆级教学,附:源码)

学习目标&#xff1a; Spring Boot 整合JWT实现基于自定义注解的 登录请求接口拦截 例&#xff1a; 一篇掌握 JWT 入门知识1.1 在学习SpringBoot 整合JWT之前&#xff0c;我们先来说说JWT进行用户身份验证的流程 1:客户端使用用户名和密码请求登录 2:服务端收到请求&#xf…

spring中@Autowire和@Resource的区别在哪里?

介绍今天使用Idea写代码的时候&#xff0c;看到之前的项目中显示有warning的提示&#xff0c;去看了下&#xff0c;是如下代码?Autowire private JdbcTemplate jdbcTemplate;提示的警告信息Field injection is not recommended Inspection info: Spring Team recommends: &quo…