• Django 之RestFramework


    1. 从request先说起

    在Django原生的request里,请求的数据可以从request.GET或者request.POST里面取到。

    需要注意的是,如果是POST请求,request.POST里面只能取到Content-Type=application/x-www-form-urlencoded 的数据,我们常用的json数据就只能从报文request.body里取。  b'{"a": 1,"b": "2"}'

    So。。。

    RestFramework的APIView 封装的新request就解决了这个问题,post请求的任何数据都可以从request.data里面拿到 ,而get请求需要在request.GET里面取。

    并且它还保留了原生request的接口:request._request

    2. 序列化

    简单使用

    开发我们的Web API的第一件事是为我们的Web API提供一种将代码片段实例序列化和反序列化为诸如json之类的表示形式的方式。我们可以通过声明与Django forms非常相似的序列化器(serializers)来实现。

    准备models部分:

    class Book(models.Model):
        title=models.CharField(max_length=32)
        price=models.IntegerField()
        pub_date=models.DateField()
        CHOICES = ((1, "Python"), (2, "Go"), (3, "Linux"))
        category = models.IntegerField(choices=CHOICES, verbose_name="图书的类别")
        publish=models.ForeignKey("Publish")
        authors=models.ManyToManyField("Author")
        def __str__(self):
            return self.title
    
    class Publish(models.Model):
        name=models.CharField(max_length=32)
        email=models.EmailField()
        def __str__(self):
            return self.name
    
    class Author(models.Model):
        name=models.CharField(max_length=32)
        age=models.IntegerField()
        def __str__(self):
            return self.name
    View Code

    序列化类:

    from rest_framework import serializers
    from .models import Book
    
    
    class BookSerializer(serializers.Serializer):
        id = serializers.IntegerField(required=False)
        title = serializers.CharField(max_length=32, validators=[my_validate])  # 自定义验证方法,校验优先级高于下面的validate_title方法
    
        CHOICES = ((1, "Python"), (2, "Go"), (3, "Linux"))
        category = serializers.ChoiceField(choices=CHOICES, source="get_category_display",
                                           read_only=True)  # read_only表示get方法拿到的值
        w_category = serializers.ChoiceField(choices=CHOICES, write_only=True)  # write_only 表示只进行操作不进行展示
        pub_time = serializers.DateField()
    
        publisher = PublisherSerializer(
            read_only=True)  # 如若只想展示关键信息不想全部展示,只需要指定 publish = serializers.CharField(source="publish.name")
        publisher_id = serializers.IntegerField(write_only=True)
    
        # authors = serializers.SerializerMethodField()  # 所以处理多对多关系使用SerializerMethodField 方法
        # def get_authors(self, obj):  # get_字段 可以返回任何类型你想返回的值
        #     temp = []
        #     for author in obj.authors.all():
        #         temp.append(author.name)
        #     return temp
    
        # 处理多对多的get请求时有两种方法:1、如上,想展示什么字段都可以。2、如下,嵌套定义的序列化类,这里想展示的数量不能自己定制
        author = AuthorSerializer(many=True, read_only=True)  # many=True 表示有多个值
    
        author_list = serializers.ListField(write_only=True)
    
        def create(self, validated_data):  # post请求重新create方法
            book = Book.objects.create(title=validated_data["title"], category=validated_data["w_category"],
                                       pub_time=validated_data["pub_time"], publisher_id=validated_data["publisher_id"])
            book.author.add(*validated_data["author_list"])
            return book
    
        def update(self, instance, validated_data):  # 更新重写update
            instance.title = validated_data.get("title", instance.title)
            instance.category = validated_data.get("category", instance.category)
            instance.pub_time = validated_data.get("pub_time", instance.pub_time)
            instance.publisher_id = validated_data.get("publisher_id", instance.publisher_id)
            if validated_data.get("author_list"):
                instance.author.set(validated_data["author_list"])
            instance.save()
            return instance
    
        def validate_title(self, value):  # 指定某个字段的验证
            if "python" not in value.lower():
                raise serializers.ValidationError("标题必须含有python")
            return value
    
        def validate(self, attrs):  # 联合验证
            if attrs["w_category"] == 1 and attrs["publisher_id"] == 1:
                return attrs
            else:
                raise serializers.ValidationError("分类以及标题不符合要求")
    
    
    def my_validate(value):  # 额外为字段定制的校验
        if "敏感信息" in value.lower():
            raise serializers.ValidationError("不能含有敏感信息")
        else:
            return value
    

    views部分:

    from rest_framework.views import APIView
    from rest_framework.response import Response  # 可以用Django原生的Response,但是rest_framework的response做了一些封装。可以格式化你的json数据,并且浏览器访问会返回一个api页面
    from .models import *
    from django.shortcuts import HttpResponse
    # from django.core import serializers
    
    class BookViewSet(APIView):
    
        def get(self, request, *args, **kwargs):
            book_list = Book.objects.all()
            # 序列化方式1:
            # from django.forms.models import model_to_dict
            # import json
            # data=[]
            # for obj in book_list:
            #     data.append(model_to_dict(obj))
            # print(data)
            # return HttpResponse("ok")
    
            # 序列化方式2:
            # data=serializers.serialize("json",book_list)
            # return HttpResponse(data)
    
            # 序列化方式3:
            bs = BookSerializers(book_list, many=True)  # many=true 代表了返回的是一个queryset列表,返回一个对象就不用加
            return Response(bs.data)

    ModelSerializer

    class BookSerializer(serializers.ModelSerializer):
        category_display = serializers.SerializerMethodField(read_only=True)  # 自定义要获取的字段内容,在下面用get_<field-name>钩子来自定义内容
        publisher_info = serializers.SerializerMethodField(read_only=True)  # 此方法
        authors = serializers.SerializerMethodField(read_only=True)
    
        def get_category_display(self, obj):
            return obj.get_category_display()  # orm中Choice_field显示中文的方式
    
        def get_authors(self, obj):  # get_字段:可以返回你想要返回的值
            authors_query_set = obj.author.all()
            return [{"id": author_obj.id, "name": author_obj.name} for author_obj in authors_query_set]
    
        def get_publisher_info(self, obj):
            # obj 是我们序列化的每个Book对象
            publisher_obj = obj.publisher
            return {"id": publisher_obj.id, "title": publisher_obj.title}
    
        class Meta:
            model = Book
            # fields = ["id", "title", "pub_time"]
            fields = "__all__"  # 遇到一对多或多对多默认取主键,所以需要自己定义取得数据
            # depth = 1 #外键嵌套的层级数量
    
            extra_kwargs = {"category": {"write_only": True}, "publisher": {"write_only": True},
                            "author": {"write_only": True}}  # 使用extra_kwargs参数为ModelSerializer添加或修改原有的选项参数
    

    提交post请求

    复制代码
      def post(self,request,*args,**kwargs):
           
            bs=BookSerializers(data=request.data,many=False)
            if bs.is_valid():
                # print(bs.validated_data)
                bs.save()
                return Response(bs.data)
            else:
                return HttpResponse(bs.errors)
    复制代码

    重写save中的create方法

    post请求需要重写create()方法,put请求需要重写update()方法

    复制代码
    class BookSerializers(serializers.ModelSerializer):
    
          class Meta:
              model=Book
              fields="__all__"
              # exclude = ['authors',]
              # depth=1
    
          def create(self, validated_data):
            
              authors = validated_data.pop('authors')
              obj = Book.objects.create(**validated_data)
              obj.authors.add(*authors)
              return obj
    复制代码

     单条数据的get和put请求

    复制代码
    class BookDetailViewSet(APIView):
    
        def get(self,request,pk):
            book_obj=Book.objects.filter(pk=pk).first()
            bs=BookSerializers(book_obj)
            return Response(bs.data)
    
        def put(self,request,pk):
            book_obj=Book.objects.filter(pk=pk).first()
            bs=BookSerializers(book_obj,data=request.data)
            if bs.is_valid():
                bs.save()
                return Response(bs.data)
            else:
                return HttpResponse(bs.errors)
    复制代码

    超链接API:Hyperlinked

    复制代码
    class BookSerializers(serializers.ModelSerializer):
          publish= serializers.HyperlinkedIdentityField(
    view_name='publish_detail',
    lookup_field="publish_id",
    lookup_url_kwarg="pk") class Meta: model=Book fields="__all__" #depth=1
    复制代码

    urls部分:

    urlpatterns = [
        url(r'^books/$', views.BookViewSet.as_view(),name="book_list"),
        url(r'^books/(?P<pk>d+)$', views.BookDetailViewSet.as_view(),name="book_detail"),
        url(r'^publishers/$', views.PublishViewSet.as_view(),name="publish_list"),
        url(r'^publishers/(?P<pk>d+)$', views.PublishDetailViewSet.as_view(),name="publish_detail"),
    ]

    3. 视图优化

    上一节的视图部分:

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from .models import *
    from django.shortcuts import HttpResponse
    from django.core import serializers
    
    
    from rest_framework import serializers
    
    
    class BookSerializers(serializers.ModelSerializer):
          class Meta:
              model=Book
              fields="__all__"
              #depth=1
    
    
    class PublshSerializers(serializers.ModelSerializer):
    
          class Meta:
              model=Publish
              fields="__all__"
              depth=1
    
    
    class BookViewSet(APIView):
    
        def get(self,request,*args,**kwargs):
            book_list=Book.objects.all()
            bs=BookSerializers(book_list,many=True,context={'request': request})
            return Response(bs.data)
    
    
        def post(self,request,*args,**kwargs):
            print(request.data)
            bs=BookSerializers(data=request.data,many=False)
            if bs.is_valid():
                print(bs.validated_data)
                bs.save()
                return Response(bs.data)
            else:
                return HttpResponse(bs.errors)
    
    
    class BookDetailViewSet(APIView):
    
        def get(self,request,pk):
            book_obj=Book.objects.filter(pk=pk).first()
            bs=BookSerializers(book_obj,context={'request': request})
            return Response(bs.data)
    
        def put(self,request,pk):
            book_obj=Book.objects.filter(pk=pk).first()
            bs=BookSerializers(book_obj,data=request.data,context={'request': request})
            if bs.is_valid():
                bs.save()
                return Response(bs.data)
            else:
                return HttpResponse(bs.errors)
    
    
    class PublishViewSet(APIView):
    
        def get(self,request,*args,**kwargs):
            publish_list=Publish.objects.all()
            bs=PublshSerializers(publish_list,many=True,context={'request': request})
            return Response(bs.data)
    
    
        def post(self,request,*args,**kwargs):
    
            bs=PublshSerializers(data=request.data,many=False)
            if bs.is_valid():
                # print(bs.validated_data)
                bs.save()
                return Response(bs.data)
            else:
                return HttpResponse(bs.errors)
    
    
    class PublishDetailViewSet(APIView):
    
        def get(self,request,pk):
    
            publish_obj=Publish.objects.filter(pk=pk).first()
            bs=PublshSerializers(publish_obj,context={'request': request})
            return Response(bs.data)
    
        def put(self,request,pk):
            publish_obj=Publish.objects.filter(pk=pk).first()
            bs=PublshSerializers(publish_obj,data=request.data,context={'request': request})
            if bs.is_valid():
                bs.save()
                return Response(bs.data)
            else:
                return HttpResponse(bs.errors)
    上一节实现的逻辑代码

    3.1 使用混合(mixins)

    from web.models import *
    from web.serializer import BookSerializer
    from rest_framework.mixins import 
        (ListModelMixin, CreateModelMixin, DestroyModelMixin, UpdateModelMixin, RetrieveModelMixin)
    from rest_framework.generics import GenericAPIView
    
    
    class BookViewSet(ListModelMixin,
                      CreateModelMixin,
                      GenericAPIView):
        queryset = Book.objects.all()  # 必须要指定一个quetyset
        serializer_class = BookSerializer  # 必须要指定一个序列化类
    
        def get(self, request, *args, **kwargs):
            return self.list(request, *args, **kwargs)
    
        def post(self, request, *args, **kwargs):
            return self.create(request, *args, **kwargs)
    
    
    class BookDetailViewSet(RetrieveModelMixin,  # 单条数据操作视图
                            UpdateModelMixin,
                            DestroyModelMixin,
                            GenericAPIView):
        queryset = Book.objects.all()
        serializer_class = BookSerializer
    
        def get(self, request, *args, **kwargs):
            return self.retrieve(request, *args, **kwargs)
    
        def put(self, request, *args, **kwargs):
            return self.update(request, *args, **kwargs)
    
        def delete(self, request, *args, **kwargs):
            return self.destroy(request, *args, **kwargs)

    3.2 使用通用的基于类的视图

    通过使用mixin类,我们使用更少的代码重写了这些视图,但我们还可以再进一步。REST框架提供了一组已经混合好(mixed-in)的通用视图,我们可以使用它来简化我们的views.py模块。

    from rest_framework import mixins
    from rest_framework import generics
    
    class BookViewSet(generics.ListCreateAPIView):
    
        queryset = Book.objects.all()
        serializer_class = BookSerializers
    
    class BookDetailViewSet(generics.RetrieveUpdateDestroyAPIView):
        queryset = Book.objects.all()
        serializer_class = BookSerializers

    3.3 viewsets.ModelViewSet

    urls.py:

    urlpatterns = [
        re_path(r'^books/$', views.BookViewSet.as_view({"get": "list", "post": "create"}), name="book_list"),
        re_path(r'^books/(?P<pk>d+)$', views.BookViewSet.as_view({
            'get': 'retrieve',
            'put': 'update',
            'patch': 'partial_update',
            'delete': 'destroy'
        }), name="book_detail"),
    ]

    views.py:

    from rest_framework.viewsets import ModelViewSet
    
    class BookViewSet(ModelViewSet):
        queryset = Book.objects.all()  # 指定一个RetrieveModelMixin
        serializer_class = BookSerializer  # 指定一个序列化类

    4. 认证与权限组件

    4.1 认证组件

    局部视图认证

    先定义一个认证类:

    from rest_framework.authentication import BaseAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    class Authentication(BaseAuthentication): def authenticate(self,request): token=request._request.GET.get("token") token_obj=UserToken.objects.filter(token=token).first() if not token_obj: raise exceptions.AuthenticationFailed("验证失败!") return (token_obj.user,token_obj)

    在views.py:

    def get_random_str(user):
        import hashlib,time
        ctime=str(time.time())
    
        md5=hashlib.md5(bytes(user,encoding="utf8"))
        md5.update(bytes(ctime,encoding="utf8"))
    
        return md5.hexdigest()
    
    
    from app01.service.auth import *
    
    from django.http import JsonResponse
    class LoginViewSet(APIView):
        authentication_classes = [Authentication,]
        def post(self,request,*args,**kwargs):
            res={"code":1000,"msg":None}
            try:
                user=request._request.POST.get("user")
                pwd=request._request.POST.get("pwd")
                user_obj=UserInfo.objects.filter(user=user,pwd=pwd).first()
                print(user,pwd,user_obj)
                if not user_obj:
                    res["code"]=1001
                    res["msg"]="用户名或者密码错误"
                else:
                    token=get_random_str(user)
                    UserToken.objects.update_or_create(user=user_obj,defaults={"token":token})
                    res["token"]=token
    
            except Exception as e:
                res["code"]=1002
                res["msg"]=e
    
            return JsonResponse(res,json_dumps_params={"ensure_ascii":False})

    全局视图认证组件

    settings.py配置如下:

    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",]
    }

    4.2 权限组件

    局部视图权限

    在app01.service.permissions.py中:

    from rest_framework.permissions import BasePermission
    class SVIPPermission(BasePermission):
        message="SVIP才能访问!"
        def has_permission(self, request, view):
            if request.user.user_type==3:
                return True
            return False

    在views.py:

    from app01.service.permissions import *
    
    class BookViewSet(generics.ListCreateAPIView):
        permission_classes = [SVIPPermission,]
        queryset = Book.objects.all()
        serializer_class = BookSerializers

    全局视图权限

    settings.py配置如下:

    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
        "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",]
    }

    5. throttle(访问频率)组件

    局部视图throttle

    在app01.service.throttles.py中:

    from rest_framework.throttling import BaseThrottle
    
    VISIT_RECORD={}
    class VisitThrottle(BaseThrottle):
    
        def __init__(self):
            self.history=None
    
        def allow_request(self,request,view):
            remote_addr = request.META.get('REMOTE_ADDR')
            print(remote_addr)
            import time
            ctime=time.time()
    
            if remote_addr not in VISIT_RECORD:
                VISIT_RECORD[remote_addr]=[ctime,]
                return True
    
            history=VISIT_RECORD.get(remote_addr)
            self.history=history
    
            while history and history[-1]<ctime-60:
                history.pop()
    
            if len(history)<3:
                history.insert(0,ctime)
                return True
            else:
                return False
    
        def wait(self):
            import time
            ctime=time.time()
            return 60-(ctime-self.history[-1])

    在views.py中:

    from app01.service.throttles import *
    
    class BookViewSet(generics.ListCreateAPIView):
        throttle_classes = [VisitThrottle,]
        queryset = Book.objects.all()
        serializer_class = BookSerializers

    全局视图throttle

    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
        "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
        "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",]
    }

    内置throttle类

    在app01.service.throttles.py修改为:

    class VisitThrottle(SimpleRateThrottle):
    
        scope="visit_rate"
        def get_cache_key(self, request, view):
    
            return self.get_ident(request)

    settings.py设置:

    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
        "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
        "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",],
        "DEFAULT_THROTTLE_RATES":{
            "visit_rate":"5/m",  # 5:5次 m:分
        }
    }

    6. 解析器

    request类

    django的request类和rest-framework的request类的源码解析

    局部视图

    复制代码
    from rest_framework.parsers import JSONParser,FormParser
    class PublishViewSet(generics.ListCreateAPIView):
        parser_classes = [FormParser,JSONParser]
        queryset = Publish.objects.all()
        serializer_class = PublshSerializers
        def post(self, request, *args, **kwargs):
            print("request.data",request.data)
            return self.create(request, *args, **kwargs)
    复制代码

    全局视图

    复制代码
    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
        "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
        "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",],
        "DEFAULT_THROTTLE_RATES":{
            "visit_rate":"5/m",
        },
        "DEFAULT_PARSER_CLASSES":['rest_framework.parsers.FormParser',]
    }
    复制代码

    分页

    简单分页

    复制代码
    from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination
    
    class PNPagination(PageNumberPagination):
            page_size = 1
            page_query_param = 'page'
            page_size_query_param = "size"
            max_page_size = 5
    
    class BookViewSet(viewsets.ModelViewSet):
    
        queryset = Book.objects.all()
        serializer_class = BookSerializers
        def list(self,request,*args,**kwargs):
    
            book_list=Book.objects.all()
            pp=LimitOffsetPagination()
            pager_books=pp.paginate_queryset(queryset=book_list,request=request,view=self)
            print(pager_books)
            bs=BookSerializers(pager_books,many=True)
    
            #return Response(bs.data)
            return pp.get_paginated_response(bs.data)
    复制代码

    偏移分页

    from rest_framework.pagination import LimitOffsetPagination
  • 相关阅读:
    Redis 常用配制参数
    CentOS 7 环境下配制 Redis 服务
    Mysql ERROR 1032 (HY000): Can't find record in TABLE
    Linux下利用Shell使PHP并发采集淘宝产品
    Linux C连接Mysql
    PHP采集淘宝商品
    关于Certificate、Provisioning Profile、App ID的介绍及其之间的关系
    mac下svn无法上传.a文件的问题
    armv6, armv7, armv7s的区别
    【转】图片处理:颜色矩阵和坐标变换矩阵
  • 原文地址:https://www.cnblogs.com/zhouxiaoming123/p/11821181.html
Copyright © 2020-2023  润新知