• Django REST framework 2


    DRF的版本控制

    • API 版本控制允许在不同的客户端之间更改行为(同一个接口的不同版本会返回不同的数据)。 DRF提供了许多不同的版本控制方案。
    • 可能会有一些客户端因为某些原因不再维护了,但是后端的接口还要不断的更新迭代,这个时候通过版本控制返回不同的内容就是一种不错的解决方案。

    一、DRF提供的版本控制方案

    DRF提供了五种版本控制方案,如下图:

    二、版本控制系统的使用

    1、全局配置

    这里以URLPathVersioning 为例,还是在项目的settings.py中REST_FRAMEWORK配置项下配置:

    REST_FRAMEWORK = {
        ...
        'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
        'DEFAULT_VERSION': 'v1',  # 默认的版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],  # 有效的版本
        'VERSION_PARAM': 'version',  # 版本的参数名与URL conf中一致
    }

    2、urls.py中

    urlpatterns = [
        ...
        url(r'^(?P<version>[v1|v2]+)/publishers/$', views.PublisherViewSet.as_view({'get': 'list', 'post': 'create'})),
        url(r'^(?P<version>[v1|v2]+)/publishers/(?P<pk>\d+)/$', views.PublisherViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
    
    ]

    3、视图中

    • 视图中可以通过访问 request.version 来获取当前请求的具体版本,然后根据不同的版本来返回不同的内容。
    • 可以在视图中自定义具体的行为,下面以不同的版本返回不同的序列化类为例。
    class PublisherViewSet(ModelViewSet):
        queryset = models.Publisher.objects.all()
        serializer_class = PublisherModelSerializer
    
        def get_serializer_class(self):
            """不同的版本使用不同的序列化类"""
            if self.request.version == 'v1':
                return PublisherModelSerializerVersion1
            else:
                return PublisherModelSerializer
    
        #或者不同的版本使用不同的数据
        def get_queryset(self):
            if self.request.version == 'v1':
                return models.Publisher.objects.all()
            else:
                return models.Publisherold.objects.all()

    4、局部配置

    注意,通常是不会单独给某个视图设置版本控制的,如果你确实需要给单独的视图设置版本控制,可以在视图中设置versioning_class属性,如下:

    class PublisherViewSet(ModelViewSet):
    
        ...
        versioning_class = URLPathVersioning

    认证

    • 身份验证是将传入请求与一组标识凭据(例如请求来自的用户或其签名的令牌)相关联的机制。然后 权限 和 限制 组件决定是否拒绝这个请求。
    • 认证确定了你是谁
    • 权限确定你能不能访问某个接口
    • 限制确定你访问某个接口的频率

    一、自定义Token认证

    REST framework 提供了一些身份验证方案,并且还允许实现自定义方案。

    1、表:

    class UserInfo(models.Model):
        username = models.CharField(max_length=16)
        password = models.CharField(max_length=32)
        type = models.SmallIntegerField(
            choices=((1, '普通用户'), (2, 'VIP用户')),
            default=1
        )
    
    
    class Token(models.Model):
        user = models.OneToOneField(to='UserInfo')
        token_code = models.CharField(max_length=128)
     

    2、定义一个登录视图:

    def get_random_token(username):
        """
        根据用户名和时间戳生成随机token
        :param username:
        :return:
        """
        import hashlib, time
        timestamp = str(time.time())
        m = hashlib.md5(bytes(username, encoding="utf8"))
        m.update(bytes(timestamp, encoding="utf8"))
        return m.hexdigest()
    
    
    class LoginView(APIView):
        """
        校验用户名密码是否正确从而生成token的视图
        """
        def post(self, request):
            res = {"code": 0}
            print(request.data)
            username = request.data.get("username")
            password = request.data.get("password")
    
            user = models.UserInfo.objects.filter(username=username, password=password).first()
            if user:
                # 如果用户名密码正确
                token = get_random_token(username)
                models.Token.objects.update_or_create(defaults={"token_code": token}, user=user)
                res["token"] = token
            else:
                res["code"] = 1
                res["error"] = "用户名或密码错误"
            return Response(res)

    3、定义一个认证类

    from rest_framework.authentication import BaseAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    
    
    class MyAuth(BaseAuthentication):
        def authenticate(self, request):
            if request.method in ["POST", "PUT", "DELETE"]:
                request_token = request.data.get("token", None)
                if not request_token:
                    raise AuthenticationFailed('缺少token')
                token_obj = models.Token.objects.filter(token_code=request_token).first()
                if not token_obj:
                    raise AuthenticationFailed('无效的token')
                return token_obj.user.username, None
            else:
                return None, None

    4、视图级别认证

    class CommentViewSet(ModelViewSet):
    
        queryset = models.Comment.objects.all()
        serializer_class = app01_serializers.CommentSerializer
        authentication_classes = [MyAuth, ]

    5、全局级别认证

    # 在settings.py中配置
    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.MyAuth", ]
    }

    权限

    只有VIP用户才能看的内容。

    一、自定义

    1、自定义一个权限类

    # 自定义权限
    class MyPermission(BasePermission):
        message = 'VIP用户才能访问'
    
        def has_permission(self, request, view):
            """
            自定义权限只有VIP用户才能访问
            """
            # 因为在进行权限判断之前已经做了认证判断,所以这里可以直接拿到request.user
            if request.user and request.user.type == 2:  # 如果是VIP用户
                return True
            else:
                return False

    2、视图级别配置

    class CommentViewSet(ModelViewSet):
    
        queryset = models.Comment.objects.all()
        serializer_class = app01_serializers.CommentSerializer
        authentication_classes = [MyAuth, ]
        permission_classes = [MyPermission, ]

    3、全局级别设置

    # 在settings.py中设置rest framework相关配置项
    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.MyAuth", ],
        "DEFAULT_PERMISSION_CLASSES": ["app01.utils.MyPermission", ]
    }

    限制

    DRF内置了基本的限制类,首先我们自己动手写一个限制类,熟悉下限制组件的执行过程。

    一、自定义

    1、自定义限制类

    VISIT_RECORD = {}
    # 自定义限制
    class MyThrottle(object):
    
        def __init__(self):
            self.history = None
    
        def allow_request(self, request, view):
            """
            自定义频率限制60秒内只能访问三次
            """
            # 获取用户IP
            ip = request.META.get("REMOTE_ADDR")
            timestamp = time.time()
            if ip not in VISIT_RECORD:
                VISIT_RECORD[ip] = [timestamp, ]
                return True
            history = VISIT_RECORD[ip]
            self.history = history
            history.insert(0, timestamp)
            while history and history[-1] < timestamp - 60:
                history.pop()
            if len(history) > 3:
                return False
            else:
                return True
    
        def wait(self):
            """
            限制时间还剩多少
            """
            timestamp = time.time()
            return 60 - (timestamp - self.history[-1])

    2、视图使用

    class CommentViewSet(ModelViewSet):
    
        queryset = models.Comment.objects.all()
        serializer_class = app01_serializers.CommentSerializer
        throttle_classes = [MyThrottle, ]

    3、全局使用

    # 在settings.py中设置rest framework相关配置项
    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.MyAuth", ],
        "DEFAULT_PERMISSION_CLASSES": ["app01.utils.MyPermission", ]
        "DEFAULT_THROTTLE_CLASSES": ["app01.utils.MyThrottle", ]
    }

    一、内置

    1、使用内置限制类

    from rest_framework.throttling import SimpleRateThrottle
    
    
    class VisitThrottle(SimpleRateThrottle):
    
        scope = "xxx"
    
        def get_cache_key(self, request, view):
            return self.get_ident(request)

    2、全局配置

    # 在settings.py中设置rest framework相关配置项
    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.MyAuth", ],
        # "DEFAULT_PERMISSION_CLASSES": ["app01.utils.MyPermission", ]
        "DEFAULT_THROTTLE_CLASSES": ["app01.utils.VisitThrottle", ],
        "DEFAULT_THROTTLE_RATES": {
            "xxx": "5/m",
        }
    }

    DRF内置分页器

    • rest framework中提供了三种分页模式:
    • from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination

    一、PageNumberPagination

    • 按页码数分页,第n页,每页显示m条数据
    • 例如:http://127.0.0.1:8000/api/article/?page=2&size=1

    1、分页器

    class MyPageNumber(PageNumberPagination):
        page_size = 2  # 每页显示多少条
        page_size_query_param = 'size'  # URL中每页显示条数的参数
        page_query_param = 'page'  # URL中页码的参数
        max_page_size = None  # 最大页码数限制

    2、视图

    class ArticleList(APIView):
        def get(self, request, *args, **kwargs):
            res = {"code": 0}
            article_list = models.Article.objects.all().order_by("id")
            # 分页
            page_obj = MyPageNumber()
            page_article = page_obj.paginate_queryset(queryset=article_list, request=request, view=self)
            ser_obj = ArticleSerializer(page_article, many=True)
            res["data"] = ser_obj.data
            return Response(res)

    3、返回带页码链接的响应

    class ArticleList(APIView):
        def get(self, request, *args, **kwargs):
            res = {"code": 0}
            article_list = models.Article.objects.all().order_by("id")
            # 分页
            page_obj = MyPageNumber()
            page_article = page_obj.paginate_queryset(queryset=article_list, request=request, view=self)
            ser_obj = ArticleSerializer(page_article, many=True)
            res["data"] = ser_obj.data
            return page_obj.get_paginated_response(res)

    二、LimitOffsetPagination 

    • 分页,在n位置,向后查看m条数据
    • 例如:http://127.0.0.1:8000/api/article/?offset=2&limit=2

    1、分页器

    # offset分页
    class MyLimitOffset(LimitOffsetPagination):
        default_limit = 1
        limit_query_param = 'limit'
        offset_query_param = 'offset'
        max_limit = 999

    2、视图

    class ArticleList(APIView):
        def get(self, request, *args, **kwargs):
            res = {"code": 0}
            article_list = models.Article.objects.all().order_by("id")
            # 分页
            page_obj = MyLimitOffset()
            page_article = page_obj.paginate_queryset(queryset=article_list, request=request, view=self)
            ser_obj = ArticleSerializer(page_article, many=True)
            res["data"] = ser_obj.data
            return page_obj.get_paginated_response(res)

    三、CursorPagination

    • 加密分页,把上一页和下一页的id值记住

    1、分页器

    # 加密分页
    class MyCursorPagination(CursorPagination):
        cursor_query_param = 'cursor'
        page_size = 1
        ordering = '-id'  # 重写要排序的字段

    2、视图

    class ArticleList(APIView):
        def get(self, request, *args, **kwargs):
            res = {"code": 0}
            article_list = models.Article.objects.all().order_by("id")
            # 分页
            page_obj = MyCursorPagination()
            page_article = page_obj.paginate_queryset(queryset=article_list, request=request, view=self)
            ser_obj = ArticleSerializer(page_article, many=True)
            res["data"] = ser_obj.data
            # return Response(res)
            return page_obj.get_paginated_response(res)

    四、全局配置

    REST_FRAMEWORK = {
        'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
        'PAGE_SIZE': 100
    }

    五、局部配置

    可以在视图类中进行局部设置

    class PublisherViewSet(ModelViewSet):
    queryset = models.Publisher.objects.all()
    serializer_class = PublisherModelSerializer
    pagination_class = PageNumberPagination # 注意不是列表(只能有一个分页模式)

    解析器

    • 解析器的作用就是服务端接收客户端传过来的数据,把数据解析成自己可以处理的数据。本质就是对请求体中的数据进行解析。
    • 在了解解析器之前,要先知道Accept以及ContentType请求头。
    • Accept是告诉对方能解析什么样的数据,通常也可以表示想要什么样的数据。
    • 解析器工作原理的就是拿到请求的ContentType来判断前端给后端的数据类型是什么,然后在后端使用相应的解析器去解析数据。

    一、Django中的数据解析

    • 在视图中可以通过request.POST来获取前端发来的请求数据,那么Django框架是如何拿到请求体中的数据的呢?
    • 首先,request对象是 WSGIRequest 类的实例化对象
    • 在Django的视图中通过request.POST和request.FILES能够取到数据都是因为在这里把请求的数据解析,并赋值给request对象了。
    • Django的解析器是不支持 ContenType为 application/json 的,也就是说无法解析json格式的数据。

    二、DRF中的解析器

    • DRF中获取请求提交的数据是通过访问request.data
    • 如果没有配置解析器,DRF会使用默认的解析器:
    • 可以在单个视图或者全局的settings.py中配置要使用的解析器。
    • 注意:当项目中只配置了 JSONParser 解析器时,就只能解析JSON格式的数据了,客户端如果使用浏览器提交,将无法解析。
    • 注意,在视图类中定义的配置项的优先级要高于全局配置中的配置项。

    1、单个视图配置

    class BookViewSet(ModelViewSet):
        queryset = models.Book.objects.all()
        serializer_class = BookModelSerializer
        parser_classes = [JSONParser, ]

    2、全局配置

    REST_FRAMEWORK = {
        'DEFAULT_PARSER_CLASSES': (
            'rest_framework.parsers.JSONParser',
        )
    }

    渲染器

    • 渲染器同解析器相反,它定义了框架按照content_type来返回不同的响应。
    • 可以在视图中局部设置也可以在全局的settings.py中进行设置
    • 注意,在视图类中定义的配置项的优先级要高于全局配置中的配置项。
    • DRF提供的渲染器有很多,默认是:
     'DEFAULT_RENDERER_CLASSES': (
            'rest_framework.renderers.JSONRenderer',
            'rest_framework.renderers.BrowsableAPIRenderer',
        ),

    1、局部设置

    这样设置后就只能返回JSON格式的数据了,并不会像之前一样提供一个阅读友好的web页面。

    class PublisherViewSet(ModelViewSet):
        queryset = models.Publisher.objects.all()
        serializer_class = PublisherModelSerializer
        renderer_classes = [JSONRenderer, ]

    2、全局设置

    REST_FRAMEWORK = {
        'DEFAULT_RENDERER_CLASSES': (
            'rest_framework.renderers.JSONRenderer',
        ),
    }
  • 相关阅读:
    C++操作Kafka使用Protobuf进行跨语言数据交互
    聊聊Disruptor 和 Aeron 这两个开源库
    DTrace arg0-kernel mode and arg1-user mode
    top
    how to write your first linux device driver
    how to compile and replace ubuntu kernel
    linux du
    c++ rvo vs std::move
    【Android】wifi开发
    无线局域网络 WIFI/WAPI/WLAN区别浅析
  • 原文地址:https://www.cnblogs.com/bubu99/p/11112008.html
Copyright © 2020-2023  润新知