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

posted @ 2018-10-17 10:47  财经知识狂魔  阅读(882)  评论(0编辑  收藏  举报