• Django DRF 详解


    Django DRF

    目录

    1,RESTful API规范

    REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征性状态转移)。 它首次出现在2000年Roy Fielding的博士论文中。

    RESTful是一种定义Web API接口的设计风格,尤其适用于前后端分离的应用模式中。

    这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。

    事实上,我们可以使用任何一个框架都可以实现符合restful规范的API接口。

    1.1 数据的安全保障

    • url链接一般都采用https协议进行传输

      注:采用https协议,可以提高数据交互过程中的安全性

    1.2 接口特征表现

    1.3 多数据版本共存

    1.4 数据即是资源,均使用名词(可复数)

    1.5 资源操作由请求方式决定(method)

    1.6 过滤,通过在url上传参的形式传递搜索条件

    1.7 响应状态码

    1.7.1 正常响应

    • 响应状态码2xx
      • 200:常规请求
      • 201:创建成功

    1.7.2 重定向响应

    • 响应状态码3xx
      • 301:永久重定向
      • 302:暂时重定向

    1.7.3 客户端异常

    • 响应状态码4xx
      • 403:请求无权限
      • 404:请求路径不存在
      • 405:请求方法不存在

    1.7.4 服务器异常

    • 响应状态码5xx
      • 500:服务器异常

    1.8 错误处理,应返回错误信息,error当做key

    {
        error: "无权限操作"
    }
    

    1.9 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范

    GET /collection:返回资源对象的列表(数组)
    GET /collection/resource:返回单个资源对象
    POST /collection:返回新生成的资源对象
    PUT /collection/resource:返回完整的资源对象
    PATCH /collection/resource:返回完整的资源对象
    DELETE /collection/resource:返回一个空文档
    

    1.10 需要url请求的资源需要访问资源的请求链接

    # Hypermedia API,RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么
    {
      	"status": 0,
      	"msg": "ok",
      	"results":[
            {
                "name":"肯德基(罗餐厅)",
                "img": "https://image.baidu.com/kfc/001.png"
            }
          	...
    		]
    }
    

    比较好的接口返回

    # 响应数据要有状态码、状态信息以及数据本身
    {
      	"status": 0,
      	"msg": "ok",
      	"results":[
            {
                "name":"肯德基(罗餐厅)",
                "location":{
                    "lat":31.415354,
                    "lng":121.357339
                },
                "address":"月罗路2380号",
                "province":"上海市",
                "city":"上海市",
                "area":"宝山区",
                "street_id":"339ed41ae1d6dc320a5cb37c",
                "telephone":"(021)56761006",
                "detail":1,
                "uid":"339ed41ae1d6dc320a5cb37c"
            }
          	...
    		]
    }
    

    2 序列化器

    作用:

    1. 序列化,序列化器会把模型对象转换成字典,经过response以后变成json字符串
    2. 反序列化,把客户端发送过来的数据,经过request以后变成字典,序列化器可以把字典转成模型
    3. 反序列化,完成数据校验功能
    

    2.1 继承Serializer类的序列化器

    from rest_framework import serializers
    
    # 声明序列化器,所有的序列化器都要直接或者间接继承于 Serializer
    class StudentSerializer(serializers.Serializer):
        """学生信息序列化器"""
        # 1. 需要进行数据转换的字段
        id = serializers.IntegerField()
        name = serializers.CharField()
        age = serializers.IntegerField()
        sex = serializers.BooleanField()
        description = serializers.CharField()
    
        # 2. 如果序列化器继承的是ModelSerializer,则需要声明调用的模型信息
       
    
        # 3. 验证代码
    
        # 4. 编写添加和更新模型的代码
    

    注意:serializer不是只能为数据库模型类定义,也可以为非数据库模型类的数据定义。serializer是独立于数据库之外的存在。

    2.2 继承与ModelSerializer的序列化器

    ModelsSerializer与常规的Serializer相同,但提供了额外功能:
    
    基于模型类自动生成一系列字段
    基于模型类自动为Serializer生成Validators,校验器
    包含默认的create()和update()实现。自动操作表,创建和更新表对象
    
    1.我们创建一个BookInfoSerializer
    
    class BookInfoSerializer(serializers.ModelSerializer):
        """图书数据序列化器"""
        class Meta:
            model = BookInfo
            fields = '__all__'
    model指明参照哪个模型类
    fields指明为模型类的哪些字段生成
    
    
    2.指定字段
    2.1)使用字段来明确字段,__all__表名包含所有字段,也可以写明具体哪些字段,如
    
        class BookInfoSerializer(serializers.ModelSerializer):
            """图书数据序列化器"""
            class Meta:
                model = BookInfo
                fields = ('id', 'btitle', 'bpub_date')
    
        #fields作用详解:
            fields内可以填入的不仅仅只有model的字段,还可以是model中定义的方法,序列化类中定义的方法和字段
            -表模型对应的字段:直接序列化字段的内容,若是外键字段返回一个对象,需要在表模型中定义函数
            -model中定义的方法:序列化方法的结果,一般用来定制外键字段输出的信息,
            -
            -
            注意:fields中不在model表中的字段都需要设置为只读!
        
         
    2.2)使用排除可以明确排除掉哪些字段
    
        class BookInfoSerializer(serializers.ModelSerializer):
            """图书数据序列化器"""
            class Meta:
                model = BookInfo
                exclude = ('image',)
    2.3)默认ModelSerializer使用主键作为关联字段,但是我们可以使用depth来简单的生成嵌套表示,depth应该是整数,表明嵌套的层级数量。
    
        class HeroInfoSerializer2(serializers.ModelSerializer):
            class Meta:
                model = HeroInfo
                fields = '__all__'
                depth = 1
            
    2.4)显示指明字段,如:
    
        class HeroInfoSerializer(serializers.ModelSerializer):
            hbook = BookInfoSerializer()
    
            class Meta:
                model = HeroInfo
                fields = ('id', 'hname', 'hgender', 'hcomment', 'hbook')
            
    2.5)申明只读字段:read_only_fields
    
        class BookInfoSerializer(serializers.ModelSerializer):
            """图书数据序列化器"""
            class Meta:
                model = BookInfo
                fields = ('id', 'btitle', 'bpub_date', 'bread', 'bcomment')
                read_only_fields = ('id', 'bread', 'bcomment')
            
    3.添加额外参数 extra_kwargs
    	我们可以使用extra_kwargs参数为ModelSerializer添加或修改原有的选项参数
    
        class BookInfoSerializer(serializers.ModelSerializer):
            """图书数据序列化器"""
            class Meta:
                model = BookInfo
                fields = ('id', 'btitle', 'bpub_date', 'bread', 'bcomment')
                extra_kwargs = {
                    'bread': {'min_value': 0, 'required': True},
                    'bcomment': {'min_value': 0, 'required': True},
                }
    
    

    2.3 序列化器的使用

    序列化器的使用分两个阶段:

    1. 在客户端请求时,使用序列化器可以完成对数据的反序列化。
    2. 在服务器响应时,使用序列化器可以完成对数据的序列化。

    2.3.1基本使用

    1) 先查询出一个对象

    from students.models import Student
    
    student = Student.objects.get(id=3)
    

    2) 构造序列化器对象

    from .serializers import StudentSerializer
    
    serializer = StudentSerializer(instance=student)
    

    3)获取序列化数据

    通过data属性可以获取序列化后的数据,是一个字典

    serializer.data
    # {'id': 4, 'name': '小张', 'age': 18, 'sex': True, 'description': '猴赛雷'}
    

    说明

    1)用于序列化时,只传一个instance参数,默认第一个参数

    2)用于反序列化时,只传入data参数

    1. 同时传入 instance,data字段进行修改操作

    4)如果要被序列化的是包含多条数据的查询集QuerySet,可以通过添加many=True参数补充说明

    5)除了instance和data参数外,在构造Serializer对象时,还可通过context参数额外添加数据,如

    serializer = AccountSerializer(account, context={'request': request})
    

    通过context参数附加的数据,可以通过Serializer对象的context属性获取,这样一些与请求有关的逻辑判断可以再序列化类里操作了

    视图代码:

    from django.views import View
    from students.models import Student
    from .serializers import StudentSerializer
    from django.http.response import JsonResponse
    class StudentView(View):
        """使用序列化器序列化转换单个模型数据"""
        def get(self,request,pk):
            # 获取数据
            student = Student.objects.get(pk=pk)
            # 数据转换[序列化过程]
            serializer = StudentSerializer(instance=student)
            print(serializer.data)
            # 响应数据
            return JsonResponse(serializer.data)
    
    """使用序列化器序列化转换多个模型数据"""
    def get(self,request):
        # 获取数据
        student_list = Student.objects.all()
    
        # 转换数据[序列化过程]
        # 如果转换多个模型对象数据,则需要加上many=True
        serializer = StudentSerializer(instance=student_list,many=True)
        print( serializer.data ) # 序列化器转换后的数据
    
        # 响应数据给客户端
        # 返回的json数据,如果是列表,则需要声明safe=False
        return JsonResponse(serializer.data,safe=False)
    
    

    2.3.2 高级用法 source

    3,请求,响应,异常

    3.1 DRF Request

    from rest_framework.request import Request
    

    REST framework 传入视图的request对象不再是Django默认的HttpRequest对象,而是REST framework提供的扩展了HttpRequest类的Request类的对象。

    REST framework 提供了Parser解析器,在接收到请求后会自动根据Content-Type指明的请求数据类型(如JSON、表单等)将请求数据进行parse解析,解析为类字典[QueryDict]对象保存到Request对象中。

    Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。

    无论前端发送的哪种格式的数据,我们都可以以统一的方式读取数据。

    Request常用属性

    1).data

    request.data 返回解析之后的请求体数据。类似于Django中标准的request.POSTrequest.FILES属性,但提供如下特性:

    • 包含了解析之后的文件和非文件数据
    • 包含了对POST、PUT、PATCH请求方式解析后的数据
    • 利用了REST framework的parsers解析器,不仅支持表单类型数据,也支持JSON数据

    2).query_params

    request.query_params与Django标准的request.GET相同,只是更换了更正确的名称而已。

    3).请求头的数据

    ip地址和token都在http的请求头里

    request.META.get('HTTP_REMOTE_ADDR') 取IP地址

    request.META.get('HTTP_AUTHORIZATION') 取token

    补充:

    Http请求版本号

    3.2 DRF Response

    from rest_framework.response import Response
    

    REST framework提供了一个响应类Response,使用该类构造响应对象时,响应的具体数据内容会被转换(render渲染)成符合前端需求的类型。

    REST framework提供了Renderer 渲染器,用来根据请求头中的Accept(接收数据类型声明)来自动转换响应数据到对应格式。如果前端请求中未进行Accept声明,则会采用默认方式处理响应数据,我们可以通过配置来修改默认响应格式。

    可以在rest_framework.settings查找所有的drf默认配置项

    REST_FRAMEWORK = {
        'DEFAULT_RENDERER_CLASSES': (  # 默认响应渲染类
            'rest_framework.renderers.JSONRenderer',  # json渲染器
            'rest_framework.renderers.BrowsableAPIRenderer',  # 浏览API渲染器
        )
    }
    

    3.2.1 使用

    Response(data, status=None, template_name=None, headers=None, content_type=None)
    

    data数据不要是render处理之后的数据,只需传递python的内建类型数据即可,REST framework会使用renderer渲染器处理data

    data不能是复杂结构的数据,如Django的模型类对象,对于这样的数据我们可以使用Serializer序列化器序列化处理后(转为了Python字典类型)再传递给data参数。

    参数说明:

    • data: 前段要展示的数据,def的data格式不友好
    • status: 状态码,给浏览器识别的,浏览器会更据改状态码做出反应,默认200;
    • template_name: 模板名称,如果使用HTMLRenderer 时需指明;
    • headers: 用于存放响应头信息的字典;
    • content_type: 响应数据的Content-Type,通常此参数无需传递,REST framework会根据前端所需类型数据来设置该参数。

    3.2.1 Response的常用属性

    1).data

    传给response对象的序列化后,但尚未render处理的数据

    2).status_code

    状态码的数字

    3).content

    经过render处理后的响应数据

    3.2.3 状态码

    为了方便设置状态码,REST framewrok在rest_framework.status模块中提供了常用状态码常量。

    1)信息告知 - 1xx

    HTTP_100_CONTINUE
    HTTP_101_SWITCHING_PROTOCOLS
    

    2)成功 - 2xx

    HTTP_200_OK
    HTTP_201_CREATED
    HTTP_202_ACCEPTED
    HTTP_203_NON_AUTHORITATIVE_INFORMATION
    HTTP_204_NO_CONTENT
    HTTP_205_RESET_CONTENT
    HTTP_206_PARTIAL_CONTENT
    HTTP_207_MULTI_STATUS
    

    3)重定向 - 3xx

    HTTP_300_MULTIPLE_CHOICES
    HTTP_301_MOVED_PERMANENTLY
    HTTP_302_FOUND
    HTTP_303_SEE_OTHER
    HTTP_304_NOT_MODIFIED
    HTTP_305_USE_PROXY
    HTTP_306_RESERVED
    HTTP_307_TEMPORARY_REDIRECT
    

    4)客户端错误 - 4xx

    HTTP_400_BAD_REQUEST
    HTTP_401_UNAUTHORIZED
    HTTP_402_PAYMENT_REQUIRED
    HTTP_403_FORBIDDEN
    HTTP_404_NOT_FOUND
    HTTP_405_METHOD_NOT_ALLOWED
    HTTP_406_NOT_ACCEPTABLE
    HTTP_407_PROXY_AUTHENTICATION_REQUIRED
    HTTP_408_REQUEST_TIMEOUT
    HTTP_409_CONFLICT
    HTTP_410_GONE
    HTTP_411_LENGTH_REQUIRED
    HTTP_412_PRECONDITION_FAILED
    HTTP_413_REQUEST_ENTITY_TOO_LARGE
    HTTP_414_REQUEST_URI_TOO_LONG
    HTTP_415_UNSUPPORTED_MEDIA_TYPE
    HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE
    HTTP_417_EXPECTATION_FAILED
    HTTP_422_UNPROCESSABLE_ENTITY
    HTTP_423_LOCKED
    HTTP_424_FAILED_DEPENDENCY
    HTTP_428_PRECONDITION_REQUIRED
    HTTP_429_TOO_MANY_REQUESTS
    HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE
    HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS
    

    5)服务器错误 - 5xx

    HTTP_500_INTERNAL_SERVER_ERROR
    HTTP_501_NOT_IMPLEMENTED
    HTTP_502_BAD_GATEWAY
    HTTP_503_SERVICE_UNAVAILABLE
    HTTP_504_GATEWAY_TIMEOUT
    HTTP_505_HTTP_VERSION_NOT_SUPPORTED
    HTTP_507_INSUFFICIENT_STORAGE
    HTTP_511_NETWORK_AUTHENTICATION_REQUIRED
    

    3.2.4 自定义Response

    class APIResponse(Response):
        def __init__(self,code=100,msg='成功',data=None,status=None,headers=None,**kwargs):
            dic = {'code': code, 'msg': msg}
            if  data:
                dic = {'code': code, 'msg': msg,'data':data}
            dic.update(kwargs)
            super().__init__(data=dic, status=status,headers=headers)
            
            
    # 自己封装的参数,code,msg,data都会被放进一个字典中,然后将该字典打包传个drf的Response的data      
    # code是指自定义的状态码,为了说明用户的操作结果,浏览器不会根据它做出反应,是写在data里的
    # status是服务器处理状态码,浏览器会根据该状态码做出反应,一般不需要手动填写
    
    

    3.3自定义异常处理

    作用:

    1,没权限等异常,程序内部异常,也输出json格式

    2,记录日志

    前后端分离的项目,如果出现异常(程序出现问题了),drf能处理的异常有限,交由Django处理的异常返回的不是json格式的字符串,我们需要拦截这些异常,并格式化异常的范厂长格式.

    默认的异常处理是:

    这是drf的settings的配置,若要更改默认配置需要在项目的settings里重新配置.

    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    

    源码:

    from rest_framework.views import exception_handler
    def exception_handler(exc, context):
        if isinstance(exc, Http404):
            exc = exceptions.NotFound()
        elif isinstance(exc, PermissionDenied):
            exc = exceptions.PermissionDenied()
    
        if isinstance(exc, exceptions.APIException):
            headers = {}
            if getattr(exc, 'auth_header', None):
                headers['WWW-Authenticate'] = exc.auth_header
            if getattr(exc, 'wait', None):
                headers['Retry-After'] = '%d' % exc.wait
    
            if isinstance(exc.detail, (list, dict)):
                data = exc.detail
            else:
                data = {'detail': exc.detail}
    
            set_rollback()
            return Response(data, status=exc.status_code, headers=headers)
    
        return None
    

    自定义异常

    # 自定义异常处理的方法
    from rest_framework.views import exception_handler
    from rest_framework.response import APIResponse  # 自定义包装的response
    from rest_framework import status
    
    def my_exception_handler(exc, context):
        response=exception_handler(exc, context)
        # 两种情况,一个是None,drf没有处理
        #response对象,django处理了,但是处理的不符合咱们的要求
    
        if not response: #drf没处理的情况,还可以更细力度的捕获异常
            if isinstance(exc, ZeroDivisionError):
                return APIResponse(data={'status': 777, 'msg': "除以0的错误" + str(exc)}, status=status.HTTP_400_BAD_REQUEST)
            return APIResponse(data={'status':999,'msg':str(exc)},status=status.HTTP_400_BAD_REQUEST)
        
        
        else:  # drf处理的异常,我们包装一下格式
    
            return APIResponse(data={'status':888,'msg':response.data.get('detail')},status=status.HTTP_400_BAD_REQUEST)
        
    # 全局配置setting.py
    'EXCEPTION_HANDLER': 'app01.app_auth.my_exception_handler',
    

    返回与异常模板

    from rest_framework.response import Response
    from rest_framework.views import exception_handler
    from rest_framework import status
    
    # 自定义返回与异常捕获
    class APIResponse(Response):
        def __init__(self,code=100,msg='成功',data=None,status=None,headers=None,**kwargs):
            dic = {'code': code, 'msg': msg}
            if  data:
                dic = {'code': code, 'msg': msg,'data':data}
            dic.update(kwargs)
            super().__init__(data=dic, status=status,headers=headers)
    
    
    def my_exception_handler(exc, context):
        """
        使用前先全局配置
        """
        response = exception_handler(exc, context)
    
        # drf没处理的情况,自己捕获异常并包装格式
        if not response:  
            if isinstance(exc, ZeroDivisionError):
                return APIResponse(data={'status': 777, 'msg': "除以0的错误" + str(exc)}, status=status.HTTP_400_BAD_REQUEST)
            return APIResponse(data={'status': 999, 'msg': str(exc)}, status=status.HTTP_400_BAD_REQUEST)
    
    
        else:  # drf处理的异常,包装一下格式
            return APIResponse(data={'status': 888, 'msg': response.data.get('detail')}, status=status.HTTP_400_BAD_REQUEST)
    

    4,视图类集合,路由

    4.1 基于APIView写接口:

    1)手动写orm语句

    book_list=Book.objects.all()
    

    2)手动调用序列化器

    book_ser=BookSerializer(book_list,many=True)
    

    3)手动写业务逻辑处理不同的请求

    def get(self,request):
    
    def post(self,request):
    

    4,视图的类需要写两个,分别对应url的有名分组传参和不传参情况.

    url(r'^api/books2/$', views.Books2.as_view()),
    url(r'^api/book2/(?P<num>d+)/', views.Book2.as_view())
    
    class BookView(APIView):
        def get(self,request):
            book_list=Book.objects.all()
            book_ser=BookSerializer(book_list,many=True)
    
            return Response(book_ser.data)
        def post(self,request):
            book_ser = BookSerializer(data=request.data)
            if book_ser.is_valid():
                book_ser.save()
                return Response(book_ser.data)
            else:
                return Response({'status':101,'msg':'校验失败'})
    
    
    class BookDetailView(APIView):
        def get(self, request,pk):
            book = Book.objects.all().filter(pk=pk).first()
            book_ser = BookSerializer(book)
            return Response(book_ser.data)
    
        def put(self, request,pk):
            book = Book.objects.all().filter(pk=pk).first()
            book_ser = BookSerializer(instance=book,data=request.data)
            if book_ser.is_valid():
                book_ser.save()
                return Response(book_ser.data)
            else:
                return Response({'status': 101, 'msg': '校验失败'})
    
        def delete(self,request,pk):
            ret=Book.objects.filter(pk=pk).delete()
            return Response({'status': 100, 'msg': '删除成功'})
    

    下面的类分别解简化了上述步骤大量减少代码的书写:

    4.2 基于GenericAPIView写的接口:

    简化步骤,在类里直接申明了操作哪张表,调用哪个序列化器:

    queryset = models.Booklib.objects
    serializer_class = BookModelSerializer
    

    请求的函数调用申明的列表和序列化器会自动传入对象

    book=self.get_queryset().filter(pk=num).first()
    ser_obj=self.get_serializer(book)
    

    下面分别对应5种方法的注意事项:

    • queryset只有一个对象时,需要用.first()取到
    • 反序列化数据化后要判断数据是否合法,调用序列化器的save方法
    • 多条数据序列化用many=True
    class Books2(GenericAPIView):
    
        queryset = models.Booklib.objects.all()
        serializer_class = BookModelSerializer
    
        def get(self,request):
    
            book=self.get_queryset()
            book_ser=self.get_serializer(book,many=True)
    
            return CommomResponse(data=book_ser.data,headers={'key':'value'})
    
    
        def post(self,request):#创建,修改了id为只读属性,创建时不需要id参数
            book_ser=self.get_serializer(data=request.data)
    
            if book_ser.is_valid():
                book_ser.save()
    
                return CommomResponse(data=book_ser.data)
            else:
    
                return CommomResponse(101,'数据不和法',book_ser.errors)
    
    class Book2(GenericAPIView):
        queryset = models.Booklib.objects
        serializer_class = ser.BookModelSerializer
    
        def get(self,request,num):
            book=self.get_queryset().filter(pk=num).first()
            ser_obj=self.get_serializer(book)
    
            return CommomResponse(data=ser_obj.data)
    
    
        def delete(self,request,num):
            book=self.get_queryset().filter(pk=num).delete()
    
            return CommomResponse(msg='删除成功')
    
        def put(self,request,num):
            book=self.get_queryset().filter(pk=num).first()#只有一个值时一定有用.first()
            ser_obj=self.get_serializer(book,request.data)
            if ser_obj.is_valid():
                ser_obj.save()  #这是序列化器的save方法,不是queryset的
                return CommomResponse(data=ser_obj.data)
            else:
                return CommomResponse(msg='数据不合法')
    

    4.3 基于GenericAPIView和5个视图扩展类写的接口

    5个视图扩展类:直接继承object的类,这些类能自动能更具请求方式相应

    ListModelMixin:获取所有数据

    RetrieveModelMixin:获取一条数据

    CreateModelMixin:创建

    UpdateModelMixin:更新

    DestroyModelMixin:删除

    使用方法:在相应的请求函数下调用对应的函数,有参数的还得传参:

    class Books3(GenericAPIView,ListModelMixin,CreateModelMixin):
        queryset = models.Booklib.objects
        serializer_class = ser.BookModelSerializer
    
        def get(self,request):
            return self.list(request)
        def post(self,request):
            return self.create(request)
    
    
    class Book3(GenericAPIView,RetrieveModelMixin,DestroyModelMixin,UpdateModelMixin):
        queryset = models.Booklib.objects
        serializer_class = ser.BookModelSerializer
    
        def get(self,request,pk):
            return self.retrieve(request,pk)
    
        def put(self,request,pk):
            return self.update(request,pk)
    
        def delete(self,request,pk):
            return self.destroy(request,pk)
    

    2.4 基于GenericAPIView的9个视图拓展类编写接口:

    分别是:

    5个基础组合类
    CreateAPIView,ListAPIView,RetrieveAPIView,DestroyAPIView,UpdateAPIView 
    4个扩展组合类
    ListCreateAPIView,RetrieveUpdateAPIView,RetrieveDestroyAPIView,RetrieveUpdateDestroyAPIView 
    

    这9个类集合了API类和ModelMixin类的优点,同时又更进一步,类体写了请求对应的操作,继承这个类之后只需要申明操作票那个表和指定那个序列化器.

    使用方法:只需要这6行代码实现面的功能

    class Books4(ListCreateAPIView):
        queryset = models.Booklib.objects 
        #这里需要一个queryse对像所以要去到object
        serializer_class = ser.BookModelSerializer
    
    class Book4(RetrieveUpdateDestroyAPIView):
        queryset = models.Booklib.objects
        serializer_class = ser.BookModelSerializer
    

    4.5继承ViewSetMixin的视图类

    作用:只需要编写一个视图类来对应两个url

    该类重写了的as_view功能匹配了请求的method和url视图层action,并且调用函数

    注意:因为ViewSetMixin重写了as_view,查找as_view时会现去继承的第一父类查找,如果APIView在前则,不会执行重写的as_view导致功能失效.

    当然这里的APIView也可以是GenericAPIView

    class Book05(ViewSetMixin,APIView):
        def get_one_book(self,request,pk):
    
            obj_list = models.Booklib.objects.filter(pk=pk).first()
            ser_book = ser.BookModelSerializer(obj_list)
    
            return CommomResponse(data=ser_book.data)
    
        def get_books(self,request):
            obj_list = models.Booklib.objects.all()
            ser_book=ser.BookModelSerializer(obj_list,many=True)
    
            return CommomResponse(data=ser_book.data)
        
    
        #路由层可以使用actions参数
    
       url(r'^api/books5/$', views.Book05.as_view(actions={'get':'get_books'})),
       url(r'^api/book5/(?P<pk>d+)/', views.Book05.as_view(actions={'get':'get_one_book'})),
    

    4.6 基于ModelViewSet类编写接口

    ModelViewSet的父类:

    GenericViewSet 5个视图拓展类(这5个类都直接继承object)

    GenericViewSet继承了GenericAPIView和ViewSetMixin(该类直接继承object)

    GenericAPIView的继承关系就比较清晰了:

    GenericAPIView>>>APIView>>>View>>>object

    请求与函数的对应:

    get-->list 获取多个

    get-->retrieve 获取一个

    post-->create

    put-->update

    delete-->delete

    class Book06(ModelViewSet):
        queryset = models.Booklib.objects
        serializer_class = BookModelSerializer
    
        def list(self, request, *args, **kwargs):
            queryset = self.get_queryset()
    
            serializer = self.get_serializer(queryset, many=True)
            return Response(serializer.data[0:2])#此处使得获取所有数据的请求改为只获取前两个数据,这是通过重写list方法实现的
    
        @action(['GET'], False) #通过action装饰器自动生成list_3对应的url,False即不接受参数
        def list_3(self, request):
            pass
    

    4.7 自动生成路由

    使用步骤:

    第一步:导入routers模块

    第二步:有两个类,实例化得到对象

    第三步:调用对象的注册功能

    第四部:自动生成的路由,加入到原路由中

    # 1 在urls.py中配置
        path('books4/', views.Book4View.as_view()),
        re_path('books4/(?P<pk>d+)', views.Book4DetailView.as_view()),
    # 2 一旦视图类,继承了ViewSetMixin,路由
    	 path('books5/', views.Book5View.as_view(actions={'get':'list','post':'create'})), #当路径匹配,又是get请求,会执行Book5View的list方法
        re_path('books5/(?P<pk>d+)', views.Book5View.as_view(actions={'get':'retrieve','put':'update','delete':'destroy'})),
     
    # 3 继承自视图类,ModelViewSet的路由写法(自动生成路由)
    	-urls.py
            # 第一步:导入routers模块
            from rest_framework import routers
            # 第二步:有两个类,实例化得到对象
            # routers.DefaultRouter 生成的路由更多
            # routers.SimpleRouter
            router=routers.DefaultRouter()
            # 第三步:调用对象的注册功能
            # router.register('前缀','继承自ModelViewSet视图类','别名')
            router.register('books',views.BookViewSet) # 不要加斜杠了
    
            # 第四步
            # router.urls # 自动生成的路由,加入到原路由中
            # print(router.urls)
            # urlpatterns+=router.urls
            '''
    	-views.py
    		from rest_framework.viewsets import ModelViewSet
            from app01.models import Book
            from app01.ser import BookSerializer
            class BookViewSet(ModelViewSet):
                queryset =Book.objects
                serializer_class = BookSerializer
    

    2.7.1 action的使用

    作用:个视图类中自定义的方法也自动生成对应的路由

    第一步:from rest_framework import decorators

    第二步:第一个参数传入列表,列表中写请求方式['GET','POST'],第二个参数传布尔值,需要携带参数就写True

    # action干什么用?为了给继承自ModelViewSet的视图类中定义的函数也添加路由使用
    class BookViewSet(ModelViewSet):
        queryset =Book.objects.all()
        serializer_class = BookSerializer
        # methods第一个参数,传一个列表,列表中放请求方式,
        # ^books/get_1/$ [name='book-get-1'] 当向这个地址发送get请求,会执行下面的函数
        # detail:布尔类型 如果是True
        #^books/(?P<pk>[^/.]+)/get_1/$ [name='book-get-1']
        @action(methods=,detail=True)
        def get_1(self,request,pk):
            print(pk)
            book=self.get_queryset()[:2]  # 从0开始截取一条
            ser=self.get_serializer(book,many=True)
            return Response(ser.data)
        
    # 装饰器,放在被装饰的函数上方,method:请求方式,detail:是否带pk
    

    5,认证,权限,频率

    5.1 认证

    简单来说就是:认证确定了你是谁

    一般使用drf提供的,基于密码的认证,基于session的认证,基于token的认证

    5.1.1自定义认证组件的使用:

    1. 写一个类继承BaseAuthentication,重写authenticate方法.认证通过返回user对象,否则抛一个异常

    2. 在需要认证的视图类里配置 authentication_classes=[认证类1,认证类2...]

    全局使用,在setting.py中配置

    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.app_auth.MyAuthentication",]
    }
    

    局部使用,写在视图类里,可以有多个认证,从左到右依次执行

    authentication_classes=[MyAuthentication]
    

    局部禁用

    authentication_classes=[]
    

    REST_FRAMEWORK={ },依次往里添加全局配置,所有与drf有关的全局配置都写在这里

    # 写一个认证类 app_auth.py
    from rest_framework.authentication import BaseAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    from app01.models import UserToken
    class MyAuthentication(BaseAuthentication):
        def authenticate(self, request):
            # 认证逻辑,如果认证通过,返回两个值
            #如果认证失败,抛出AuthenticationFailed异常
            token=request.GET.get('token')
            if  token:
                
                #如果用户携带了token直接以token为过滤条件筛选,有结果token就是对了
                #这样通过token这个对象也能找到用户对象了
                user_token=UserToken.objects.filter(token=token).first()
                
                # 认证通过
                if user_token:
                    return user_token.user,token
                #链表查询返回user对象,反向查询表名小写直接获得表对象
                else:
                    raise AuthenticationFailed('认证失败')
            else:
                raise AuthenticationFailed('请求地址中需要携带token')
    
    

    实现原理

    源码分析:
    路径:rest_framework-->views-->APIView-->as_view-->dispatch-->self.initial-->self.perform_authentication 调用了包装后的request类的user属性,其实是个方法
    转到rest_framework.request.user -->调用了self._authenticate方法
    
    核心源码:
    def _authenticate(self):
    
        for authenticator in self.authenticators: #获取认证类的对象
            try:
                user_auth_tuple = authenticator.authenticate(self) 
                #调用认证类对象的authenticate方法
                #该方法返回一个元祖或一个异常
            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()
    
    源码解析
    #1,self.authenticators的authenticators从哪来的,它是什么?
    self.authenticators说明是调用的对象属性,即Request类的属性-->Request接收了该参数,即由调用者传入的-->调用者是APIView的dispatch,它调用了#self.initialize_request实例化并包装了request对象,
        def initialize_request(self, request, *args, **kwargs):
            """
            Returns the initial request object.
            """
            parser_context = self.get_parser_context(request)
    
            return Request(
                request,
                parsers=self.get_parsers(),
                authenticators=self.get_authenticators(),
                negotiator=self.get_content_negotiator(),
                parser_context=parser_context
            )
    
    该方法的 self.get_authenticators
        def get_authenticators(self):
            return [auth() for auth in self.authentication_classes]
        
    当前在APIView类中,self.authentication_classes    self会依次去视图类的对象空间->继承了APIView的子类的空间->APIView类的空间->项目settings的空间中查找authentication_classes的值,值就是一个个认证类.所以, #authenticators是一个个认证类产生的对象
    
    #2,重写的authenticate方法需要返回什么结果?
    认证类的authenticate方法,认证通过返回一个元祖包含两个值,认证失败抛一个异常
    认证通过:
       self.user, self.auth = user_auth_tuple 返回的第一个值会赋值个self.user,当前在Request类里,所以这两个值都给了request对象,即以后的request就有了这两个属性
           
    认证失败:
        from rest_framework.exceptions import APIException,AuthenticationFailed
        抛一个AuthenticationFailed异常会被捕获
    

    5.1.2配合Auth模块认证组件的使用:

    
    

    5.2 权限

    权限需要和认证配合使用

    权限确定登录用户能不能访问某个接口,没登录的直接pass掉

    5.2.1自定义权限组件的使用

    1)写一个类,继承BasePermission,重写has_permission,如果权限通过,就返回True,不通过就返回False

    1. 使用, 在需要认证的视图类里配置 permission_classes =[权限类1,权限类2...]

    局部使用

    class TestView(APIView):
        permission_classes = [app_auth.UserPermission]
    

    全局使用

    REST_FRAMEWORK={
        "DEFAULT_AUTHENTICATION_CLASSES":["app01.app_auth.MyAuthentication",],
        'DEFAULT_PERMISSION_CLASSES': ['app01.app_auth.UserPermission',],
    } 
    

    局部禁用

    class TestView(APIView):
        permission_classes = []
    
     
    from rest_framework.permissions import BasePermission
    
    class UserPermission(BasePermission):
        #重写has_permission,如果权限通过,就返回True,不通过就返回False
        def  has_permission(self, request, view):
            # 不是超级用户,不能访问
            # 由于认证已经过了,request内就有user对象了,当前登录用户
            user=request.user  # 当前登录用户
            
            # 如果该字段用了choice,通过get_字段名_display()就能取出choice后面的中文
            #print(user.get_user_type_display())
            
            if user.user_type==1:
                return True
            else:
                return False
            
    
    

    实现原理

    源码分析:
    路径:rest_framework-->views-->APIView-->as_view-->dispatch-->self.initial-->self.check_permissions 还在APIView这个视图类里
    
    
    核心源码:
        def check_permissions(self, request):
            
            for permission in self.get_permissions(): #一个个由权限类组成的列表被调用生成对象
                if not permission.has_permission(request, self):#调用对象has_permission,获取结果
                    self.permission_denied(
                        request, message=getattr(permission, 'message', None)
                    )
                 
    
    源码解析:
    # 1)为什么认证需要到Request类中调用方法,权限不需?
        认证去Request调用方法是为了控制APIView的request能否有user这个对象属性以判断用户是否登录,	若用户登录则request对象就有了user对象属性,权限判断在认证之后执行,若用户登录了,request就有	user对象属性了,所以不用去Request类中调用方法.
    
    # 2) self.get_permissions是什么?
    	和认证的authenticator类似,get_permissions得到一个个由权限类组成的列表
    
    # 3)重写的has_permission需要返回什么结果?
    	
    	布尔值,有权限就返回True,否则返回False
    
    
    

    5.2.2配合Auth模块权限组件的使用

    # 演示一下内置权限的使用:IsAdminUser,控制是否对网站后台有权限的人
    # 1 创建超级管理员
    # 2 写一个测试视图类
    from rest_framework.permissions import IsAdminUser
    from rest_framework.authentication import SessionAuthentication
    class TestView3(APIView):
        authentication_classes=[SessionAuthentication,]
        permission_classes = [IsAdminUser] 
        def get(self,request,*args,**kwargs):
            return Response('这是22222222测试数据,超级管理员可以看')
    # 3 超级用户登录到admin,再访问test3就有权限
    # 4 正常的话,普通管理员,没有权限看(判断的是is_staff字段)
    

    5.3 频率

    限制确定访问某个接口的频率

    5.3.1 自定义频率组件的使用

    
    

    5.3.2 配合Auth表使用频率组件

    配置了就能使用

    全局使用:settings的REST_FRAMEWORK里加入
    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSES': (
            'rest_framework.throttling.AnonRateThrottle', 限制匿名用户
            'rest_framework.throttling.UserRateThrottle', 限制登录用户(必须是Django内置的登录认证)
        ),
        'DEFAULT_THROTTLE_RATES': {
            'anon': '3/m',
            'user': '10/m',
        }
    }
    局部使用:视图类里添加
        throttle_classes = [AnonRateThrottle,UserRateThrottle]
        
    

    6,过滤,排序,分页,自动生成接口文档

    6.1 过滤器

    过滤查询结果

    安装:pip3 install django-filter
    注册,在app中注册
    先配置后使用
    全局配:
    REST_FRAMEWORK = {	
     'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
    }
    局部配:写在视图类里
      filter_backends = [DjangoFilterBackend]  
    
    
    使用:    
    #视图层必须继承了 GenericAPIView或其子类 的视图类才能使用过滤器
    #视图层必须配置过滤字段 filter_fields = ('name',) 
    #一般就是查所有的业务会使用过滤字段
    class BookView(ListAPIView):
        queryset = Book.objects.all()
        serializer_class = BookSerializer
        filter_fields = ('name',)  
        
    传参:
    http://127.0.0.1:8000/books2/?name=xxx 
    

    6.2 排序器

    对查询结果排序

    基于filter插件使用
    先配置后使用
    全局配:
    REST_FRAMEWORK = {	
     'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.OrderingFilter',)
    }
    局部配:写在视图类里
      filter_backends = [OrderingFilter]  
    
    使用:
    class Book2View(ListAPIView):
        queryset = Book.objects.all()
        serializer_class = BookSerializer
        filter_backends = [OrderingFilter,DjangoFilterBackend]
        #如果全局配置了过滤,这里想局部使用排序,需要在局部里再加入DjangoFilterBackend,因为它们使用同一个		filter_backends,局部配置会的覆盖全局的配置导致局部没有过滤功能
        ordering_fields = ('id', 'price')
        
        
    传参:
    http://127.0.0.1:8000/books2/?ordering=-price
    http://127.0.0.1:8000/books2/?ordering=price
        
    

    6.3 分页器

    对查询结果分页,继承了ViewsetMixin的视图类直接配置就用了分页功能

    继承了APIView或GenericAPIView的视图类需要手动调用分页器的功能

    # 查所有,才需要分页
    # 内置三种分页方式依次只能使用一种
    from  rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination
    
    drf分页器默认配置: import rest_framework.settings
    DEFAULTS = { 'DEFAULT_PAGINATION_CLASS': None,
                'PAGE_SIZE': None,}
    
    PageNumberPagination的使用
    
    1)直接配置使用默认功能:
        在项目的settings全局配置:
    REST_FRAMEWORK = {'DEFAULT_PAGINATION_CLASS':'rest_framework.pagination.PageNumberPagination',
                'PAGE_SIZE': 5,     }   
    
    	在类里局部配置:
        pagination_classes=MyPageNumberPagination
    默认只能使用一个参数,即PAGE_SIZE 控制信息每页条数
    
    2)自定义分页器类:
    写一个类继承分页器类,在类里重写参数,全局配置或局部配置    
    
    class MyPageNumberPagination(PageNumberPagination):
        #http://127.0.0.1:8000/api/books2/?aaa=1&size=6
        page_size=3  #每页条数
        page_query_param='aaa' #查询第几页的key
        page_size_query_param='size' # 每一页显示的条数的key
        max_page_size=5    # 每页最大显示条数
    
    
    class MyLimitOffsetPagination(LimitOffsetPagination):
         default_limit = 3   # 每页条数
         limit_query_param = 'limit' # 往后拿几条
         offset_query_param = 'offset' # 标杆
         max_limit = 5   # 每页最大几条
        
        
    class MyCursorPagination(CursorPagination):
        cursor_query_param = 'cursor'  # 每一页查询的key
        page_size = 2   #每页显示的条数
        ordering = '-id'  #排序字段
    
    3)视图类使用分页器    
    # 使用ListAPIView的视图类分页    
    class BookView(ListAPIView):
         # queryset = models.Book.objects.all().filter(is_delete=False)
         queryset = models.Book.objects.all()
         serializer_class = BookModelSerializer
         #配置分页
         pagination_class = MyCursorPagination
    
    # 使用APIView的视图类分页
    from utils.throttling import MyThrottle
    class BookView(APIView):
        # throttle_classes = [MyThrottle,]
        def get(self,request,*args,**kwargs):
            book_list=models.Book.objects.all()
           
        # 实例化得到一个分页器对象
            page_cursor=MyPageNumberPagination()
    	# 调用分页器的分页功能
            book_list=page_cursor.paginate_queryset(book_list,request,view=self)
        # 调用获取上下页功能
            next_url =page_cursor.get_next_link()
            pr_url=page_cursor.get_previous_link()
       
            book_ser=BookModelSerializer(book_list,many=True)
            return Response(data=book_ser.data)
    
        
    

    6.3 自动生成接口文档

    插件有coreapi和swagger,这里以coreapi举例

    # 1 安装:pip install coreapi
    
    # 2 配置
    	settings配置:
    REST_FRAMEWORK={
        'DEFAULT_SCHENA_CLASSES':('rest_framework.schemas.coreapi.AutoSchema'),
    }
    	
    	路由配置:
    	from rest_framework.documentation import include_docs_urls
        urlpatterns = [
            path('docs/', include_docs_urls(title='站点页面标题'))
        ]
    #3 视图类:自动接口文档能生成的是继承自APIView及其子类的视图。
    	-1 ) 单一方法的视图,可直接使用类视图的文档字符串,如
            class BookListView(generics.ListAPIView):
                """
                返回所有图书信息.
                """
        -2)包含多个方法的视图,在类视图的文档字符串中,分开方法定义,如
            class BookListCreateView(generics.ListCreateAPIView):
                """
                get:
                返回所有图书信息.
                post:
                新建图书.
                """
        -3)对于视图集ViewSet,仍在类视图的文档字符串中封开定义,但是应使用action名称区分,如
            class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
            """
            list:
            返回图书列表数据
            retrieve:
            返回图书详情数据
            latest:
            返回最新的图书数据
            read:
            修改图书的阅读量
            """
    

    7. JWT

    7.1 jwt基本原理

    所有框架都能用jwt认证,这是一个通用的认证方法

    #jwt的组成:
        1)jwt分三段式:头.体.签名 (head.payload.signature)
        
        2)#头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
        
        3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
        
        4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
        {
            "company": "公司信息",
            ...
        }
        5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
        {
            "user_id": 1,
            ...
        }
        6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
        {
            "head": "头的加密字符串",
            "payload": "体的加密字符串",
            "secret_key": "安全码"
        }
    
    
    #校验
    
    1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
    
    2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
    
    3)再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
    

    7.2 Django使用jwt认证

    ​ 别人已经写好了认证过程,配合user表调用即可使用,或者自定义jwt的使用

    7.2.1 drf项目的jwt认证开发流程

    #token的签发:
    1)用账号密码访问登录接口,
    2) 登录接口逻辑中调用方法签发token,得到token,返回给客户端
    
    #token的校验与反解
    3)校验token,算法应该写在认证类中(在认证类中调用),
    4) 请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户
    

    7.2.2 Django jwt认证的使用

    安装 pip install djangorestframework-jwt
    

    7.3 配合user表使用jwt

    7.3.1自动签发token

    #在路由层导入obtain_jwt_token
    from rest_framework_jwt.views import obtain_jwt_token
    
    urlpatterns = [
        path('login/', obtain_jwt_token),
    ]
    #视图类不需要自己写,使用obtain_jwt_token就能调用jwt组件已经写好的校验视图类,自动去user表取值校验并返回token
    
    #修改自动签发token的返回格式:在项目而settings加入
    def my_jwt_response_payload_handler(token, user=None, request=None):
        return {
            'status':100,
            'msg':'认证成功'
            'token': token
        }
    
    JWT_AUTH{
    'JWT_RESPONSE_PAYLOAD_HANDLER':
        ('rest_framework_jwt.utils.my_jwt_response_payload_handler',)
    'JWT_EXPIRATION_DELTA':datetime.timedelta(days=7), #配置token过期时间
    
    
    }
    

    7.3.2 自动认证token

    限定登录用户才能访问的接口需要token和promission配合使用
    
    token判断用户是否登录,promission判断用户是否有权限访问
    
    JSONWebTokenAuthentication,是已经写好的认证类
    在视图类的authentication_classes里加入JSONWebTokenAuthentication,该视图类就具有认证token的功能.
    
    from rest_framework_jwt.authentication import JSONWebTokenAuthentication
    from rest_framework.permissions import IsAuthenticated
    class OrderAPIView(APIView):
        
        authentication_classes = [JSONWebTokenAuthentication,] # 登录才能
        permission_classes = [IsAuthenticated,] # 权限控制
        
        def get(self,request,*args,**kwargs):
            return Response('这是订单信息')
    

    7.4 自定义jwt认证系列:

    7.4.1手动签发token+多方式登录

    # 使用用户名,手机号,邮箱,都可以登录#
    # 前端需要传的数据格式
    {
    "username":"lqz/1332323223/33@qq.com",
    "password":"lqz12345"
    }
    # 视图
    from rest_framework.views import APIView
    from rest_framework.viewsets import ViewSetMixin, ViewSet
    
    from app02 import ser
    
    class Login2View(ViewSet):  
        def login(self, request, *args, **kwargs):
            # 1 需要 有个序列化的类
            login_ser = ser.LoginModelSerializer(data=request.data,context={'request':request})
            # 2 生成序列化类对象
            # 3 调用序列号对象的is_validad
            login_ser.is_valid(raise_exception=True)
            token=login_ser.context.get('token')
            username=login_ser.context.get('username')
            # 4 return
            return Response({'status':100,'msg':'登录成功','token':token,'username':username})
        
    # 序列化类
    from rest_framework import serializers
    from api import models
    import re
    from rest_framework.exceptions import ValidationError
    
    from rest_framework_jwt.utils import jwt_encode_handler,jwt_payload_handler
    class LoginModelSerializer(serializers.ModelSerializer):
        username=serializers.CharField()  
        # 重新覆盖username字段,数据中它是unique,post,认为你保存数据,自己会校验
        class Meta:
            model=models.User
            fields=['username','password']
    
        def validate(self, attrs):  
            # 重写了父类的validate方法,在该方法里写额外的校验逻辑,校验时执行该方法
            username=attrs.get('username') # 用户名有三种方式
            password=attrs.get('password')
            
            # 通过判断,username数据不同,查询字段不一样
            # 正则匹配,如果是手机号
           
        	if re.match('^1[3-9][0-9]{9}$',username):
                user=models.User.objects.filter(mobile=username).first()
            elif re.match('^.+@.+$',username):# 邮箱
                user=models.User.objects.filter(email=username).first()
            else:
                user=models.User.objects.filter(username=username).first()
            
            if user: # 存在用户
                # 校验密码,因为是密文,要用user表的check_password方法
                if user.check_password(password):
                    # 手动签发token
                    payload = jwt_payload_handler(user)  # 把user传入,得到payload
                    token = jwt_encode_handler(payload)  # 把payload传入,得到token
                    self.context['token']=token
                    self.context['username']=user.username
                    return attrs
                else:
                    raise ValidationError('密码错误')
             else:
                raise ValidationError('用户不存在')
    

    7.4.2自定义jwt认证类

    from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication 
    from rest_framework.exceptions import AuthenticationFailed
    from rest_framework_jwt.utils import jwt_decode_handler 
    from jwt import exceptions
    
    class MyJwtAuthentication(BaseJSONWebTokenAuthentication):
        
        def authenticate(self, request):#只要是自定义的就要重写这个方法      
            jwt_value=request.META.get('HTTP_AUTHORIZATION') #获取token
            
            if jwt_value:
                
                try:            
                    payload=jwt_decode_handler(jwt_value) #校验token并取出payload
                except ExpiredSignature:
                    raise AuthenticationFailed('签名过期')
                except InvalidTokenError:
                    raise AuthenticationFailed('用户非法')
                except Exception as e:                
                    raise AuthenticationFailed(str(e)) # 其他所有异常都会走到这
                    
                user=self.authenticate_credentials(payload) #获取user对象
                
                return user,jwt_value
            
            raise AuthenticationFailed('您没有携带认证信息') # 没有值,直接抛异常
    
    #局部配置,全局配置        
    

    8,缓存

    8.1 Django中的6种缓存方式配置

    • 开发调试缓存

      CACHES = {
       'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',  # 缓存后台使用的引擎
        'TIMEOUT': 300,            # 缓存超时时间(默认300秒,None表示永不过期,0表示立即过期)
        'OPTIONS':{
         'MAX_ENTRIES': 300,          # 最大缓存记录的数量(默认300)
         'CULL_FREQUENCY': 3,          # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
        },
       }
      }
      
    • 内存缓存

      CACHES = {
       'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',  # 指定缓存使用的引擎
        'LOCATION': 'unique-snowflake',         # 写在内存中的变量的唯一值 
        'TIMEOUT':300,             # 缓存超时时间(默认为300秒,None表示永不过期)
        'OPTIONS':{
         'MAX_ENTRIES': 300,           # 最大缓存记录的数量(默认300)
         'CULL_FREQUENCY': 3,          # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
        }  
       }
      }
      
    • 文件缓存

      CACHES = {
       'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', #指定缓存使用的引擎
        'LOCATION': '/var/tmp/django_cache',        #指定缓存的路径
        'TIMEOUT':300,              #缓存超时时间(默认为300秒,None表示永不过期)
        'OPTIONS':{
         'MAX_ENTRIES': 300,            # 最大缓存记录的数量(默认300)
         'CULL_FREQUENCY': 3,           # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
        }
       }   
      }
      
    • 数据库缓存

      CACHES = {
       'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',  # 指定缓存使用的引擎
        'LOCATION': 'cache_table',          # 数据库表    
        'OPTIONS':{
         'MAX_ENTRIES': 300,           # 最大缓存记录的数量(默认300)
         'CULL_FREQUENCY': 3,          # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
        }  
       }   
      }
      
    • Memcache缓存(使用python-memcached模块)

      Memcached是Django原生支持的缓存系统.要使用Memcached,需要下载Memcached的支持库python-memcached或pylibmc.

      CACHES = {
       'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',  # 指定缓存使用的引擎
        'LOCATION': 'cache_table',          # 数据库表    
        'OPTIONS':{
         'MAX_ENTRIES': 300,           # 最大缓存记录的数量(默认300)
         'CULL_FREQUENCY': 3,          # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
        }  
       }   
      }
      
    • Memcache缓存(使用pylibmc模块)

      settings.py文件配置
       CACHES = {
        'default': {
         'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',  # 指定缓存使用的引擎
         'LOCATION':'192.168.10.100:11211',         # 指定本机的11211端口为Memcache缓存服务器
         'OPTIONS':{
          'MAX_ENTRIES': 300,            # 最大缓存记录的数量(默认300)
          'CULL_FREQUENCY': 3,           # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
         },  
        }
       }
      

    8.2 Django中的缓存应用

    8.2.1 视图函数使用缓存

    视图:

    from django.views.decorators.cache import cache_page
    import time
    from .models import *
    
    @cache_page(15)          #超时时间为15秒
    def index(request):
      t=time.time()      #获取当前时间
      bookList=Book.objects.all()
      return render(request,"index.html",locals())
    

    模板(index.html):

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h3>当前时间:-----{{ t }}</h3>
    
    <ul>
        {% for book in bookList %}
           <li>{{ book.name }}--------->{{ book.price }}$</li>
        {% endfor %}
    </ul>
    
    </body>
    </html>
    

    上面的例子是基于内存的缓存配置,基于文件的缓存该怎么配置呢??

    更改settings.py的配置

    CACHES = {
     'default': {
      'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', # 指定缓存使用的引擎
      'LOCATION': 'E:django_cache',          # 指定缓存的路径
      'TIMEOUT': 300,              # 缓存超时时间(默认为300秒,None表示永不过期)
      'OPTIONS': {
       'MAX_ENTRIES': 300,            # 最大缓存记录的数量(默认300)
       'CULL_FREQUENCY': 3,           # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
      }
     }
    }
    

    然后再次刷新浏览器,可以看到在刚才配置的目录下生成的缓存文件

    通过实验可以知道,Django会以自己的形式把缓存文件保存在配置文件中指定的目录中.

    8.2.1 全站使用缓存

    既然是全站缓存,当然要使用Django中的中间件.

    用户的请求通过中间件,经过一系列的认证等操作,如果请求的内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户

    当返回给用户之前,判断缓存中是否已经存在,如果不存在,则UpdateCacheMiddleware会将缓存保存至Django的缓存之中,以实现全站缓存

    缓存整个站点,是最简单的缓存方法
    
    在 MIDDLEWARE_CLASSES 中加入 “update” 和 “fetch” 中间件
    MIDDLEWARE_CLASSES = (
        ‘django.middleware.cache.UpdateCacheMiddleware’, #第一
        'django.middleware.common.CommonMiddleware',
        ‘django.middleware.cache.FetchFromCacheMiddleware’, #最后
    )
    “update” 必须配置在第一个
    “fetch” 必须配置在最后一个
    

    修改settings.py配置文件

    视图函数:

    from django.views.decorators.cache import cache_page
    import time
    from .models import *
    
    
    def index(request):
    
         t=time.time()      #获取当前时间
         bookList=Book.objects.all()
         return render(request,"index.html",locals())
    
    def foo(request):
        t=time.time()      #获取当前时间
        return HttpResponse("HELLO:"+str(t))
    

    模板(index.html):

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h3 style="color: green">当前时间:-----{{ t }}</h3>
    
    <ul>
        {% for book in bookList %}
           <li>{{ book.name }}--------->{{ book.price }}$</li>
        {% endfor %}
    </ul>
    
    </body>
    </html>
    

    其余代码不变,刷新浏览器是10秒,页面上的时间变化一次,这样就实现了全站缓存.

    8.2.2局部视图缓存

    例子,刷新页面时,整个网页有一部分实现缓存

    views视图函数

    from django.views.decorators.cache import cache_page
    import time
    from .models import *
    def index(request):
         t=time.time()      #获取当前时间
         bookList=Book.objects.all()
         return render(request,"index.html",locals())
    

    模板(index.html):

    {% load cache %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
     <h3 style="color: green">不缓存:-----{{ t }}</h3>
    
    {% cache 2 'name' %}
     <h3>缓存:-----:{{ t }}</h3>
    {% endcache %}
    
    </body>
    </html> 
    

    9,drf常用导入模块路径

    from rest_framework.serializers import ModelSerializer # drf的序列化器
    
    from rest_framework.request import Request # drf的请求
    from rest_framework.response import Response # drf的响应
    from rest_framework.exceptions import APIException #所有API相关的异常
    
    from rest_framework.views import APIView # drf的视图类
    from rest_framework.generics import GenericAPIView 
    from rest_framework.mixins import CreateModelMixin
    from rest_framework.viewsets import ViewSetMixin 
    
    from rest_framework.authentication import BaseAuthentication #drf的认证类
    from rest_framework.permissions import BasePermission # drf的权限类
    from rest_framework.throttling import BaseThrottle # drf的限流类
    
    from rest_framework.permissions import IsAdminUser # Django内置的权限认证
    from rest_framework.authentication import SessionAuthentication # Django内置的登录认证
    
    from jwt import exceptions  # jwt相关的异常
    import rest_framework_jwt.settings #jwt的默认配置
    from rest_framework_jwt.views import ObtainJSONWebToken #jwt提供的视图类及其他
    from rest_framework_jwt.authentication import JSONWebTokenAuthentication #jwt的认证类
    from rest_framework_jwt.authentication import jwt_decode_handler #和utils里的是同一个
    from rest_framework_jwt.utils import jwt_response_payload_handler #jwt自动签发token返回器
    from rest_framework_jwt.utils import jwt_decode_handler,jwt_payload_handler #jwt解码器
    
    import untitled4.settings # 该项目的配置
    import rest_framework_jwt.settings #jwt的默认配置
    import rest_framework.settings  #drf所有的默认配置
    import rest_framework.status  #drf所有的状态码
    from rest_framework.views import exception_handler #drf默认异常处理器
    
    from django_filters.rest_framework import DjangoFilterBackend # 过滤器筛选器
    from rest_framework.schemas.coreapi import AutoSchema # 自动生成接口
    from rest_framework.pagination import PageNumberPagination # 分页器
    
  • 相关阅读:
    Huntor中国CRM评估报告连载(二)
    子曾经曰过:先有司,赦小过,举贤才
    Huntor中国CRM评估报告连载(一)
    开源.net微博组建维护团队
    汉拓中国CRM评估报告简介
    CHFMake代码生成器Beta版下载
    山寨版Windows.Forms用户控件
    [转]Team Foundation Server 2008 單一伺服器簡易安裝說明 (Win2003+SQL2005+TFS2008)
    黑马程序员——c语言学习心得——函数传递二维数组
    黑马程序员——oc语言学习心得—— 属性声明和赋值
  • 原文地址:https://www.cnblogs.com/Franciszw/p/13341191.html
Copyright © 2020-2023  润新知