• django restframework


      django restframework是基于django和restful协议开发的框架

      在restful协议里,一切皆是资源,操作是通过请求方式控制

      在开始前,你需要对CBV和FBV两种架构模式有个大概的了解,django restframework用的就是CBV架构,这里提供一篇博客供你欣赏CBV

      安装:pip install djangorestframework

    django原生request

      首先看到下面这段代码

    def post(self, request):
        print(request.body)
        print(request.POST)
    

       请求数据一般封装在请求头中,而上面打印的数据,都是经过处理后的数据,那背后是怎么进行封装的呢?

      对于get请求,直接去url后面的数据

      而对于post请求

    request.body: a=1&b=2
    request.POST:
        if contentType:urlencoded: a=1&b=2  ---> {"a":1,"b":2}
    

       当发urlencoded数据时,两个都能打印数据,但是如果就发json数据,就只有request.body里有了

      request源码剖析

      怎么看源码呢?打印下request的类型就可以了,print(type(request)), 查看打印的WSGIRequest就可以了

    from django.core.handlers.wsgi import WSGIRequest
    

       去它下面找POST,发现这么一句

        POST = property(_get_post, _set_post)
    

       在_get_post方法中,进入这个方法self._load_post_and_files(),在它里面就这么一段代码

            elif self.content_type == 'application/x-www-form-urlencoded':
                #如果urlencoded类型才把self.body赋给了self._post
                self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()
            else:
                self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()
    

    restframework request

      对于restframework的request,我们需要了解下在restframework里请求流程,它和django大致相同,因为它的APIView继承是django的View,但在APiView中重写了dispatch方法

      看到这段代码

    url(r'^publishers/$', views.PublishViewSet.as_view(),name="publish_list"),
    

       执行PublishViewSet就是APIView的as_view方法

    class APIView(View):
    

       APIView继承了View,APIView中有as_view方法,所以会执行这个方法,方法中有这么一句代码

    view = super(APIView, cls).as_view(**initkwargs)
    

       最终还是执行了父类里的as_view方法,所以最终执行结果,得到这么这个view函数

            def view(request, *args, **kwargs):
                self = cls(**initkwargs)
                if hasattr(self, 'get') and not hasattr(self, 'head'):
                    self.head = self.get
                self.request = request
                self.args = args
                self.kwargs = kwargs
                return self.dispatch(request, *args, **kwargs)
    

       当请求来时,会执行view函数,把dispatch结果返回,而这里dispatch方法则不是View里的,而是APIView的,因为APIView重写了这个方法,而django restframework的精髓就全部在这里边了

        def dispatch(self, request, *args, **kwargs):
            """
            `.dispatch()` is pretty much the same as Django's regular dispatch,
            but with extra hooks for startup, finalize, and exception handling.
            """
            self.args = args
            self.kwargs = kwargs
            #构建一个新的request,把旧的request封装给新的request的_request字段
            #执行.data会执行新request类中的data属性,而在self._load_data_and_files()里
            '''
            if not _hasattr(self, '_data'):
                self._data, self._files = self._parse()
                if self._files:
                    self._full_data = self._data.copy()
                    self._full_data.update(self._files)
                else:
                    self._full_data = self._data
            '''
            #最终返回self._full_data,而上面就是对请求内容进行解析并封装
            request = self.initialize_request(request, *args, **kwargs)
            '''
            #post
            print("request.data", request.data)
            print("request.data type", type(request.data))
            #get
            print(request._request.GET)
            print(request.GET)
            '''
            self.request = request
            self.headers = self.default_response_headers  # deprecate?
    
            try:
                self.initial(request, *args, **kwargs)
    
                # Get the appropriate handler method
                if request.method.lower() in self.http_method_names:
                    handler = getattr(self, request.method.lower(),
                                      self.http_method_not_allowed)
                else:
                    handler = self.http_method_not_allowed
                #获取get post等方法后执行,这里用是新的request
                response = handler(request, *args, **kwargs)
    
            except Exception as exc:
                response = self.handle_exception(exc)
    
            self.response = self.finalize_response(request, response, *args, **kwargs)
            return self.response
    

      在dispatch方法,通过请求方式映射,获取到我们写好的请求方法,并执行

    序列化

      restframework在前后端传输数据时,主要是json数据,过程中就要需要把其他数据转换成json数据,比如数据库查询所有数据时,是queryset对象,那就要把这对象处理成json数据返回前端

    models

    from django.db import models
    
    # Create your models here.
    
    class Book(models.Model):
        title=models.CharField(max_length=32)
        price=models.IntegerField()
        pub_date=models.DateField()
        publish=models.ForeignKey("Publish", on_delete=True)
        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
    

      

      那么这里提供三种序列化的方式:

    1. 对查询的数据类型进行基础数据类型的强转,比如list(queryset对象.values('name', 'sex')), 单个数据对象 model_to_dict(obj)
    2. django提供的serialize方法,data=serializers.serialize("json",book_list)
    3. restframework提供的serialize方法,但事先要定义序列化模型,BookSerializers(book_list,many=True),并且使用restframework提供Response返回数据(需要注意的是,你必现在app里注册了rest_framework)
    from django.shortcuts import render, HttpResponse
    from rest_framework.views import APIView
    from .models import *
    
    # Create your views here.
    
    from rest_framework import serializers
    from rest_framework.response import Response
    
    class AuthorSerializers(serializers.Serializer):
        name = serializers.CharField(max_length=32)
        age = serializers.IntegerField()
    
    
    class AuthorView(APIView):
    
        def get(self, request, *args, **kwargs):
            authors = Author.objects.all()
            # 方式1
            # data = authors.values("name","age")
    
            # from django.forms.models import model_to_dict
            # data = []
            # for obj in authors:
            #     data.append(model_to_dict(obj))
    
            #方式2
            # from django.core import serializers
            # data = serializers.serialize("json", authors)
            # return HttpResponse(data)
    
            #方式3
            author_ser = AuthorSerializers(authors, many=True)
            # return Response(author_ser.data)
    

      

      上面也只是说了下单表序列化,如果表中涉及到一对多,多对多怎么操作呢?

    1. 一对多,通过source="publish.name"指定字段
    2. 多对多,通过get_字段名钩子函数来定义要获取的内容
    from django.shortcuts import render, HttpResponse
    from rest_framework.views import APIView
    from .models import *
    
    # Create your views here.
    
    from rest_framework import serializers
    from rest_framework.response import Response
    
    class BookSerializers(serializers.Serializer):
        title = serializers.CharField(max_length=32)
        price = serializers.IntegerField()
        pub_date = serializers.DateField()
    
        # 一对多
        # publish = serializers.CharField()  #不加source时,默认给的是Publish模型定义__str__返回的字段
        publish = serializers.CharField(source="publish.name")
    
        # 多对多
        # authors = serializers.CharField(source="authors.all")  #获取是一个queryset对象  字符串
        authors = serializers.SerializerMethodField()  #通过钩子函数自定制需要的信息
        def get_authors(self, obj):
            temp = []
            for author in obj.authors.all():
                temp.append({'name':author.name, 'email':author.age})
            return temp
    
    class BookView(APIView):
    
        def get(self, request, *args, **kwargs):
            books = Book.objects.all()
            bs = BookSerializers(books, many=True)
            return Response(bs.data)
    

      

      当然上面的过程,定义serializers模型,针对表定义每个字段,有些繁琐,所以序列化模型也有类似于ModelForm用法,不过一对多,多对多都是默认值,取得都是对应对象的id

      如果你想定制多对多和一对多,在ModelSerializers重写这类型字段,但是需要注意的是,里面提供的create方法不支持source定制,所以你还需要重写create方法

    from django.shortcuts import render, HttpResponse
    from rest_framework.views import APIView
    from .models import *
    
    # Create your views here.
    
    from rest_framework import serializers
    from rest_framework.response import Response
    
    class BookSerializers(serializers.ModelSerializer):
        class Meta:
            model = Book
            fields = "__all__"
    
        #source指定字段,不影响get查看序列化,但是影响post创建数据,所以需要你重写create方法
        publish = serializers.CharField(source='publish.name')
        authors = serializers.SerializerMethodField()  #通过钩子函数自定制需要的信息
        def get_authors(self, obj):
            temp = []
            for author in obj.authors.all():
                temp.append({'name':author.name, 'email':author.age})
            return temp
    
        def create(self, validated_data):
            # author_list = validated_data.pop("authors")
            obj = Book.objects.create(**validated_data)
            # obj.authors.add(*author_list)
            return obj
    
    
    class BookView(APIView):
    
        def get(self, request, *args, **kwargs):
            books = Book.objects.all()
            bs = BookSerializers(books, many=True)
            return Response(bs.data)
    
        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)
    

      序列化超链接:HyperlinkedIdentityField序列化字段,指定三个参数 view_name url别名lookup_field填入链接里的值对应字段, lookup_url_kwarg url里对应的形参名

    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
    
    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"),
    ]
    

      对于重写create方法情景的总结(Django 1.10.1):

      1.序列化类继承serializers.Serializer,需要重写create,update方法,看源码这两个方法只是定义了抛错

    class AuthorSerializers(serializers.Serializer):
        name = serializers.CharField(max_length=32)
        age = serializers.IntegerField()
    
        def create(self, validated_data):
            obj = Author.objects.create(**validated_data)
            return obj
    

      2.表对象为单表,序列化类继承serializers.ModelSerializer,不需要重写create,update方法,源码里就在这个类下实现了这两个方法,另外这两个方法是在序列化类下,所以它跟你使用哪个视图类无关,比如使用generics.ListCreateAPIView

    class PublishSerializers(serializers.ModelSerializer):
        class Meta:
            model = Publish
            fields = "__all__"
    

      3.表对象为关联表,序列化类继承serializers.ModelSerializer,不存在定制某个字段,不需要重写create,update方法

    class BookSerializers(serializers.ModelSerializer):
        class Meta:
            model = Book #book表关联表,有一对多,多对多
            fields = "__all__"
    

      4.表对象为关联表,序列化类继承serializers.ModelSerializer,source指定某个字段,提示指定字段要发实例(一对多),存在的疑问就是怎么在前端添加实例?

      多对多,如果通过SerializerMethodField生成,序列化时,不提供多对多字段传过来的值,等同这种情景下,多对多只是只读显示,不用来post添加操作,如果要操作把添加对象和多对多添加分成两步操作

    class BookSerializers(serializers.ModelSerializer):
        class Meta:
            model = Book #book表关联表,有一对多,多对多
            fields = "__all__"
    
        authors = serializers.SerializerMethodField()
        def get_authors(self, obj):
            temp = []
            for author in obj.authors.all():
                temp.append({'name':author.name, 'email':author.age})
            return temp
    
        publish = serializers.CharField(source="publish.email")
    
        def create(self, validated_data):
            # authors = validated_data.pop("authors")
            obj = Book.objects.create(**validated_data)
            # obj.authors.add(*authors)
            return obj
    

      

    视图

      第一阶段 老老实实的干

      下面视图代码,可以说是规规矩矩的做法,每个视图类下,都写各个请求方法,细心的你肯定发现了,每个视图的同类请求方法实现过程处理序列化模型和查询的表不同,其他的都一样,代码重复

    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)
    

      

      第二阶段 mixin封装类编写

      restframework已经对我们使用的这些请求方法,封装到一些类里面,我们只要继承这些类,并调用特定的方法,把结果返回即可

      至于是哪个序列化模型和哪个模型的数据,通过静态字段queryset和serializer_class指定

      而GenericAPIView继承了APIView,所以在程序启动时,执行的还是APIView的as_view方法

    代码简化如下

    from rest_framework import mixins
    from rest_framework import generics
    
    class BookViewSet(mixins.ListModelMixin,
                      mixins.CreateModelMixin,
                      generics.GenericAPIView):
    
        queryset = Book.objects.all()
        serializer_class = BookSerializers
    
        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(mixins.RetrieveModelMixin,
                        mixins.UpdateModelMixin,
                        mixins.DestroyModelMixin,
                        generics.GenericAPIView):
        queryset = Book.objects.all()
        serializer_class = BookSerializers
    
        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)
    

       我们大概可以看下这些类封装方法的实现过程,其中源码中的方法可以在GenericAPIView类找到

        def list(self, request, *args, **kwargs):
            #self.get_queryset()获取静态字段指定数据
            #filter_queryset支持配置文件筛选数据
            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)
            #get_serializer获取静态字段中指定的序列化类,并实例对象
            serializer = self.get_serializer(queryset, many=True)
            return Response(serializer.data)
    

       

      第三阶段  通用的基于类

      当然上面这个实现过程,在定义请求方法,还是有点重复,rest框架提供了一组已经混合好(minxed-in)的通用视图进一步封装

      generics.ListCreateAPIView 查看多条和添加视图

      generics.RetrieveUpdateDestroyAPIView  查看单条,修改和删除视图

    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
    
    class PublishViewSet(generics.ListCreateAPIView):
    
        queryset = Publish.objects.all()
        serializer_class = PublshSerializers
    
    class PublishDetailViewSet(generics.RetrieveUpdateDestroyAPIView):
        queryset = Publish.objects.all()
        serializer_class = PublshSerializers
    

       点进去一个类,发现它其实就是通过多继承组合了第二阶段中的类

    class ListCreateAPIView(mixins.ListModelMixin,
                            mixins.CreateModelMixin,
                            GenericAPIView):
        """
        Concrete view for listing a queryset or creating a model instance.
        """
        def get(self, request, *args, **kwargs):
            return self.list(request, *args, **kwargs)
    
        def post(self, request, *args, **kwargs):
            return self.create(request, *args, **kwargs)
    

       

      第四阶段 viewsets.ModelViewSet

      上面的代码已经够简洁了吧,它还可以简洁,你可以观察一下,就是在对所有的书操作和单本书操作,他们使用的数据和序列化模型的一样的,rest框架还做到这方面的简化,他们两本质的区别在于操作所有书是get,post方法,而单本就是get,put,delete方法,实现原理是在url上对请求方法进行映射

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

    from rest_framework.viewsets import ModelViewSet

    class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializers

       而ModelViewSet实现过程也是多继承

    class ModelViewSet(mixins.CreateModelMixin,
                       mixins.RetrieveModelMixin,
                       mixins.UpdateModelMixin,
                       mixins.DestroyModelMixin,
                       mixins.ListModelMixin,
                       GenericViewSet):
    
        pass
    

       那我们再看下,最后这种方式在请求到来时,是怎么执行的,从多继承中看,找as_view函数

      最终还是会找到ViewSetMixin下的as_view执行,把请求映射关系传给了actions

    def as_view(cls, actions=None, **initkwargs):
    

       传入到view函数中,闭包

    def view(request, *args, **kwargs):
        self = cls(**initkwargs)
        # We also store the mapping of request methods to actions,
        # so that we can later set the action attribute.
        # eg. `self.action = 'list'` on an incoming GET request.
        self.action_map = actions
    
        # Bind methods to actions
        # This is the bit that's different to a standard view
        for method, action in actions.items():
            handler = getattr(self, action)
            setattr(self, method, handler)
    
        if hasattr(self, 'get') and not hasattr(self, 'head'):
            self.head = self.get
    
        self.request = request
        self.args = args
        self.kwargs = kwargs
    
        # And continue as usual
        return self.dispatch(request, *args, **kwargs)
    

      当请求来时,执行闭包的view函数,在下列这段代码进行请求方法的映射

        for method, action in actions.items():
            #method = get,post
            #action=list,create
            handler = getattr(self, action)
            #handler==self.list,self.create函数
            setattr(self, method, handler)  #self.get-->self.list,  self.post-->self.create
    

      执行dispatch下这段代码时

                if request.method.lower() in self.http_method_names:
                    #get请求时,执行self.list....
                    handler = getattr(self, request.method.lower(),
                                      self.http_method_not_allowed)
    

    认证组件

      认证前提是需要登录的,所以你还需要写好登录

    models

    class User(models.Model):
        name = models.CharField(max_length=32)
        pwd = models.CharField(max_length=32)
    
        def __str__(self):
            return self.name
    
    class Token(models.Model):
        user = models.OneToOneField("User")
        token = models.CharField(max_length=128)
    
        def __str__(self):
            return self.token
    

     登录视图

    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 .models import User
    
    class LoginView(APIView):
    
        def post(self,request):
    
            name=request.data.get("name")
            pwd=request.data.get("pwd")
            user=User.objects.filter(name=name,pwd=pwd).first()
            res = {"state_code": 1000, "msg": None}
            if user:
    
                random_str=get_random_str(user.name)
                token=Token.objects.update_or_create(user=user,defaults={"token":random_str})
                res["token"]=random_str
            else:
                res["state_code"]=1001 #错误状态码
                res["msg"] = "用户名或者密码错误"
    
            import json
            return Response(json.dumps(res,ensure_ascii=False))
    

      首先你必须明确的是,认证,权限,频率那都是请求到来时的操作,大概都能猜到会在分发dispatch下执行,那我们就找到APIView下dispatch看下,下面这几句代码

      你会发现,在进入到认证..这些操作的时候,request已经是重新封装后的新的request

            request = self.initialize_request(request, *args, **kwargs)
            self.request = request
            self.headers = self.default_response_headers  # deprecate?
    
            try:
                #认证 权限 频率都在这里面做了
                self.initial(request, *args, **kwargs)
    

      在initial方法下,有这三句代码,分别对应认证,权限,频率组件

            self.perform_authentication(request)
            self.check_permissions(request)
            self.check_throttles(request)
    

      那我们就先看认证组件干了些什么事,一旦理解认证组件,其他的两个就好理解了,实现方式是类似的

      在perform_authentication认证方法里,就一句代码,你可能惊呼,这啥啊,就这么一句?给人感觉是字段啊,但深究下,如果是字段肯定实现不了认证功能,你需要大胆猜测是静态方法,是不是?我们去这个request下找下就知道了,注意:这里request是新的request

        def perform_authentication(self, request):
            """
            Perform authentication on the incoming request.
    
            Note that if you override this and simply 'pass', then authentication
            will instead be performed lazily, the first time either
            `request.user` or `request.auth` is accessed.
            """
            request.user
    

       也就是这个返回的request

    request = self.initialize_request(request, *args, **kwargs)
    

        而它返回的就是这个request对象

            return Request(
                request,
                parsers=self.get_parsers(),
                authenticators=self.get_authenticators(),
                negotiator=self.get_content_negotiator(),
                parser_context=parser_context
            )
    

       去它下面还真找到user静态方法

        @property
        def user(self):
            """
            Returns the user associated with the current request, as authenticated
            by the authentication classes provided to the request.
            """
            if not hasattr(self, '_user'):
                with wrap_attributeerrors():
                    self._authenticate()
            return self._user
    

       最后它会执行_authenticate方法

        def _authenticate(self):
            """
            Attempt to authenticate the request using each authentication instance
            in turn.
            """
            for authenticator in self.authenticators:
                try:
                    user_auth_tuple = authenticator.authenticate(self)
                except exceptions.APIException:
                    self._not_authenticated()
                    raise
    
                if user_auth_tuple is not None:
                    self._authenticator = authenticator
                    self.user, self.auth = user_auth_tuple
                    return
    
            self._not_authenticated()
    

       要想搞明白上面代码,就必须要知道self.authenticators是啥?

      在__init__方法里,有这么句,而authenticators在实例化对象传下来的

            self.authenticators = authenticators or ()
    

       实例化对象时,传入是下面这玩意,那它又干了啥呢?

    authenticators=self.get_authenticators(),
    

       下面self就是我们定义的视图类,它会去我们定义的类下面找authentication_classes,循环并实例化

        def get_authenticators(self):
            """
            Instantiates and returns the list of authenticators that this view can use.
            """
            return [auth() for auth in self.authentication_classes]
    

       authentication_classes又是些啥了?从名字上看,就是认证类,所以这个是由你来定义的,但是如果我们不定义的呢?

      当前类没有,就会找父类去中,一直找到APIView下

        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    

       会去api_settings下取个DEFAULT_AUTHENTICATION_CLASSES默认的认证类

    api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
    

       None,DEFAULTS对应APISettings下__init__方法的user_settings和defaults

      而DEFAULTS则是settings配置文件的变量,并配置这个,默认情况下就是这些认证类

        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework.authentication.SessionAuthentication',
            'rest_framework.authentication.BasicAuthentication'
        ),
    

       api_settings.DEFAULT_AUTHENTICATION_CLASSES会去执行APISettings的__getattr__方法

        def __getattr__(self, attr):
            if attr not in self.defaults:
                raise AttributeError("Invalid API setting: '%s'" % attr)
    
            try:
                # Check if present in user settings
                #self.user_settings这里调用时静态方法
                #attr=DEFAULT_AUTHENTICATION_CLASSES
                '''
                @property
                def user_settings(self):
                    #user_settings为None,所以这里hasattr(self, '_user_settings')为False
                    if not hasattr(self, '_user_settings'):
                        #尝试去settings配置文件找REST_FRAMEWORK这么个配置
                        self._user_settings = getattr(settings, 'REST_FRAMEWORK', {})
                    return self._user_settings
                '''
                #如果在settings找REST_FRAMEWORK配置就返回配置,没找到就是{}
                #没配置空字典获取DEFAULT_AUTHENTICATION_CLASSES就报错,走异常分支
                val = self.user_settings[attr]
            except KeyError:
                # Fall back to defaults
                '''
                去默认配置文件取到这两项
                'DEFAULT_AUTHENTICATION_CLASSES': (
                    'rest_framework.authentication.SessionAuthentication',
                    'rest_framework.authentication.BasicAuthentication'
                ),
                '''
                val = self.defaults[attr]
    
            # Coerce import strings into classes
            if attr in self.import_strings:
                val = perform_import(val, attr)
    
            # Cache the result
            self._cached_attrs.add(attr)
            setattr(self, attr, val)
            return val
    

       所以我们可以得出

        如果想局部配置认证类,在我们定义视图类下通过authentication_classes指定(列表)

        如果想全局在settings下配置

    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.TokenAuth",]
    }
    

       回到request下user属性方法下的_authenticate方法

        def _authenticate(self):
            """
            Attempt to authenticate the request using each authentication instance
            in turn.
            """
            for authenticator in self.authenticators:
                #authenticator  认证类实例
                try:
                    #调用认证类实例下的authenticate方法,传入了self,也就是request
                    #所以你认证类要有authenticate方法,返回值为元组
                    user_auth_tuple = authenticator.authenticate(self)
                except exceptions.APIException:
                    self._not_authenticated()
                    raise
    
                if user_auth_tuple is not None:
                    self._authenticator = authenticator
                    #元组内容 用户信息  和 auth信息,我们用的token
                    self.user, self.auth = user_auth_tuple
                    return
    

     认证类

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

     视图类

    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})
    

    权限组件

      了解认证组件的源码后,下面这两个组件就不带看了,直接看怎么用吧

      权限类里重写has_permission方法

    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
    

       视图类里通过permission_classe指定权限类

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

       全局配置

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

    访问频率组件

      依据什么来判定它的访问频率,IP?访问相关的信息你都可以从request.META中获取

      频率类里定义allow_request方法

    from rest_framework.throttling import BaseThrottle
    
    class VisitThrottle(BaseThrottle):
        '''
        一分钟内访问超过5次  禁用两分钟
        '''
    
        VISIT_RECORD = {}
    
        def allow_request(self, request, view):
            remote_addr = request.META.get('REMOTE_ADDR')
            print(remote_addr)
            now_time = time.time()
    
            if remote_addr not in VisitThrottle.VISIT_RECORD:
                VisitThrottle.VISIT_RECORD[remote_addr] = {
                    'start_time' : [now_time,],
                    'forbid' : False
                }
                return True
    
            start_time = VisitThrottle.VISIT_RECORD[remote_addr]['start_time']
            forbid_state = VisitThrottle.VISIT_RECORD[remote_addr]['forbid']
            while start_time and start_time[-1] < now_time - 60 and not forbid_state:
                start_time.pop()
    
            if forbid_state:
                if now_time - start_time[-1] > 120:
                    print('两分钟过了,解禁')
                    VisitThrottle.VISIT_RECORD[remote_addr] = {
                        'start_time': [now_time, ],
                        'forbid': False
                    }
                    print(VisitThrottle.VISIT_RECORD[remote_addr])
                    return True
                else:
                    print("两分钟内禁止访问,已经过了%s秒"%(now_time - start_time[-1]))
                    return False
            else:
                if len(start_time) < 5:
                    print("访问%s次"%(len(start_time) + 1))
                    print(start_time)
                    start_time.insert(0, now_time)
                    return True
                else:
                    print("访问%s次,禁止访问"%len(start_time))
                    VisitThrottle.VISIT_RECORD[remote_addr] = {
                        'start_time': [now_time, ],
                        'forbid': True
                    }
                    return False
    

       视图类中通过throttle_classes指定频率类

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

       全局配置

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

     内置玩法

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

     settings

    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",
        }
    }
    

     解析器和响应器

      上面已经对django和restframework的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',]
    }
    

       响应器主要是Response返回的内容,它会帮你生成展示数据的页面,页面支持你定义好的请求方法,如果你不想看它给页面,也可以通过?format=json查看

    分页组件

      分两种情况,一种是原生get,另外一种就是钩子映射到list方法,所以你想如果想进行分页,可以这么做

      重写get或list方法

    from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination
    
      def get(self,request):
            book_list=Book.objects.all()
            pp=PageNumberPagination()
            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)
    

      当然上面这个过程是没有给一页显示多少的,而且分页内部没有默认值,它会去settings下取PAGE_SIZE,所以你好配置这个

    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.TokenAuth",]
        "PAGE_SIZE":2
    }
    

       当然这个是全局的,如果你想局部,你可以这么干,自定义分页类,下面实例过程就换成你定义这个类

    pp=PageNumberPagination()
    

       自定义分页类

    class PNPagination(PageNumberPagination):
            page_size = 1
            page_query_param = 'page'  #url分页参数名
            page_size_query_param = "size"  #url每页数参数名
            max_page_size = 5  #当?page=1&size=6 size超过5时就不生效
    

      针对钩子映射的,还可以这么做,通过pagination_class指定分页器,当然指定可以不用重写list方法了,因为在list方法的源码里,会自动获取这个指定的分液器

    class AuthorModelView(viewsets.ModelViewSet):
        pagination_class = PNPagination
        authentication_classes = [TokenAuth,]
        permission_classes = []
        throttle_classes = []# 限制某个IP每分钟访问次数不能超过20次
        queryset = Author.objects.all()
        serializer_class = AuthorModelSerializers
    

      对LimitOffsetPagination偏移分页,自定义类时  default_limit指定限制多少,url里   offset参数指定偏移量

    restframe

  • 相关阅读:
    centos7 启动mongodb时报错ERROR: child process failed, exited with error number 1
    liunxcentos7下 跟目录空间不足docker load镜像报错空间不足
    centos7下初始化硬盘挂载目录
    Jenkins打包出错
    CentOS 7 安装 Percona XtraDB Cluster 5.7
    Etcd集群搭建(证书通信)
    centos7下prometheus+grafana监控
    nginx代理
    装Centos7系统
    Dockerfile常用指令使用案例
  • 原文地址:https://www.cnblogs.com/xinsiwei18/p/9742391.html
Copyright © 2020-2023  润新知