第二十六章:drf

一、drf 框架介绍

1.关于前后端分离

接下来,你将进入 前后端分离项目开发 模块。 这也是现在企业中比较常见的开发模式。

疑问:

  • 什么是前后端分离?与之前的开发模式有什么区别?
  • 企业为什么要用前后端分离?

1.1 什么是前后端分离?

  • 前后端不分离,像咱们之前学习django、案例、crm项目、bug管理 时的那些模块。

    特点:
    	- 用户访问URL
    	- 执行视图函数,视图进行业务处理
    	- 视图render,读取HTML模块+数据渲染,将渲染完成的HTML/CSS/JS返回并呈现在用户浏览器上。
    	
    配合开发:
    	- 前端,写HTML、CSS、JS
    	- 后端,前端代码给我后端,后端代码 + 前端代码 集成到项目中。
    

  • 前后端分离

    特点:
    	- 一般基于 vue.js、react.js、angular.js 框架来编写前端页面(本质上是HTML、CSS、JS)。
    	- 页面上如果需要呈现数据,则需要则需要通过 ajax 的形式向后端发送请求(URL)并获取数据。
    	- 后端接收到请求后,执行视图函数并进行业务处理
    	- 后端的视图执行完毕后,给前端返回JSON格式数据。
    	- 前端接收到JSON格式数据后呈现在浏览器上即可。
    	
    配合开发:
    	- 前端,写HTML、CSS、JS(数据都是通过调用后端API获得)
    	- 后端,写API接口
    	- 前后端约定好接口的规则。
    


1.2 为什么要使用前后端分离?

目前企业一般都会采用前后端分离的形式来进行项目开发,这种模式:

  • 前后端职责清晰,前端开发者只vue.js、react.js、angular.js等框架编写页面;后端开发者只用Python编写后端代码;(两者通过json格式请求数据的传输)。
  • 开发高效,前后端做自己擅长的领域且使用vue.js等前端框架比用传统的HTML、CSS、JS、jQuery等开发速度快很多。
  • 有利于项目的扩展(开发APP、微信小程序等)。

注意:前后端不分离的项目,现在一般用于开发用户量少、简单的项目。

2. 关于项目安排

大多数项目最终是基于前后端分离来进行开发,所以需要学会前端开发必备技能、后端开发必备技能后,再进行业务功能开发,项目讲解的安排如下:

  • 第一部分:作为后端开发者,学会基于 Django 编写后端API(给前端提供URL并返回相应格式数据)。
  • 第二部分:作为前端开发者,学会基于 vue.js 编写前端页面并调用后端API获取数据。
  • 第三部分:结合前端和后端技术,开发本项目的业务功能。

二、RESTful与Django视图模式

1. RESTful 规范

对于后端开发者,本质上就是提供URL给前端开发者调用并返回相应的数据。例如:

现在咱们大家知道前端后端分离的项目是需要:前端、后端 双方来进行合作开发,既然合作进行开发就必须要提前约定一些规范,以防止双方”打架“,例如:

  • 数据传输用XML格式?JSON格式?

  • 出现错误时,错误信息有谁来提供?

    方案1:错误时后端返回错误信息,前端只做呈现即可。
    	{
    		code:40001,
    		error:"xxx错误"
    	}
    方案2:错误时后端返回错误码,前端根据错误码的对应关系呈现。
    	{
    		code:40001
    	}
    
  • 等等等...

所以,我们需要先来学习下规范,然后再来进行后续开发。

restful是主流的一套API规范,企业进行前后端分离开发一般都会遵循它,他定义了很多规范的条款。

例如:如果现在让大家来开发一个对 用户表 进行增删改查的接口,在不了解restful规范前, 大家一定会这样来搞:

接口:/users/list/			用户列表
接口:/users/add/			添加用户
接口:/users/detail/(\d+)	用户详细信息
接口:/users/edit/(\d+)	更新用户
接口:/users/del/(\d+)		删除用户

很早之前开发者们也确实都是这么干的,直到后来有人提出了 restful API规范(包含了很多规定),如果按照这个规范来开发上述的功能的话:

接口:/users/			方法:GET     =>   用户列表
接口:/users/			方法:POST    =>   添加列表
接口:/users/(\d+)/	方法:GET     =>   获取单条数据
接口:/users/(\d+)/	方法:DELETE  =>   删除数据
接口:/users/(\d+)/	方法:PUT     =>   更新数据

暂且不说restful规范有多好,对于前端和后端只要能统一了规范,对大家的开发效率都会有很大的帮助,不用再做很多无效的沟通了。

所以,接下来咱们就是学习最常见的restful规范。

1.1.HTTPS协议

建议使用https协议替代http协议,让接口数据更加安全。

这一条其实与开发无关,在最后的项目部署时只要使用https部署即可。

如果是基于HTTP协议,则意味着用户浏览器再向服务器发送数据时,都是以明文的形式传输,如果你在某咖啡厅上网,他就可以把你网络传输的数据明文都获取到。

如果是基于HTTPS协议,则意味着用户浏览器再向服务器发送数据时,都是以密文的形式传输,中途即使有非法用户获取到网络数据,也可以密文的,无法破译。

HTTPS保证了数据安全,但由数据传输存在着加密和解密的过程,所以会比HTTP协议慢一些。

注意:此处大家先了解https和http的不同,至于底层原理和部署,在项目部署时再细讲。

参考文档:https://www.cnblogs.com/wupeiqi/p/11647089.html

1.2. 域名

对于后端API接口中要体现API标识,例如:



1.3. 版本

对于后端API接口中要体现版本,例如:

- http://api.example.com/v1/

- http://api.example.com/?version=v1

- http://v1.example.com/

- http://api.example.com/
  请求头:Accept: application/json; version=v1

1.4. 路径

restful API这种风格中认为网络上的一切都称是资源,围绕着资源可以进行 增删改查等操作。

这些资源,在URL中要使用名词表示(可复数),围绕着资源进行的操作就用Method不同进行区分。

https://api.example.com/v1/person
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees

1.5. 请求方法

根据请求方法不同进行不同的操作。

GET		在服务器取出资源(一项或多项)
POST	在服务器新建一个资源
PUT		在服务器更新资源(客户端提供改变后的完整资源)
PATCH	在服务器更新资源(客户端提供改变的属性)
DELETE	在服务器删除资源

例如:

https://api.example.com/v1/users
https://api.example.com/v1/users/1/

接口:/users/			方法:GET     =>   用户列表
接口:/users/			方法:POST    =>   添加用户
接口:/users/(\d+)/	方法:GET     =>   获取单条数据
接口:/users/(\d+)/	方法:DELETE  =>   删除数据
接口:/users/(\d+)/	方法:PUT     =>   更新数据
接口:/users/(\d+)/	方法:PATCH   =>   局部更新

1.6. 搜索条件

在URL中通过参数的形式来传递搜索条件。

https://api.example.com/v1/users
https://api.example.com/v1/zoos?limit=10				指定返回记录的数量
https://api.example.com/v1/zoos?offset=10				指定返回记录的开始位置
https://api.example.com/v1/zoos?page=2&per_page=100		指定第几页,以及每页的记录数
https://api.example.com/v1/zoos?sortby=name&order=asc	指定返回结果按照哪个属性排序,以及排序顺序
https://api.example.com/v1/zoos?animal_type_id=1		指定筛选条件

1.7. 返回数据

针对不同操作,服务器向用户返回的结果结构应该不同。

https://api.example.com/v1/users
https://api.example.com/v1/users/2/
URL 方法 描述 返回数据
/users/ GET 列表 返回资源对象的列表
[ {id:1,name:"武沛齐"}, {id:1,name:"日天"} ]
/users/ POST 添加 返回新生成的资源对象
/users/(\d+)/ GET 获取单条数据 返回单个资源对象
/users/(\d+)/ DELETE 删除数据 返回一个空文档
null
/users/(\d+)/ PUT 更新数据 返回完整的资源对象
/users/(\d+)/ PATCH 局部更新 返回完整的资源对象

一般在实际的开发过程中会对上述返回数据进行补充和完善,例如:每次请求都返回一个字典,其中包含:

  • code,表示返回码,用于表示请求执行请求,例如:0表示请求成功,1003表示参数非法,40009数据量太大等。
  • data,表示数据
  • error,错误信息
{
    code:0,
    data:[ {id:1,name:"武沛齐"},   {id:1,name:"日天"}  ]
}
{
    code:41007,
    error:"xxxxx"
}

1.8. 状态码

后端API在对请求进行响应时,除了返回数据意外还可以返回状态码,来表示请求状况。

from django.urls import path, include
from app01 import views

urlpatterns = [
    path('users/', views.users),
]
from django.http import JsonResponse

def users(request):
    info = {
        "code": 1000,
        "data": {"id":1,"name":"武沛齐"}
    }
    return JsonResponse(info, status=200)

200 OK - [GET]:服务器成功返回用户请求的数据
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
301 Moved Permanently  被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一
302 Found 请求的资源现在临时从不同的 URI 响应请求
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作。
401 Unauthorized - [*]:表示用户未认证(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

更多看这里:http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

状态码可以表示一部分的服务端的处理请求,但特别细致的信息无法全都都包括,所以一般在开发中会配合code返回码来进行。

例如:https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Global_Return_Code.html

1.9. 错误处理

错误处理,状态码是4xx时,应返回错误信息,error当做key。

{
    error: "Invalid API key"
}

在 1.1.7 中中已包含。

了解常用 resful 规范,那么以后在开发后端API接口时,就要根据上述要求遵循 restful 规范(非必须,视公司情况灵活变通)。

案例展示

基于 django + restful 规范来开发一个后台接口示例(FBV)。

# urls.py

from django.urls import path
from app01 import views

# http://www.xxx.com/api/v1/users/

urlpatterns = [
    path('api/<str:version>/users/', views.users),
    path('api/<str:version>/users/<int:pk>/', views.users),
]
# views.py

from django.http import JsonResponse


def users(request, version, pk=None):
    print("版本:", version)
    if not pk:
        if request.method == "GET":
            # 请求用户列表
            info = {
                "code": 0,
                "data": [{"id": 1, "name": "武沛齐"}]
            }
            return JsonResponse(info)
        elif request.method == "POST":
            # 新增用户,读取 request.POST 中提交的数据并添加到数据库中
            info = {
                "code": 0,
                "data": {"id": 1, "name": "武沛齐"}
            }
            return JsonResponse(info)
        else:
            info = {
                "code": 1000,
                "error": "请求错误"
            }
            return JsonResponse(info)

    if request.method == "GET":
        # 获取ID=pk的用户信息,并返回
        info = {
            "code": 0,
            "data": {"id": 1, "name": "武沛齐"}
        }
        return JsonResponse(info)
    elif request.method == "DELETE":
        # 删除id=pk的用户
        info = {
            "code": 0,
            "data": {}
        }
        return JsonResponse(info)
    elif request.method == "PUT":
        # 读取request.POST中的数据 + pk,更新数据库中的用户信息
        info = {
            "code": 0,
            "data": {"id": 1, "name": "武沛齐"}
        }
        return JsonResponse(info)
    elif request.method == "PATCH":
        # 读取request.POST中的数据 + pk,更新数据库中的用户信息
        info = {
            "code": 0,
            "data": {"id": 1, "name": "武沛齐"}
        }
        return JsonResponse(info)
    else:
        info = {
            "code": 1000,
            "error": "请求错误"
        }
        return JsonResponse(info)

2. Django的FBV和CBV

基于 django 开发项目时,对于视图可以使用 FBV 和 CBV 两种模式编写。

  • FBV,function base views,其实就是编写函数来处理业务请求。

    from django.contrib import admin
    from django.urls import path
    from app01 import views
    urlpatterns = [
        path('users/', views.users),
    ]
    
    from django.http import JsonResponse
    
    def users(request,*args, **kwargs):
        if request.method == "GET":
            return JsonResponse({"code":1000,"data":"xxx"})
        elif request.method == 'POST':
            return JsonResponse({"code":1000,"data":"xxx"})
        ...
    
  • CBV,class base views,其实就是编写类来处理业务请求。

    from django.contrib import admin
    from django.urls import path
    from app01 import views
    urlpatterns = [
        path('users/', views.UserView.as_view()),
    ]
    
    from django.views import View
    
    class UserView(View):
        def get(self, request, *args, **kwargs):
            return JsonResponse({"code": 1000, "data": "xxx"})
    
        def post(self, request, *args, **kwargs):
            return JsonResponse({"code": 1000, "data": "xxx"})
    

其实,CBV和FBV的底层实现本质上相同的。

CBV,其实就是在FBV的基础上进行的功能的扩展,根据请求的方式不同,直接定位到不同的函数中去执行。

如果是基于 django 编写 restful API,很显然使用CBV的方式会更加简洁,因为 restful 规范中就是根据 method 不同来执行不同操作。

基于 django CBV和 restful 规范开发实战案例:

# urls.py

from django.urls import path
from app01 import views

urlpatterns = [
    # http://www.xxx.com/api/v1/users/
    path('api/<str:version>/users/', views.UserView.as_view()),

    # http://www.xxx.com/api/v1/users/2/
    path('api/<str:version>/users/<int:pk>/', views.UserView.as_view()),

]
# views.py

from django.views import View
from django.http import JsonResponse


class UserView(View):
    def get(self, request, version, pk=None):
        if not pk:
            # 请求用户列表
            info = {
                "code": 0,
                "data": [
                    {"id": 1, "name": "武沛齐"},
                    {"id": 1, "name": "武沛齐"},
                ]
            }
            return JsonResponse(info)
        else:
            # 获取ID=pk的用户信息,并返回
            info = {
                "code": 0,
                "data": {"id": 1, "name": "武沛齐"}
            }
            return JsonResponse(info)

    def post(self, request, version):
        # 新增用户,读取 request.POST 中提交的数据并添加到数据库中
        info = {
            "code": 0,
            "data": {"id": 1, "name": "武沛齐"}
        }
        return JsonResponse(info)

    def delete(self, request, version, pk):
        # 删除id=pk的用户
        info = {
            "code": 0,
            "data": {}
        }
        return JsonResponse(info)

    def put(self, request, version, pk):
        # 读取request.POST中的数据 + pk,更新数据库中的用户信息
        info = {
            "code": 0,
            "data": {"id": 1, "name": "武沛齐"}
        }
        return JsonResponse(info)

    def patch(self, request, version, pk):
        # 读取request.POST中的数据 + pk,更新数据库中的用户信息
        info = {
            "code": 0,
            "data": {"id": 1, "name": "武沛齐"}
        }
        return JsonResponse(info)

从上面的示例看来,基于 django 框架完全可以开发 restful API。

django restframework 框架是在 django 的基础上又给我们提供了很多方便的功能,让我们可以更便捷基于 django 开发 restful API,来一个简单的实例,快速了解下:

  • 基于django

  • 基于 django + django restframework 框架

三、接口测试工具

# API接口写好,后端人员要测试,不可能在浏览器里测试
# 使用postman软件,来做接口测试
	-浏览器只能发送 get 请求,不能自动发送 post,delete 请求
    -postman ---》开源软件,只是谷歌浏览器的插件,越做好好,后来可以按装到操作系统上,再后来,收费
    -很多很多其他的,不同公司用的也可能不一样,你只需要明白一个点,这个工具只是用来发送http请求
# 接口测试软件有很多,postwoman
# 官网下载:https://www.postman.com/downloads/
# 双击安装



四、djangorestframework 快速使用

1.rest_framework 使用

  • 安装

    pip install djangorestframework==3.12.4
    
    版本要求:djangorestframework==3.12.4
    	Python (3.5, 3.6, 3.7, 3.8, 3.9)
    	Django (2.2, 3.0, 3.1)
        
    版本要求:djangorestframework==3.11.2
    	Python (3.5, 3.6, 3.7, 3.8)
    	Django (1.11, 2.0, 2.1, 2.2, 3.0)
    
  • 配置,在 settings.py 中添加配置

    INSTALLED_APPS = [
        ...
        # 注册rest_framework(drf)
        'rest_framework',
    ]
    
    # drf相关配置以后编写在这里 
    REST_FRAMEWORK = {
       
    }
    
  • 代码实现

# urls.py

from django.contrib import admin
from django.urls import path
from rest_framework.routers import SimpleRouter
from app01 import views

router = SimpleRouter()
router.register('books', views.BookView)
urlpatterns = [
    path('admin/', admin.site.urls),
]
urlpatterns += router.urls
# views.py

from .models import Book
from rest_framework.viewsets import ModelViewSet
from .serializer import BookSerializer


class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
# models.py

from django.db import models


class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.CharField(max_length=32)
    publish = models.CharField(max_length=32)
# serializer.py

from rest_framework import serializers
from .models import Book


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

2. cbv 源码分析

# cbv 写好后,路由配置如下
	-第一个参数是路径,第二个参数是视图函数的内存地址(视图类执行as_view这个类方法,把它执行完,结果放在第二个参数上:我们猜执行完的结果是个函数内存地址)
    
路由中写的:path('api/v1/books/', views.BookView.as_view()),第二个参数无论是fbv还是cbv放的都是函数内存地址
    -当请求来了,匹配成功会执行,views.BookView.as_view()(request)
    -views.BookView.as_view()执行结果是View的类方法as_view返回的结果是内层函数view,是个函数内层地址
    -本身请求来了,匹配成功,会执行view(request)
    def view(request, *args, **kwargs):
        return self.dispatch(request, *args, **kwargs)

    -self.dispatch  View类的方法
    def dispatch(self, request, *args, **kwargs):
        # request.method请求方式转成小写,必须在列表中才能往下走
        if request.method.lower() in self.http_method_names:
            # 反射,去self【视图类的对象:BookView】,去通过get字符串,反射出属性或方法
            # BookView的get方法
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
                # BookView的get方法,加括号,传入request
                return handler(request, *args, **kwargs)

drf 中重写了 as_viewdispatch方法,其实就是在原来 django 的功能基础上添加了一些功能,例如:

  • as_view,免除了 csrf 验证,一般前后端分离不会使用 csrf token 认证(后期会使用 jwt 认证)。
  • dispatch,内部添加了版本处理、认证、权限、访问频率限制等诸多功能(后期逐一讲解)。

五、APIView + drf 的使用

使用 APIView 类后,重写了 as_view() 方法,重新封装了 request 参数。请求都没有 csrf 的校验了,且执行了认证,频率,权限功能。

1.代码编写

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request

class BookView(APIView):  # APIView 继承自 django 的 View
    def get(self, request):
        # print(type(self.request))  # 新的 request
        # print(type(request))
        print(request._request)
        print(type(request._request)) # django.core.handlers.wsgi.WSGIRequest

        # print(request.method)  # get
        # print(request.path)  # /books/
        # print(request.GET)  # 原来的 get 请求提交的参数
        # print(request.POST)  # 原来 post 请求提交的参数

        book_list = Book.objects.all()
        # book_list 是 queryset 对象不能直接序列化,只能通过 for 循环一个个拼成列表套字典的形式
        res_list = []
        for book in book_list:
            res_list.append({'name': book.name, 'price': book.price, 'publish': book.publish})
        return Response(res_list)

2.APIView 源码分析

# 视图类继承 APIView 后,执行流程就发生了变化,这个变化就是整个的 drf 的执行流程

# 一旦继承了 APIView 入口
	-路由配置跟之前继承View是一样的----》找视图类的as_view---》【APIView的as_view】
        @classmethod
        def as_view(cls, **initkwargs):
            # 又调用了父类(View)的as_view
            view = super().as_view(**initkwargs)
            '''
            # 从此以后,所有的请求都没有 csrf 的校验了
            # 在函数上加装饰器
            @csrf_exempt
            def index(request):
                pass
            本质等同于 index=csrf_exempt(index)
            '''
            return csrf_exempt(view)
    
    -请求来了,路由匹配成功会执行 View 类的 as_view 类方法内的 view 闭包函数(但是没有了 csrf 认证)
    -真正的执行,执行 self.dispatch---->APIView 的 dispatch  【这是重点】
     def dispatch(self, request, *args, **kwargs):
        # 参数的request是原来的django原生的request
        # 下面的request,变成了drf提供的Request类的对象---》return Request(。。。)
        request = self.initialize_request(request, *args, **kwargs)
        # self 是视图类的对象,视图类对象.request=request 新的request
        self.request = request
        try:
            # 执行了认证,频率,权限 [不读]
            self.initial(request, *args, **kwargs)
            # 原来的 View 的 dispatch 的东西
            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
            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
    
    
# 总结:APIView的执行流程
    1 去除了所有的 csrf
    2 包装了新的 request,以后在视图类中用的 request 是新的,Request 类的对象,不是原生的了
    	- 原生的在:新的 requets._request
    3 在执行视图类的方法之前,执行了 3 大认证【认证,频率,权限】
    4 如果在 3 大认证或视图函数方法执行过程中出了错,会有异常捕获 ----》全局异常捕获

3.Request 类源码分析

# 1 视图类中使用的 request 对象,已经变成了 drf 提供的 Request 类的对象了
	-原生 djagno 的 request 是这个类的对象:django.core.handlers.wsgi.WSGIRequest
    -drf 的 request 是这个类的对象:rest_framework.request.Request
    
# 2 request 已经不是原来的 request 了,还能像原来的 request 一样使用吗?
	-用起来,像之前一样
        print(request.method)  # get
        print(request.path)  # /books/
        print(request.GET)   # 原来的get请求提交的参数
        print(request.POST)  # 原来post请求提交的参数
        
# 3 Request 源码
	-方法 __getattr__
    -在视图类的方法中,执行 request.method,新的 request 是没有 method,就触发了新的 Request 的 __getattr__ 方法的执行
    def __getattr__(self, attr):
        try:
            # 从老的 request 中反射出 要取得属性
            return getattr(self._request, attr)
        except AttributeError:
            return self.__getattribute__(attr)
    
    -request.data--->这是个方法,包装成了数据属性
    	-以后无论post,put。。放在body中提交的数据,都从request.data中取,取出来就是字典
        -无论是那种编码格式
        
    -request.query_params--->这是个方法,包装成了数据属性
    	-get请求携带的参数,以后从这里面取
        -query_params:查询参数--->restful规范请求地址中带查询参数
        
    -request.FILES--->这是个方法,包装成了数据属性
    	-前端提交过来的文件,从这里取

# 4 Request 类总结
	-1 新的 request 用起来,跟之前一模一样,因为新的取不到,会取老的 __getattr__
    -2 request.data 无论什么编码,什么请求方式,只要是 body 中的数据,就从这里取,字典
    -3 request.query_params 就是原来的 request._request.GET
    -4 上传的文件从 request.FILES

# 5 python 中有很多魔法方法,在类中,某种情况下触发,会自动执行
	-__str__: 打印对象会调用
    -__init__: 类() 会调用
    -__call__: 对象() 会调用
    -__new__: 实例化一个对象时,首先调用 __new__() 方法构造一个类的实例,并为其分配对应类型的内存空间
	-__getattr__: 对象.属性,如果属性不存在,会触发它的执行

六、序列化组件的使用

api接口开发,最核心最常见的一个过程就是序列化,所谓序列化就是把【数据转换格式】,序列化可以分两个阶段:

# 序列化: 把我们识别的数据转换成指定的格式提供给别人
	-字典,列表------》json格式存到文件中了
	-例如:我们在django中获取到的数据默认是模型对象,但是模型对象数据无法直接提供给前端或别的平台使用,所以我们需要把数据进行序列化,变成字符串或者json数据,提供给别人
	-read
# 反序列化:把别人提供的数据转换/还原成我们需要的格式
	-例如:前端js提供过来的json数据,对于python而言就是字符串,我们需要进行反序列化换成模型类对象,这样我们才能把数据保存到数据库中。
    -write

1.序列化的基本使用

1.1 模型类

# models.py

from django.db import models


class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.CharField(max_length=32)
    publish = models.CharField(max_length=32)

1.2 定义序列化类

# myserializer.py

# 写序列化类:给 book 进行序列化
# from rest_framework.serializers import Serializer
from rest_framework import serializers


class BookSerializer(serializers.Serializer):
    # 要序列化的字段    有很多字段类,字段类有很多字段属性
    name = serializers.CharField()  # 字段类
    # price = serializers.CharField()
    publish = serializers.CharField()

1.3 查询多条数据 [序列化]

class BookView(APIView):  # APIView继承自django的View
    def get(self, request):
        book_list = Book.objects.all()
        # instance表示要序列化的数据,many=True表示序列化多条(instance是qs对象,一定要传many=True)
        ser = BookSerializer(instance=book_list, many=True)

        return Response(ser.data)

1.4 查询单条数据 [序列化]

class BookDetailView(APIView):
    def get(self, request, pk):
        book = Book.objects.filter(pk=pk).first()
        ser = BookSerializer(instance=book)
        return Response(ser.data)

1.5 新增 [反序列化]

视图类

class BookView(APIView):  # APIView 继承自 django 的 View
    def post(self, request):
        # 前端传递数据,从 request.data 取出来
        ser = BookSerializer(data=request.data)
        if ser.is_valid():  # 表示校验前端传入的数据   没有写校验规则,现在等于没校验
            ser.save()  # 再写东西,这里会报错  调用 save 会触发 BookSerialize r的 save 方法,判断了,如果 instance 有值执行 update,没有值执行 create
            return Response(ser.data)
        else:
            return Response(ser.errors)

序列化类

class BookSerializer(serializers.Serializer):
    # 要序列化的字段    有很多字段类,字段类有很多字段属性
    name = serializers.CharField()  # 字段类
    price = serializers.CharField()
    publish = serializers.CharField()

    # 重写create方法,
    def create(self, validated_data):
        res = Book.objects.create(**validated_data)
        return res

1.6 修改 [反序列化]

视图类

class BookDetailView(APIView):
    def put(self, request, pk):
        book = Book.objects.filter(pk=pk).first()
        # 前端传递数据,从request.data取出来
        ser = BookSerializer(instance=book, data=request.data)
        if ser.is_valid():  # 表示校验前端传入的数据   没有写校验规则,现在等于没校验
            ser.save()  # 再写东西,这里会报错  调用save会触发BookSerializer的save方法,判断了,如果instance有值执行update,没有值执行create
            return Response(ser.data)
        else:
            return Response(ser.errors)

序列化类

class BookSerializer(serializers.Serializer):
    # 要序列化的字段    有很多字段类,字段类有很多字段属性
    name = serializers.CharField()  # 字段类
    price = serializers.CharField()
    publish = serializers.CharField()

    # 重写 update
    def update(self, instance, validated_data):
        # instance 要修改的对象
        # validated_data 校验过后的数据
        instance.name = validated_data.get('name')
        instance.price = validated_data.get('price')
        instance.publish = validated_data.get('publish')
        instance.save()
        return instance

1.7 删除

视图类

class BookDetailView(APIView):
    def delete(self, request, pk):
        models.Book.objects.filter(pk=pk).delete()
        return Response()

2.序列化常用字段

# BooleanField	BooleanField()
# NullBooleanField	NullBooleanField()
# CharField	CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)
# EmailField	EmailField(max_length=None, min_length=None, allow_blank=False)
# RegexField	RegexField(regex, max_length=None, min_length=None, allow_blank=False)
# SlugField	SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+
# URLField	URLField(max_length=200, min_length=None, allow_blank=False)
# UUIDField	UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose' 如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex' 如 "5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
# IPAddressField	IPAddressField(protocol=’both’, unpack_ipv4=False, **options)
# IntegerField	IntegerField(max_value=None, min_value=None)
# FloatField	FloatField(max_value=None, min_value=None)
# DecimalField	DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置
# DateTimeField	DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)
# DateField	DateField(format=api_settings.DATE_FORMAT, input_formats=None)
# TimeField	TimeField(format=api_settings.TIME_FORMAT, input_formats=None)
# DurationField	DurationField()
# ChoiceField	ChoiceField(choices) choices与Django的用法相同
# MultipleChoiceField	MultipleChoiceField(choices)
# FileField	FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
# ImageField	ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)

---------记住一下几个-----------
CharField
BooleanField
IntegerField
DecimalField


# ListField: {name:'lqz',age:19,hobby:['篮球','足球']}
# DictField:{name:'lqz',age:19,wife:{'name':'刘亦菲','age':33}}

3.常用字段参数

# 选项参数:
参数名称	  作用

# 给 CharField 字段类使用的参数
max_length	  最大长度
min_lenght	  最小长度
allow_blank	  是否允许为空
trim_whitespace	是否截断空白字符

# 给 IntegerField 字段类使用的参数
max_value	  最小值
min_value	  最大值

# 通用参数:放在哪个字段类上都可以的
required	表明该字段在反序列化时必须输入,默认True
default	    反序列化时使用的默认值
allow_null	表明该字段是否允许传入None,默认False
validators	该字段使用的验证器【不需要了解】
error_messages	包含错误编号与错误信息的字典
label	用于HTML展示API页面时,显示的字段名称
help_text	用于HTML展示API页面时,显示的字段帮助提示信息

# 重点
read_only	表明该字段仅用于序列化输出,默认False
write_only	表明该字段仅用于反序列化输入,默认False

4.序列化类高级用法

4.1 source

# 获取所有图书接口 APIView+Response+序列化类

# name 字段在前端显示的时候叫 book_name
	-使用 source,字段参数,可以指定序列化表中得哪个字段
    	book_title = serializers.CharField(min_length=1, max_length=20, source='title')
    - source 指定的可以是字段,也可以是方法,用于重命名
    - source 可以做跨表查询

示例:

# model.py
from django.db import models

# Create your models here.

class Book(models.Model):
    title = models.CharField(max_length=20, verbose_name='名称')
    price = models.IntegerField(verbose_name='价格')
    press = models.CharField(max_length=20, verbose_name='出版社')
    
    def prefix_name(self):
        return 'ysg_' + self.title
# myserializer.py
from rest_framework import serializers
from app01.models import Book

class BookSerializer(serializers.Serializer):
    # book_title = serializers.CharField(min_length=1, max_length=20, source='title')
    book_title = serializers.CharField(min_length=1, max_length=20, source='prefix_name')
    price = serializers.IntegerField(min_value=1, max_value=99)
    press= serializers.CharField(min_length=3, max_length=20)

    def create(self, validated_data):
        print(validated_data)
        book_obj = Book.objects.create(**validated_data)
        return book_obj
# views.py
from django.shortcuts import render

# Create your views here.

from rest_framework.views import APIView
from rest_framework.response import Response
from app01.myseria import myserializer

from app01 import models


class GetData(APIView):
    def post(self, request):
        ser = myserializer.BookSerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        else:
            return Response({'code': 1001, 'msg': ser.errors})
    
    def get(self, request):
        books = models.Book.objects.all()
        ser = myserializer.BookSerializer(instance=books, many=True)
        return Response(ser.data)
# 请求数据 post
# book_title = serializers.CharField(min_length=1, max_length=20, source='title')
url:http://127.0.0.1:8000/drf/books/
{
    "title": "梦的解析2",
    "price": "89",
    "press": "上海出版社"
}

# 验证错误的消息
{
    "code": 1001,
    "msg": {
        "book_title": [
            "这个字段是必填项。"
        ]
    }
}
# 请求数据 get
# book_title = serializers.CharField(min_length=1, max_length=20, source='prefix_name')
url:http://127.0.0.1:8000/drf/books/
[
    {
        "book_title": "ysg_梦的解析",
        "price": 89,
        "press": "上海出版社"
    },
    {
        "book_title": "ysg_梦的解析2",
        "price": 89,
        "press": "上海出版社"
    }
]

4.2 定制序列化字段的两种方式

# urls.py

from django.urls import path, include
from .views import Format, Format2, FormatBook, FormatBook2


urlpatterns = [
    # path('format/', Format.as_view()),
    # path('format/<int:pk>/', Format2.as_view()),
    path('format/', FormatBook.as_view()),
    path('format/<int:pk>/', FormatBook2.as_view()),
]
# models.py

from django.db import models


# 图书跟作者:多对多,需要建立中间表,但是我们可以通过ManyToManyField自动生成,写在哪里都行
# 图书跟出版社:一对多,一个出版社,出版多本书,关联字段写在多的一方,写在Book
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.CharField(max_length=32)

    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)  # 留住,还有很多
    authors = models.ManyToManyField(to='Author')
	
    # 在表模型中设置
    def publish_detail(self):
        return {'name': self.publish.name, 'addr': self.publish.addr}

    def author_list(self):
        l = []
        for author in self.authors.all():
            l.append({'name': author.name, 'phone': author.phone})
        return l


class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=32)

    # 指定在book中现实publish的名字方案一,用的不多
    # def __str__(self):
    #     return self.name


class Author(models.Model):
    name = models.CharField(max_length=32)
    phone = models.CharField(max_length=11)

# views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from .serializer import FormatSerializer, FormatModelSerializer
from .models import Book, Publish, Author


class Format(APIView):
    def get(self, request):
        books = Book.objects.all()
        print(books)
        ser = FormatSerializer(instance=books, many=True)
        return Response({'code': 0, 'data': ser.data})

    def post(self, reuqest):
        ser = FormatSerializer(data=reuqest.data)
        if ser.is_valid():
            ser.save()
            return Response({'code': 0, 'data': ser.data})
        else:
            return Response({'code': 0, 'msg': '格式错误'})

class Format2(APIView):
    def put(self, request, pk):
        info = Book.objects.filter(pk=pk).first()
        ser = FormatSerializer(instance=info, data=request.data)
        if ser.is_valid():
            ser.save()
            return Response({'code': 0, 'data': ser.data})
        else:
            return Response({'code': 0, 'msg': ser.errors})
        
        
class FormatBook(APIView):
    def get(self, request):
        books = Book.objects.all()
        ser = FormatModelSerializer(instance=books, many=True)
        return Response({'code': 0, 'data': ser.data})

    def post(self, reuqest):
        ser = FormatModelSerializer(data=reuqest.data)
        if ser.is_valid():
            ser.save()
            return Response({'code': 0, 'data': ser.data})
        else:
            return Response({'code': 0, 'msg': '格式错误'})

class FormatBook2(APIView):
    def put(self, request, pk):
        info = Book.objects.filter(pk=pk).first()
        ser = FormatModelSerializer(instance=info, data=request.data)
        if ser.is_valid():
            ser.save()
            return Response({'code': 0, 'data': ser.data})
        else:
            return Response({'code': 0, 'msg': ser.errors})

4.2.1 使用Serializer

4.2.1.1 在 Serializer 中
# serializer.py

from rest_framework import serializers
from .models import Book, Publish, Author

class FormatSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=32)
    price = serializers.CharField(max_length=32)

    author = serializers.ListField(write_only=True)
    publish = serializers.CharField(write_only=True)

    # 在表模型中设置
    # author_list = serializers.ListField(read_only=True)
    # publish_dict = serializers.DictField(read_only=True)

    # 在 Serializer 中
    author_list = serializers.SerializerMethodField()
    def get_author_list(self, obj):
        lit = []
        for aut in obj.author.all():
            lit.append({'name': aut.name, 'phone': aut.phone, 'gender': aut.get_gender_display()})
        return lit

    publish_dict = serializers.SerializerMethodField()
    def get_publish_dict(self, obj):
        dit = {'name': obj.publish.name, 'addr': obj.publish.addr}
        return dit

    def create(self, validated_data):
        book = Book.objects.create(
            name=validated_data.get('name'),
            price=validated_data.get('price'),
            publish_id=validated_data.get('publish')
        )
        book.author.add(*validated_data.get('author'))
        return book

    def update(self, instance, validated_data):
        instance.name = validated_data.get('name')
        instance.price = validated_data.get('price')
        instance.publish_id = validated_data.get('publish')
        author = validated_data.get('author')
        instance.author.clear()
        instance.author.add(*author)
        instance.save()
        return instance
4.2.1.1 在表模型中设置
# serializer.py

from rest_framework import serializers
from .models import Book, Publish, Author

class FormatSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=32)
    price = serializers.CharField(max_length=32)

    author_list = serializers.ListField(read_only=True)
    publish_dict = serializers.DictField(read_only=True)
	
    # 在表模型中设置
    author = serializers.ListField(write_only=True)
    publish = serializers.CharField(write_only=True)

    def create(self, validated_data):
        book = Book.objects.create(
            name=validated_data.get('name'),
            price=validated_data.get('price'),
            publish_id=validated_data.get('publish')
        )
        book.author.add(*validated_data.get('author'))
        return book

    def update(self, instance, validated_data):
        instance.name = validated_data.get('name')
        instance.price = validated_data.get('price')
        instance.publish_id = validated_data.get('publish')
        author = validated_data.get('author')
        instance.author.clear()
        instance.author.add(*author)
        instance.save()
        return instance

4.2.2 使用ModelSerializer

4.2.2.1 在 Serializer 中
# myserializer.py

class FormatModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['name', 'price', 'author_list', 'publish_dict', 'author', 'publish']
        extra_kwargs = {
            'author_list': {'read_only': True},
            'publish_dict': {'read_only': True},
            'author': {'write_only': True},
            'publish': {'write_only': True},
        }
    
    # 在 Serializer 中
    author_list = serializers.SerializerMethodField()

    def get_author_list(self, obj):
        lit = []
        for aut in obj.author.all():
            lit.append({'name': aut.name, 'phone': aut.phone, 'gender': aut.get_gender_display()})
        return lit

    publish_dict = serializers.SerializerMethodField()

    def get_publish_dict(self, obj):
        dit = {'name': obj.publish.name, 'addr': obj.publish.addr}
        return dit
4.2.2.1 在表模型中设置
# myserializer.py

class FormatModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['name', 'price', 'author_list', 'publish_dict', 'author', 'publish']
        extra_kwargs = {
            'author_list': {'read_only': True},
            'publish_dict': {'read_only': True},
            'author': {'write_only': True},
            'publish': {'write_only': True},
        }

4.2.3 定义子序列化

在 Serializer 中使用,有一个大前提:Course 表中要存在与 teacher 一致的字段

子序列化

class TeacherModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Teacher
        fields = ['id', 'name', 'role', 'title', 'signature', 'image']

序列化

class CourseModelSerializer(serializers.ModelSerializer):
    teacher = TeacherModelSerializer()	# 引用子序列化
    class Meta:
        model = Course
        fields = ['id',
                  'name',
                  'course_img',
                  'brief',
                  'pub_date',
                  'period',
                  'attachment_path',
                  'students',
                  'sections',
                  'pub_sections',
                  'price',
                  'course_type_name',
                  'level_choices_name',
                  'status_choicese_name',
                  'teacher', # Course 表中要存在与 teacher 一致的字段
                  'course_category_dit'
        ]

4.3 使用ModelSerializer步骤

# 在模型类中写逻辑代码,称之为 ddd,领域驱动模型

### 使用继承 Serializer 的序列化类保存需要重写 create 方法
# 缺点
	1 在序列化中每个字段都要写,无论是序列化还是反序列化
    2 如果新增或者修改,在序列化类中都需要重写 create 或 update
    
# 解决这个缺点,使用ModelSerializer来做
1 定义一个类继承 ModelSerializer
2 类内部写内部内 class Meta:
3 在内部类中指定 model (要序列化的表)
4 在内部类中指定 fields (要序列化的字段,写__all__表示所有,不包含方法,写[一个个字段])
5 在内部类中指定 extra_kwargs,给字段添加字段参数的
6 在序列化类中,可以重写某个字段,优先使用你重写的
    name = serializers.SerializerMethodField()
	def get_name(self, obj):
    	return 'sb---' + obj.name
 
7 以后不需要重写 create 和 update 
	-ModelSerializer写好了,兼容性更好,任意表都可以直接存

4.4 反序列化数据校验源码分析

# 先校验字段自己的规则(最大,最小),走局部钩子校验,走全局钩子
# 局部:validate_name,全局叫:validate 为什么?

# 入口:从哪开始看,哪个操作,执行了字段校验ser.is_valid()
	-BaseSerializer内的is_valid()方法
	    def is_valid(self, *, raise_exception=False):
            if not hasattr(self, '_validated_data'):
                try:
                    # 真正的走校验,如果成功,返回校验过后的数据
                    self._validated_data = self.run_validation(self.initial_data)
                except ValidationError as exc:
            return not bool(self._errors)
    
    -内部执行了:self.run_validation(self.initial_data)---》本质执行的 Serializer 的
    	-如果你按住 ctrl 键,鼠标点击,会从当前类中找 run_validation,找不到会去父类找
    	-这不是代码的执行,代码执行要从头开始找,从自己身上再往上找
    	    def run_validation(self, data=empty):
                #局部钩子的执行
                value = self.to_internal_value(data)
                try:
                    # 全局钩子的执行,从根上开始找着执行,优先执行自己定义的序列化类中得全局钩子
                    value = self.validate(value)
                except (ValidationError, DjangoValidationError) as exc:
                    raise ValidationError(detail=as_serializer_error(exc))

                return value
  -全局钩子看完了,局部钩子---》 self.to_internal_value---》从根上找----》本质执行的Serializer的
     def to_internal_value(self, data):
        for field in fields: # fields:序列化类中所有的字段,for循环每次取一个字段对象
            # 反射:去self:序列化类的对象中,反射 validate_字段名 的方法
            validate_method = getattr(self, 'validate_' + field.field_name, None)
            try:
                # 这句话是字段自己的校验规则(最大最小长度)
                validated_value = field.run_validation(primitive_value)
                # 局部钩子
                if validate_method is not None:
                    validated_value = validate_method(validated_value)
            except ValidationError as exc:
                errors[field.field_name] = exc.detail

        return ret
    
 # 你自己写的序列化类---》继承了ModelSerializer---》继承了Serializer---》BaseSerializer---》Field

4.5 assert 断言

# 框架的源码中,大量使用断言
# assert :断言,作用的判断,断定一个变量必须是xx,如果不是就报错

# 你的土鳖写法
# name = 'lqz1'
# if not name == 'lqz':
#     raise Exception('name不等于lqz')
#
# print('程序执行完了')

# assert 的断言写法
name = 'lqz1'
assert name == 'lqz', 'name不等于lqz'
print('程序执行完了')

4.6 钩子

# 跟 forms 很像
	-字段自己的校验规则
    -局部钩子
    -全局钩子
    
# 字段自己的校验规则
	-如果继承的是Serializer,写法如下
    	name=serializers.CharField(max_length=8,min_length=3,error_messages={'min_length': "太短了"})
    -如果继承的是ModelSerializer,写法如下
       extra_kwargs = {
            'name': {'max_length': 8, 'min_length': 3, 'error_messages': {'min_length': "太短了"}},
        }
        
# 局部钩子
    -如果继承的是Serializer,写法一样
    -如果继承的是ModelSerializer,写法一样
	def validate_name(self, name):
        if name.startswith('sb'):
            # 校验不通过,抛异常
            raise ValidationError('不能以sb卡头')
        else:
            return name

# 全局钩子
	-如果继承的是Serializer,写法一样
    -如果继承的是ModelSerializer,写法一样
	def validate(self, attrs):
        if attrs.get('name') == attrs.get('publish_date'):
            raise ValidationError('名字不能等于日期')
        else:
            return attrs

七:请求与响应

1.Request 与 Response 类

1.1 Request

1.1.1 Request 前端编码格式

需求是该接口只能接收 json 格式,不能接收其他格式

# 总共有三个
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser

# 方式一:在继承自 APIView 及其子类的的视图类中配置(局部配置)
class BookView(APIView):
    parser_classes = [JSONParser,]
    
# 方法二:在配置文件中配置(影响所有,全局配置)
-django 有套默认配置,每个项目有个配置
-drf 有套默认配置,每个项目也有个配置 ---》就在 django 的配置文件中

REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': [
        # 'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        # 'rest_framework.parsers.MultiPartParser',
    ],
}

1.1.2 Request 属性和方法

request 类,在继承 APIView 后,请求对象时,每次都是一个重新封装后新的 request

# Request 类:属性与方法
- data:POST、PUT、PATCH、META 请求方式解析后的数据
	-- 原生 diango,put 数据提交后,在 request.POST 中取不到的(在 request.body 中)
- __getattr__
- query_params
其它的用法与之前的一样(FILES、method、path。。。)--->底层原理(__getattr__)

1.2 Response

1.2.1 Response的编码格式

drf 是 djagno 的一个 app,所以要注册

drf 做了个判断,如果是浏览器,默认使用模板美化界面,如果是 postman 只要 json 数据

 # 方式一:在视图类中写(局部配置)
-两个响应类 ---》找 ---》drf 的配置文件中找 --》两个类
-from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer   class BookView(APIView):
    renderer_classes=[JSONRenderer,]

# 方式二:在项目配置文件中写(全局配置)
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
}

1.2.2 Resposne 属性或方法

# drf 的Response 源码分析
	-from rest_framework.response import Response
    -视图类的方法返回时,retrun Response ,走它的__init__, init 中可以传什么参数
    
# Response init 可以传的参数
    def __init__(self, 
                 data=None, 
                 status=None,
                 template_name=None, 
                 headers=None,
                 exception=False, 
                 content_type=None)
    
   -data:之前咱们写的 ser.data  可以是字典或列表,字符串 ---》序列化后返回给前端 ---》前端在响应体中看到的就是这个 

   -status:http 响应的状态码,默认是 200,你可以改
        -drf 在 status 包下,把所有 http 响应状态码都写了一遍,常量
        -from rest_framework.status import HTTP_200_OK
        -Response('dddd',status=status.HTTP_200_OK)
        
   -template_name:了解即可,修改响应模板的样子,BrowsableAPIRenderer 定死的样子,后期公司可以自己定制

   -headers:响应头,http 响应的响应头
    	-原生 djagno,如何像响应头中加东西
            # 四件套 render,redirect,HttpResponse,JsonResponse
            obj = HttpResponse('dddd')
            obj['xxc'] = 'yyc'
            return obj

   -content_type :响应编码格式,一般不动
     
  # 重点:data,status,headers

2 执行小结

-总结:
	-解析类的使用顺序:优先用视图类自己的,然后用项目配置文件,最后用内置的配置文件

-实际项目如何配置
	-基本上都运行 JSONParser, FormParser
	-如果上传文件只允许 MultiPartParser

八:视图基类

视图类中:
	通过重写 get_serializer,达到不同方法使用的序列化类不一样
    通过重写 get_queryset,达到不同方法使用的数据不一样
    通过重写 perform_destroy,达到控制某些能删,某些不能删除的目的

1. 2 个视图基类

from rest_framework.views import APIView
from rest_framework.generics import GenericAPIView
# APIView
# GenericAPIView

# GenericAPIView:属性和方法
-- 属性
	queryset = None			# 要序列化的数据 
    serializer_class = None	# 序列化类
    如:
        queryset = User.objects.all()
        serializer_class = UserSerializer
    ----- 了解 -----
    lookup_field		# 通过 get_object 获取单个对象的查询 key 值,value 值是路由中传进来的
    filter_backends		# 过滤类
    pagination_class 	# 分页类
-- 方法
	self.get_queryser()		# 获取要序列化的数据
    self.get_object() 		# 根据 lookup_field 配置的参数获取单个对象
    self.get_serializer()	# 获取序列化类
    self.get_serializer_class()	# 获取序列化类,get_serializer 调用了它
    ----- 了解 -----
    self.filter_queryset		# 用于过滤
    self.paginate_xxx		# 跟分页有关
	

2. 5 个视图扩展类

必须配合 GenericAPIView 使用,不能配合 APIView 使用

from rest_framework.mixins import RetrieveModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, ListModelMixin

# RetrieveModelMixin:写了一个 Create 方法,就是原来咱们 post 中的代码
# RetrieveModelMixin:retrieve,就是咱们原来的 get
# UpdateModelMixin:update 方法,就是咱们原来的 put
# ListModelMixin:list 方法,就是原来咱们的 get
# DestroyModelMixin:destroy 方法,就是原来咱们的 delete


class UserView(GenericAPIView, ListModelMixin, CreateModelMixin):
    # 配置两个 类属性
    queryset = User.objects.all()
    serializer_class = UserSerializer

    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 UserDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    def get(self,request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args,**kwargs):
        return self.update(request,*args,**kwargs)

    def delete(self, request, *args,**kwargs):
        return self.destroy(request,*args,**kwargs)

3. 9 个视图子类

继承 GenericAPIView+ 某个或某几个视图扩展类

from rest_framework.generics import ListAPIView,CreateAPIView, RetrieveAPIView,DestroyAPIView,UpdateAPIView
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView,RetrieveUpdateAPIView,RetrieveDestroyAPIView


# 正常来讲  Destroy+Update 应该有一个,作者没加
class UserView(ListCreateAPIView):
    # 配置两个 类属性
    queryset = User.objects.all()
    serializer_class = UserSerializer

class UserDetailView(RetrieveUpdateDestroyAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

4. 视图集

4.1 继承ModelViewSet编写5个接口

# views.py
from rest_framework.viewsets import ModelViewSet,ReadOnlyModelViewSet
class UserView(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

# urls.py
path('user/', views.UserView.as_view({'get': 'list', 'post': 'create'})),
path('user/<int:pk>', views.UserView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),

4.2 继承ReadOnlyModelViewSet编写2个接口

# views.py
from rest_framework.viewsets import ModelViewSet,ReadOnlyModelViewSet
class UserView(ReadOnlyModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

# urls.py
path('user/', views.UserView.as_view({'get': 'list', 'post': 'create'})),

4.3 源码分析 ViewSetMixin

# 请求来了,路由匹配成功---》get请求,匹配成功books,会执行  views.BookView.as_view({'get': 'list', 'post': 'create'})()------>读as_view【这个as_view是ViewSetMixin的as_view】

    @classonlymethod
    def as_view(cls, actions=None, **initkwargs):
        # 如果没有传actions,直接抛异常,路由写法变了后,as_view中不传字典,直接报错
        if not actions:
            raise TypeError("The `actions` argument must be provided when "
                            "calling `.as_view()` on a ViewSet. For example "
                            "`.as_view({'get': 'list'})`")
		# 。。。。其他代码不用看
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if 'get' in actions and 'head' not in actions:
                actions['head'] = actions['get']
            self.action_map = actions
            for method, action in actions.items():
                handler = getattr(self, action)
                setattr(self, method, handler)

            return self.dispatch(request, *args, **kwargs)
        # 去除了csrf校验
        return csrf_exempt(view)
    
    
# 路由匹配成功执行views.BookView.as_view({'get': 'list', 'post': 'create'})()----》本质执
行ViewSetMixin----》as_view----》内的view()---》代码贴过来
    def view(request, *args, **kwargs):
            #actions 是传入的字典--->{'get': 'list', 'post': 'create'}
            self.action_map = actions
            # 第一次循环:method:get,action:list
            # 第一次循环:method:post,action:create
            for method, action in actions.items():
                # 反射:去视图类中反射,action对应的方法,action第一次是list,去视图类中反射list方法
                # handler就是视图类中的list方法
                handler = getattr(self, action)
                # 反射修改:把method:get请求方法,handler:list
                # 视图类的对象的get方法,变成了list
                setattr(self, method, handler)

            return self.dispatch(request, *args, **kwargs) #dispatch是APIView的
        
        
        
 # 总结:
	-1 只要继承ViewSetMixin的视图类,路由写法就变了(重写了as_veiw)
    -2 变成需要需要传入字典映射方法:{'get': 'list', 'post': 'create'}
    	-只要传入actions,以后访问get就是访问list,访问post,就是访问create
    -3 其他执行跟之前一样 
    -4 以后视图类类中的方法名,可以任意命名,只要在路由中做好映射即可【重要】

4.4 小结

'''
from rest_framework.viewsets下有这几个类
ModelViewSet:5个试图扩展类+ViewSetMixin+GenericAPIView
ReadOnlyModelViewSet::2个试图扩展类+ViewSetMixin+GenericAPIView   只读的两个
ViewSetMixin:魔法,重新了as_view,只要继承他,以后路由写法变成了映射方法
ViewSet:ViewSetMixin+ APIView
GenericViewSet:ViewSetMixin+ GenericAPIView
'''


#重点:
	以后,你想继承APIView,但是想变路由写法【视图类中方法名任意命名】,要继承ViewSet
    以后,你想继承GenericAPIView,但是想变路由写法【视图类中方法名任意命名】,要继承GenericViewSet

5.视图类继承关系

Processon 链接:https://www.processon.com/view/link/63e0cff310f39607276b731f

九:路由

1.自动生成路由

# 导入
from rest_framework.routers import SimpleRouter, DefaultRouter
# 实例化
router = SimpleRouter()
# 注册 register(prefix, viewset, basename) 前缀、视图集、基名
router.register('user', views.UserView, 'user')


urlpatterns = [
    # 加入到 urlpatterns 中(两种方式:二)
    path('', include(router.urls)),
]

# 加入到 urlpatterns 中(两种方式:一)
urlpatterns += router.urls
1 自动生成的路由映射关系其实定死了	
	-本质是自动做映射,能够自动成的前提是,视图类中要有 5个方法的某要给或多个
	   get--->list
	   get---->retrieve
	   put---->update
	   post---->create
	   delete---->destory
	-ModelViewSet,ReadOnlyModelViewSet可以自动生成
	-9个试图子类+配合ViewSetMixin   才可以自动生成
	-GenericAPIView+5个试图扩展类+配合ViewSetMixin   才能自动生成

2 以后写的视图类不需要写action装饰器的话,视图类中必须要有
	-list,destroy,retrieve,create,update方法之一
    -其实是必须是 5 个视图扩展类之一+GenericAPIView   9个视图子类,ModelViewSet
    
3 SimpleRouter 和 DefaultRouter
	-DefaultRoute r比SimpleRouter多一个根路径,显示所有注册过的路由

2.action 装饰器的使用

# 在视图函数中,会有一些其它名字的方法,必须要使用 action 装饰器做映射
    # methods:支持的请求方式,列表
    # detail:默认是False 
    	-False,不带id的路径:send/send_sms/
        -True,带id的路径:send/2/send_sms/
    # url_path: 控制生成的/user/后的路径是什么,如果不写,默认以方法名命名 /user/login/,一般跟函数名同名即可
    # url_name:别名,用于反向解析
    
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from rest_framework.response import Response

from app01 import models
import uuid

class UserNameView(ViewSet)
    @action(methods=['GET', 'POST'], detail=False, url_path='login', url_name='login')
    def login(self, request):
        if request.method == 'POST':
            token = str(uuid.uuid4())
            username = request.data.get('username')
            password = request.data.get('password')
            models.User.objects.update_or_create(defaults={'token': token}, username=username, password=password)
            return Response(username)
        users = models.User.objects.all().values('username')
        return Response(users)

# 以后看到的 drf 路由写法
	后期,都是自动生成,一般不在urlpatterns 加入路由了

 # 补充:
	-1 不同请求方式可以使用不同序列化类
    -2 不同action使用不同序列化类
class SendView(GenericViewSet):
    queryset = None
    serializer_class = '序列化类'

    def get_serializer(self, *args, **kwargs):
        if self.action=='lqz':
            return '某个序列化类'
        else:
            return '另一个序列化列'
    @action(methods=['GET'], detail=True)
    def send_sms(self, request,pk):
        print(pk)
        # 手机号,从哪去,假设get请求,携带了参数
        phone = request.query_params.get('phone')
        print('发送成功,%s' % phone)
        return Response({'code': 100, 'msg': '发送成功'})

    @action(methods=['GET'], detail=True)
    def lqz(self,request):  # get
        # 序列化类
        pass

    @action(methods=['GET'], detail=True)
    def login(self,request):  # get
        # 序列化类
        pass

3.路由小结

# 1 drf路由的使用
	-路由有三种写法
    	-视图类.as_view()
        -视图类.as_view({'get':'list'})
        -自动生成路由
        	from rest_framework.routers import SimpleRouter, DefaultRouter
        	-导入--实例化--注册--加入到 urlpatterns
            -类有两个:SimpleRouter, DefaultRouter(可以显示根目录)
            -加入到 urlpatterns 的两种方式:列表加操作,include
    -路由的第二种和第三种写法,视图类必须继承 ViewSetMixin ---》重写了类方法as_view
    -要自动生成:其实自动生成的是定死的{'get':'list','post':'create','delete':destroy,put:update,get:retrieve}
    -如果你完全自动生成路由,视图类必须有:list,create....
    -可以继承 ViewSetMixin + GenericAPIView + 5个视图扩展类
    -继承 ViewSetMixin + 9 个视图子类
    
    -视图类中得其他方法必须使用 action 装饰器来装饰,才能自动生成路由
    /user/login
    @action(methods=['post','get'],detail=False,url_path='login')

十:认证

1.实现步骤

# 通过认证类完成,使用步骤
	1 写一个认证类,继承 BaseAuthentication
    2 重写 authenticate 方法,在内部做认证
    3 如果认证通过,返回两个值【返回 None 或两个值】
    4 认证不通过抛 AuthenticationFailed 异常
    5 只要返回了两个值,在后续的 request.user 就是当前登录用户
    6 局部使用和全局使用
    	-方式一:局部配置
        	class BookView(ModelViewSet):
    			authentication_classes = [LoginAuth,]
        -方式二:全局配置
        	REST_FRAMEWORK={
            	'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.LoginAuth',]
        	}
            
         -局部禁用:
        	authentication_classes = []

2.代码实例

# auth.py

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed

class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        """
        Authenticate the request and return a two-tuple of (user, token).
        """
        # 在这里实现认证,如果是登录的,继续往后走返回两个值,如果不是抛异常
        # 请求中是否携带 token,判断是否登录,放在地址栏中
        token = request.GET.get('token')
        if token:
            user_token = models.User.objects.filter(token=token).first()
            if user_token:
                return user_token.username, token
            else:
                raise AuthenticationFailed('token 认证失败')
        else:
            raise AuthenticationFailed('token 没传')
# views.py

from app01.auth import LoginAuth
# 查询所有
class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer


# 查询单个
class BookDetailView(ViewSetMixin, RetrieveAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    authentication_classes = [LoginAuth, ]  # 需要写一个认证类,需要咱们写

# urls.py

router.register('books', views.BookView, 'books')
router.register('books', views.BookDetailView, 'books')

3.认证配置

# 局部配置
    authentication_classes = [LoginAuth,]
# 局部禁用
    authentication_classes = []
# 全局配置
    REST_FRAMEWORK={
        'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.LoginAuth',]
    }

4.源码分析

# 之前读过:drf的apiview,在执行视图类的方法之前,执行了3大认证----》dispatch方法中的
    -497行左右, self.initial(request, *args, **kwargs)---》执行3大认证
    
# APIView类的399行左右:
    def initial(self, request, *args, **kwargs):
        # 能够解析的编码,版本控制。。。。
        self.format_kwarg = self.get_format_suffix(**kwargs)
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

     	# 认证组件的执行位置【读它】
        self.perform_authentication(request)
        # 权限组件 
        self.check_permissions(request)
        # 频率组件
        self.check_throttles(request)
        
        
 # APIView的316行左右
    def perform_authentication(self, request):
        request.user #咱们觉得它是个属性,其实它是个方法,包装成了数据属性
        
        
 # Request类的user方法   219行左右
    @property
    def user(self):
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user
    
    
 # self 是Request的对象,找Request类的self._authenticate()   373 行
    def _authenticate(self):
        # self.authenticators 我们配置在视图类上认证类的一个个对象,放到列表中
        # Request类初始化的时候,传入的
        for authenticator in self.authenticators:
            try:
                # 返回了两个值,第一个是当前登录用户,第二个的token,只走这一个认证类,后面的不再走了
                # 可以返回None,会继续执行下一个认证类
                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是当次请求的新的Request的对象
                #self.auth=token
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()
        
        
        
 # self.authenticators  去Request类的init中找     152行左右
    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        .....
        self.authenticators = authenticators or ()
		.....
        
        
 # 什么时候调用Reqeust的__init__?---》APIVIew的dispatch上面的492行的:request = self.initialize_request(request, *args, **kwargs)-----》385行----》
    def initialize_request(self, request, *args, **kwargs):
        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
    
    
	-APIView 的 --get_authenticators()----》return [auth() for auth in self.authentication_classes]
    
    -self.authentication_classes:视图类中配置的一个个的认证类的列表,如果没配,配置文件中,内置配置文件中
    
    
    
 # 总结:
	1 配置在视图类上的认证类,会在执行视图类方法之前执行,在权限认证之前执行
    2 自己写的认证类,可以返回两个值或 None
    3 后续可以从 request.user 取出当前登录用户(前提是你要在认证类中返回)

十一:权限

1.权限步骤

# 使用步骤
	-第一步:写一个类,继承BasePermission
    -第二步:重写 has_permission 方法,在该方法在中实现权限认证,在这方法中,request.user 就是当前登录用户
    -第三步:在方法中校验用户是否有权限(request.user就是当前登录用户)
    -第四步:如果有权限,返回True,没有权限,返回False
    -第五步:self.message 是给前端的提示信息
    -第六步:局部使用,全局使用,局部禁用

2.实例代码

# Permission.py

from rest_framework.permissions import BasePermission


class UserBasePermission(BasePermission):
    # self 中没有找类属性,类属性中没有找父类
    # message = f'用户没有该权限'

    def has_permission(self, request, view):
        if request.user.user_type == 1:
            return True
        else:
            self.message = f'{request.user.get_user_type_display()},没有该权限'
            return False

3.配置权限

# 全局配置
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': ['app01.permission.UserBasePermission', ]
}

# 局部配置
from app01.permission import UserBasePermission

permission_classes = [UserBasePermission, ]

4.源码分析

# 权限的源码执行流程
	-写一个权限类,局部使用,配置在视图类的,就会执行权限类的has_permission方法,完成权限校验
    
    
# 之前读过:drf的apiview,在执行视图类的方法之前,执行了3大认证----》dispatch方法中的
    -497行左右, self.initial(request, *args, **kwargs)---》执行3大认证
    
# APIView类的399行左右:
    def initial(self, request, *args, **kwargs):
        # 能够解析的编码,版本控制。。。。
        self.format_kwarg = self.get_format_suffix(**kwargs)
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

     	# 认证组件的执行位置
        self.perform_authentication(request)
        # 权限组件  [读它]
        self.check_permissions(request)
        # 频率组件
        self.check_throttles(request)
        
        
        
# APIView的326 左右
    def check_permissions(self, request):
        # self.get_permissions()----》[CommonPermission(),]
        # permission 是我们配置在视图类上权限类的对象
        for permission in self.get_permissions():
            # 权限类的对象,执行has_permission,这就是为什么我们写的权限类要重写has_permission方法	
            # self 是视图类的对象,就是咱们自己的的权限类的has_permission的view参数
            if not permission.has_permission(request, self):
                # 如果return 的是False,就会走这里,走这里是,没有权限
                # 如果配了多个权限类,第一个没过,直接不会再执行下一个权限类了
                self.permission_denied(
                    request,
                    message=getattr(permission, 'message', None),
                    code=getattr(permission, 'code', None)
                )
                
                
# APIView的274行左右  get_permissions
    def get_permissions(self):
        # self.permission_classes  是咱们配置在视图类上的列表,里面是一个个的权限类,没加括号
        # permission_classes = [CommonPermission]
        # [CommonPermission(),]   本质返回了权限类的对象,放到列表中
        return [permission() for permission in self.permission_classes]
    
    
    
    
# 总结:
	-APIView---dispatch----》initial---》倒数第二行---》self.check_permissions(request)
    	里面取出配置在视图类上的权限类,实例化得到对象,一个个执行对象的has_permission方法,如果返回False,就直接结束,不再继续往下执行,权限就认证通过
        
    -如果视图类上不配做权限类:permission_classes = [CommonPermission],会使用配置文件的api_settings.DEFAULT_PERMISSION_CLASSES
    优先使用项目配置文件,其次使用drf内置配置文件

十二:频率

1.频率步骤

# 使用步骤:
	-第一步:写一个类:继承SimpleRateThrottle
    -第二步:重写 get_cache_key,返回唯一的字符串,会以这个字符串做频率限制
    -第三步:写一个类属性 scope='随意写',必须要跟配置文件对象
    -第四步:配置文件中写
      'DEFAULT_THROTTLE_RATES': {
        '随意写': '3/m'  # 3/h  3/s  3/d
    	}
    -第五步:局部配置,全局配置,局部禁用

2.实例代码

# rates.py
from rest_framework.throttling import SimpleRateThrottle, BaseThrottle

class Login_Rate(SimpleRateThrottle):
    scope = 'sumber'
    def get_cache_key(self, request, view):
        # 返回什么,频率就以什么做限制
        # 可以通过用户 id 限制
        # 可以通过 ip 地址限制
        return request.META.get('REMOTE_ADDR')
    
# settings.py
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        'sumber': '3/m'
    }
}

3.配置频率

# 全局配置
'DEFAULT_THROTTLE_CLASSES': [app01.rates.Login_Rate, ]

# 局部配置
from app01.rates import Login_Rate

throttle_classes = [Login_Rate, ]

4.自定义频率类

class SuperThrottle(BaseThrottle):
    VISIT_RECORD = {}

    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        # 自己写逻辑,判断是否超频
        # (1)取出访问者ip
        # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走 {ip地址:[时间1,时间2,时间3,时间4]}
        # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        # (1)取出访问者ip
        ip = request.META.get('REMOTE_ADDR')
        import time
        ctime = time.time()
        # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
        if ip not in self.VISIT_RECORD:
            self.VISIT_RECORD[ip] = [ctime, ]
            return True
        # self.history  = [时间1]
        self.history = self.VISIT_RECORD.get(ip,[])
        # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        while self.history and ctime - self.history[-1] > 60:
            self.history.pop()
        # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        if len(self.history) < 3:
            self.history.insert(0, ctime)
            return True
        else:
           
            return False

    def wait(self):
        import time
        ctime = time.time()
        return 60 - (ctime - self.history[-1])

5.源码分析

5.1 check_throttles 源码

# 之前读过:drf的apiview,在执行视图类的方法之前,执行了3大认证----》dispatch方法中的
    -497行左右, self.initial(request, *args, **kwargs)---》执行3大认证
    
# APIView类的399行左右:
    def initial(self, request, *args, **kwargs):
        # 能够解析的编码,版本控制。。。。
        self.format_kwarg = self.get_format_suffix(**kwargs)
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

     	# 认证组件的执行位置
        self.perform_authentication(request)
        # 权限组件 
        self.check_permissions(request)
        # 频率组件【读它】
        self.check_throttles(request)
        
        
# APIView 的352行
    def check_throttles(self, request):
        throttle_durations = []
        #self.get_throttles() 配置在视图类上的频率类的对象,放到列表中
        # 每次取出一个频率类的对象,执行allow_request方法,如果是False,频率超了,不能再走了
        # 如果是True,没有超频率,可以直接往后
        for throttle in self.get_throttles():
            if not throttle.allow_request(request, self):
                throttle_durations.append(throttle.wait())

        if throttle_durations:
            # Filter out `None` values which may happen in case of config / rate
            # changes, see #1438
            durations = [
                duration for duration in throttle_durations
                if duration is not None
            ]

            duration = max(durations, default=None)
            self.throttled(request, duration)
            
            
            
# 总结:
	-我们写的频率类:继承BaseThrottle,重写allow_request,在内部判断,如果超频了,就返回False,如果没超频率,就返回True

5.2 SimpleRateThrottle 源码

# SimpleRateThrottle
	-源码里执行的频率类的allow_request,读SimpleRateThrottle的allow_request
    
class SimpleRateThrottle(BaseThrottle):
    cache = default_cache
    timer = time.time
    cache_format = 'throttle_%(scope)s_%(ident)s'
    scope = None
    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES
    def __init__(self):  # 只要类实例化得到对象就会执行,一执行,self.rate就有值了,而且self.num_requests和self.duration
        if not getattr(self, 'rate', None): # 去频率类中反射rate属性或方法,发现没有,返回了None,这个if判断就符合,执行下面的代码
            self.rate = self.get_rate()  #返回了  '3/m'
        #  self.num_requests=3
        #  self.duration=60
        self.num_requests, self.duration = self.parse_rate(self.rate)

    def get_rate(self):
         return self.THROTTLE_RATES[self.scope] # 字典取值,配置文件中咱们配置的字典{'ss': '3/m',},根据ss取到了 '3/m'

    def parse_rate(self, rate):
        if rate is None:
            return (None, None)
        # rate:字符串'3/m'  根据 / 切分,切成了 ['3','m']
        # num=3,period=m
        num, period = rate.split('/')
        # num_requests=3  数字3
        num_requests = int(num)
        # period='m'  ---->period[0]--->'m'
        # {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        # duration=60
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        # 3     60
        return (num_requests, duration)

    def allow_request(self, request, view):
        if self.rate is None:
            return True
        # 咱们自己写的,返回什么就以什么做限制  咱们返回的是ip地址
        # self.key=当前访问者的ip地址
        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True
        # self.history 访问者的时间列表,从缓存中拿到,如果拿不到就是空列表,如果之前有 [时间2,时间1]
        self.history = self.cache.get(self.key, [])
        # 当前时间
        self.now = self.timer()
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()
        return self.throttle_success()
    
    
  # 总结:以后要再写频率类,只需要继承SimpleRateThrottle,重写get_cache_key,配置类属性scope,配置文件中配置一下就可以了

十三:分页

查询所有的接口才需要分页。分页后端写法是固定的,前端展现形式是不一样的。

  • pc 端的下一页的点击

  • app 中,翻页是下拉加载更多

1.分页步骤

# drf 中分页的使用:
	- 写一个类,继承 drf 提供的三个分页类之一 
        - PageNumberPagination, CursorPagination, LimitOffsetPagination
    - 重写某几个类属性
    - 把它配置在继承自 GenericAPIView + ListModelMixin 的子视图类上
    - 如果继承的是 APIView,需要自己写
    	page = MyPageNumberPagination()
        res = page.paginate_queryset(qs, request)

2.实例代码

# pagination.py
from rest_framework.pagination import PageNumberPagination, CursorPagination, LimitOffsetPagination

# PC端 常用
class BookPageNumberPagination(PageNumberPagination):
    page_size = 2    # 每页显示的条数
    # Client can control the page using this query parameter.
    page_query_param = 'page'   # /books/?page=3  查询第几页的参数
    # Client can control the page size using this query parameter.
    # Default is 'None'. Set to eg 'page_size' to enable usage.
    page_size_query_param = 'size'  # /books/?page=3&size=4  # 查询第三页,每页显示4条
    # Set to an integer to limit the maximum page size the client may request.
    # Only relevant if 'page_size_query_param' has also been set.
    max_page_size = 10  # 限制通过size查询,最大的条数
  
# 偏移分页
class BookLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 3  # 每页显示的条数
    limit_query_param = 'limit'  # /books/?limit=4   这一页显示4条数据
    offset_query_param = 'offset'  # /books/?offset=3&limit=4  # 从第3条开始,取4条数据
    max_limit = 5  # 限制最多显示多少条
  
# APP 大数据量 常用	游标分页
class BookCursorPagination(CursorPagination): # 只能上一页和下一页,它的分页效率是最高的,高于上面所有的分页方式,大数据量的分页,建议使用这种
    cursor_query_param = 'cursor'
    page_size = 3  #每页显示条数
    ordering = 'id'  # 排序,必须是表中得字段

3.配置分页

from rest_framework.viewsets import ViewSet, ModelViewSet
from app01 import pagination
class BookView(ModelViewSet):
    # 局部配置
    pagination_class = pagination.BookLimitOffsetPagination

    queryset = models.Book.objects.all()
    serializer_class = serializr.BookSerializer

 
# 全局配置
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS':['app01.drf.page.ListLimitOffsetPagination', ],
}

4.基于APIView编写分页

# page.py

from rest_framework.pagination import LimitOffsetPagination

class ListLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 3  # 每页显示的条数
    limit_query_param = 'limit'  # /books/?limit=4   这一页显示4条数据
    offset_query_param = 'offset'  # /books/?offset=3&limit=4  # 从第3条开始,取4条数据
    max_limit = 5  # 限制最多显示多少条
# views.py

# 分页功能,只有查询所有才有
from rest_framework.viewsets import ViewSet
from .drf.page import ListLimitOffsetPagination


class CarModelView2(ViewSet):
    def list(self, request):
        carmodel = CarModel.objects.all()
        paginator = ListLimitOffsetPagination()
        page = paginator.paginate_queryset(carmodel, request, self)
        if page is not None:
            serializer = CarModelSerializers(instance=page, many=True)
            return Response({
                'total': paginator.count,
                'next': paginator.get_next_link(),
                'previous': paginator.get_previous_link(),
                'results': serializer.data
            })

5.源码分析

十四:排序

查询所有才涉及到排序,其它接口不需要

# 必须是继承 GenericAPIView + ListModelMixin 的子视图类上
	-配置排序类:
    filter_backends=[OrderingFilter,]
    -配置排序的字段
    ordering_fields=['id','price']
    -支持前端的访问形式
    http://127.0.0.1:8000/books/?ordering=-price,id # 先按价格的降序排,如果价格一样再按id的升序排
    
# 纯自己写的,继承了APIView的,需要自己从请求地址中取出排序规则,自己排序
	-'price','-id'=reqeust.query_params.get('ordering').split(',')
    -qs = Book.objects.all().order_by('price','-id')
    
    
 # 分页和排序能一起用,但是是先排序后分页的
# 请求方式
http://127.0.0.1:8000/drf/book/?page=1&size=5&ordering=price,id&token=a5e72741-8e49-4a5e-90be-7333d005a01a

# views.py
from rest_framework.viewsets import ViewSet, ModelViewSet
from rest_framework.filters import OrderingFilter, SearchFilter
class BookView(ModelViewSet):
    # 分页
    pagination_class = pagination.BookPageNumberPagination
    # 排序
    filter_backends = [OrderingFilter, ]
    ordering_fields = ['id', 'price']

    queryset = models.Book.objects.all()
    serializer_class = serializr.BookSerializer

十五:过滤

1.内置过滤

# 查询所有才涉及到过滤,其它接口都不需要
# restful 规范中有一条,请求地址中带过滤条件:分页,排序,过滤统称为过滤
# 使用内置过滤类使用步骤   查询所有才涉及到排序,其它接口都不需要
	必须是继承GenericAPIView+ListModelMixin的子视图类上
        -配置过滤类:
        filter_backends=[SearchFilter,]
        -配置过滤的字段
        ordering_fields=['name','publish']
        -支持前端的访问形式
        http://127.0.0.1:8000/books/?search=三 # 只要 name 中或 publish 中有三都能搜出来
# 内置过滤类只能通过 search 写条件,如果配置了多个过滤字段,是或者的条件
        
 # 不够用:
	-第三方:过滤类
    -自己写:自己写过滤类
# 请求方式
http://127.0.0.1:8000/drf/book/?page=1&size=5&search=爱&token=a5e72741-8e49-4a5e-90be-7333d005a01a

# views.py
from rest_framework.filters import OrderingFilter, SearchFilter
class BookView(ModelViewSet):
    # 分页
    pagination_class = pagination.BookPageNumberPagination
    # 指定过滤类
    filter_backends=[SearchFilter,]
    # 指定过滤字段
    search_fields=['name']

    queryset = models.Book.objects.all()
    serializer_class = serializr.BookSerializer

2.三方过滤(重点)

内置的过滤类 ,只能通过 ?search= 搜索条件,不能指定 name 或 publish 来搜索。

多个过滤类和排序类可以共用,filter_backends=[],可以配置多个,执行顺序是从做往右,所以,放在最左侧的尽量先过滤掉大部分数据

# 面试官问你,你在工作中遇到的问题及如何解决的?
	-我在写 xx 接口的时候,因为我们过滤条件很多,搜索数据库很慢,写了很多搜索类,之前时候搜索类的排序是随意的,搜索效率比较低,后来,读了 drf 搜索类的源码,发现执行顺序是从左往右,我就想到了,最左侧的搜索类,尽量搜索速度快,并且过滤掉的数据多,后续再搜索就会更快,所有我调整了一下搜索类的配置顺序,发现该接口的效率有所提升

2.1 精确过滤步骤

安装:pip3 install django-filter

from django_filters.rest_framework import DjangoFilterBackend

# 1 配置在视图类中配置:filter_backends = [DjangoFilterBackend, ]
# 2 配置搜索的字段:filterset_fields = ['name', 'publish']
# 3 支持的搜索方式:http://127.0.0.1:8008/books/?name=三国演义&publish=南京出版社
	- 精准搜索,条件是并且的关系
from rest_framework.viewsets import ViewSet, ModelViewSet
from django_filters.rest_framework import DjangoFilterBackend
class BookView(ModelViewSet):
    # 过滤
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['name', 'price']

    queryset = models.Book.objects.all()
    serializer_class = serializr.BookSerializer
    

# 支持的查询方式
http://127.0.0.1:8000/api/v1/books/?price=939
http://127.0.0.1:8000/api/v1/books/?price=939&name=细雨中的呼喊

3.自定义过滤类(重点)

3.1 自定义内置模糊过滤类

# 1 写一个类,继承 BaseFilterBackend
# 2 重写方法:filter_queryset,在该方法中实现过滤,返回 qs 对象
def filter_queryset(self, request, queryset, view):
    # 在这里面实现过滤,一定要返回 qs 对象,过滤后的数据
    name = request.query_params.get('name', None)
    publish = request.query_params.get('publish', None)
    qs = queryset.filter(name__icontains=name, publish__icontains=publish)
    return qs
# 3 配置在视图类上
filter_backends = [MyFilter, ]
# filters.py

from rest_framework.filters import BaseFilterBackend

class BookFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        name_val = request.query_params.get('name', None)
        qs = queryset.filter(name__icontains=name_val)
        return qs
    
    
# views.py
from rest_framework.viewsets import ViewSet, ModelViewSet
from .filters import BookFilter

class BookView(ModelViewSet):
    # 自定义过滤
    filter_backends = [BookFilter, ]

    queryset = models.Book.objects.all()
    serializer_class = serializr.BookSerializer

3.2 自定义三方模糊过滤类

4.排序和过滤的源码分析

# 继承了 GenericAPIView+ListModelMixin,只要在视图类中配置 filter_backends 它就能实现过滤和排序
	-drf内置的过滤类(SearchFilter),排序类(OrderingFiler)
    -django-filter
    -自定义:写一个类,继承BaseFilterBackend,重写filter_queryset,返回的qs对象,就是过滤或排序后的
    
    
# 只有获取多有才涉及到排序
	-list方法
    def list(self, request, *args, **kwargs):
        # self.get_queryset()所有数据,经过了self.filter_queryset返回了qs
        # self.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)
	   # 如果没有分页,走正常的序列化,返回
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)
    
    
    -self.filter_queryset完成了过滤,当前在视图类中,self是视图类的对象,去视图类中找没找到,去父类---》GenericAPIView---》filter_queryset
    
        def filter_queryset(self, queryset):
            for backend in list(self.filter_backends):
                queryset = backend().filter_queryset(self.request, queryset, self)
            return queryset
        
   
  # 总结:
	-写的过滤类要重写 filter_queryset,返回 qs(过滤或排序后)对象
    -后期如果不写过滤类,只要在视图类中重写 filter_queryset,在里面实现过滤也可以

十六:全局异常处理

drf 中无论在三大认证还是视图类的方法中执行,只要报错(主动抛异常),都能执行一个函数(异常处理的函数),只要除了异常在 APIView 的 dispatch 中捕获了

1.实现步骤

# drf 中分页的使用:
	- 写一个方法,传入 (exc, context) 两个方法 
    - 调用异常处理函数 exception_handler(exc, context) 得到返回值
    	-- from rest_framework.views import exception_handler
    - 有值则为 drf 的报错,无值则为 django 的报错
    - 返回固定的 json 返回值格式
    - 将错误信息记录日志

2.实例代码

# 自己写一个函数,配置在配置文件中,以后出了异常,它就走咱们写的函数了
	-1 统一返回格式 {'code':10001,'msg':错误信息}
    -2 记录日志
# exception.py
from rest_framework.views import exception_handler
from rest_framework.response import Response
import datetime

def drf_exception_handler(exc, context):
    """
    Returns the response that should be used for any given exception.

    By default we handle the REST framework `APIException`, and also
    Django's built-in `Http404` and `PermissionDenied` exceptions.

    Any unhandled exceptions may return `None`, which will cause a 500 error
    to be raised.
    """
    # 记录日志:只要进入到这里面,一定是出了异常,只要出异常就要记录日志
    request = context.get('request')	# 当此请求的 request
    username = request.user.username
    new_time = datetime.datetime.now
    msg = f'''
    时间: {new_time()}
    登录用户id: {request.user.username}
    用户ip: {request.META.get('REMOTE_ADDR')}
    请求方式: {request.method}
    请求地址: {request.path}
    执行的视图类: {context.get('view')}
    错误原因: {exc}'''
    print(msg)
     # 后期以上的代码要写到日志中
    
    # 统一返回格式
    response = exception_handler(exc, context)	# 内置的这个异常处理函数,只处理了 drf 自己的异常(继承了了APIException的异常)
    if response:	# 如果 response 有值,说明错误被处理了(Http404,PermissionDenied,APIException)
        return Response({'code': 10001, 'msg': f'drf 报错了{response.data.get("detail", "位置报错")}'})
    else:	# 如果是 None,这个错误没有被处理,说明这个错误不是 drf 的错误,而是 django 的或代码的错误
        return Response({'code': 10002, 'msg': f'系统报错了{str(exc)}'})  

3.配置处理

# settings.py

# 配置在配置文件中
    REST_FRAMEWORK = {
        'EXCEPTION_HANDLER': 'app01.exctptions.common_exception_handler'
    }

十七:jwt 原理

1.cookie,session,token介绍

# https://www.cnblogs.com/liuqingzheng/p/8990027.html

token: 三段式
	第一段:头:公司信息,加密方式。。。	{}
    第二段:荷载,真正的数据 {name:lqz,id:1}
    第三段:签名,通过第一段和第二段,通过某种加密方式加密得到的 aafasdfas
    
    
token 的使用分两个阶段
	-登录成功后的【签发 token 阶段】---》生成三段
    -登录成功访问某个接口的 【验证阶段】---》验证 token 是否合法
    
    
cookie是:存在客户端浏览器的键值对
session是:存在于服务端的键值对
token是:三段式,服务端生成的,存放在客户端(浏览器就放在 cookie 中,移动端:存在移动端中)

使用 token 的认证机制,服务端还要存数据吗? token 是服务的生成,客户端保存,服务的不存储 token

img

2.base64编码和解码

# base64 可以把字符串编码成 base64 的编码格式:(大小写字母,数字和 =)eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogImxxeiIsICJhZG1pbiI6IHRydWV9

# base64 可以把 base64 编码的字符串,解码回原来的格式

# 应用场景:
	- jwt 中使用
    - 网络中传输字符串就可以使用 base64 编码
    - 网络中传输图片,也可能使用 base64 的编码
    
    
# 编码解码


# 把图片保存起来看看
import json
import base64
d = {'name': 'lqz', 'userid': 6, 'age': 19}
info = json.dumps(d)
print(info)
# 把字符串使用base64编码
res=base64.b64encode(info.encode('utf-8'))
print(res)  # eyJuYW1lIjogImxxeiIsICJ1c2VyaWQiOiA2LCAiYWdlIjogMTl9


res=base64.b64decode(s)
with open('code.png','wb') as f:
    f.write(res)

3.jwt 原理介绍

# Json web token (JWT), token 的应用于 web 方向的称之为 jwt

# 构成和工作原理
	JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了 Jwt 字符串。就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

	# header:头
    	-声明类型,这里是 jwt
	    -声明加密的算法 通常直接使用 HMAC SHA256
        -公司信息
        由
            {
              'typ': 'JWT',
              'alg': 'HS256'
            }
        变成了(base64 的编码)
        eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    # payload:荷载
        exp: jwt的过期时间,这个过期时间必须要大于签发时间
        iat: jwt的签发时间
        用户信息: 用户信息
        由
            {
              "exp": "1234567890",
              "name": "John Doe",
              "userid": 3
            }
       变成了(base64 的编码)
       eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
        
    # signature:签名
    	把头和荷载加密后得到的:TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
        
        
    #注意:secret 是保存在服务器端的(加密方式+盐),jwt 的签发生成也是在服务器端的,secret 就是用来进行 jwt 的签发和验证的,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个 secret, 那就意味着客户端是可以自我签发 jwt 了
    
# jwt 使用流程最核心的是:
    -签发:登录接口签发
    -认证:认证类认证

4.drf-jwt 快速使用

4.1 使用步骤

# jwt:签发(登录接口) 认证(认证类)
# django中使用jwt
	-可以自己写
    -https://github.com/jpadilla/django-rest-framework-jwt  (比较老)
    -https://github.com/jazzband/djangorestframework-simplejwt (比较新)
        
        
# 安装
	pip3 install djangorestframework-jwt
# 快速使用
	-迁移表,因为它默认使用 auth 的 user 表签发 token
    -创建超级用户(auth 的 user 表中要有记录)
    -咱们不需要写登录接口了,如果是使用 auth 的 user 表作为用户表,它可以快速签发
    -签发(登录):只需要在路由中配置(因为它帮咱们写好了登录接口)
    from rest_framework_jwt.views import obtain_jwt_token
    urlpatterns = [
        path('login/', obtain_jwt_token),
    ]
    -认证(认证类):导入,配置在视图类上
    	class TestView(APIView):
            authentication_classes = [JSONWebTokenAuthentication,]
            permission_classes = [IsAuthenticated,]
            
    -前端访问时,token 需要放在请求头中
    	Authorization:jwt token 串

4.2 代码示例

# urls.py

# 签发
from django.urls import path, include
from app01 import views
from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    path('login/', obtain_jwt_token),	# 签发路由
    path('shop/', views.Shop.as_view()),	# 认证路由
]
# views.py

# 认证
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated


class Shop(APIView):
    authentication_classes = [JSONWebTokenAuthentication, ]
    permission_classes = [IsAuthenticated, ]
    def get(self, request):

        return Response('OK')

5.drf-jwt 修改返回格式

5.1 使用步骤

# 登录成功后,前端看到的格式,太固定了,只有token,我们想做成
	{code:100,msg:'登录成功',token:adfasdfasdf}
    
    
# 固定写法:写一个函数,函数返回什么,前端就看到什么,配置在配置文件中

# 使用步骤:
	-1 写一个函数
    def jwt_response_payload_handler(token, user=None, request=None):
        return {
            'code':100,
            'msg':'登录成功',
            'username':user.username,
            'token':token
        }
     -2 把函数配置在配置文件中
        JWT_AUTH={
        'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.response.jwt_response_payload_handler',
    	}
        
 # 以后登录接口返回的格式就是咱们写的函数的返回值

5.2 代码示例

# respons.py

def Login_jwt_response_payload_handler(token, user=None, request=None):
    """
    Returns the response data for both the login and refresh views.
    Override to return a custom response such as including the
    serialized representation of the User.

    Example:

    def jwt_response_payload_handler(token, user=None, request=None):
        return {
            'token': token,
            'user': UserSerializer(user, context={'request': request}).data
        }

    """
    return {
        'code': 0,
        'msg':'登录成功',
        'user': user.username,
        'token': token
    }


# settings.py 
JWT_AUTH = {
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.response.Login_jwt_response_payload_handler'
}

6.自定义 user 表,签发 token

6.1 使用步骤

# 1、models.py 中定义UserInfo表

class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)

# 2、views.py 写一个登录接口
from rest_framework.exceptions import APIException

from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER


from .models import UserInfo
class UserView(APIView):
    def post(self, request):
        try:
            username = request.data.get('username')
            password = request.data.get('password')
            user=UserInfo.objects.get(username=username,password=password)
            # 根据user,签发token---》三部分:头,荷载,签名
            # 使用djagnorestframework-jwt模块提供的签发token的函数,生成token
            payload = jwt_payload_handler(user) # 通过user对象---》{username:lqz,id:1,过期时间}
            token=jwt_encode_handler(payload) # 根据payload---》得到token:头.荷载.签名
            print(payload)
            print(token)

            return Response({'code':100,'msg':'登录成功','token':token})
        except Exception as e:
            raise APIException('用户名或密码错误')

6.2 代码示例

# models.py

class Username(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)

    def __str__(self):
        return self.username
    
# PS: Username 模型类中的用户字段必须为 username,因为 get_username() 方法中通过 user.username 来获取用户名 
from rest_framework_jwt.utils import jwt_payload_handler

def jwt_payload_handler(user):
    username_field = get_username_field()
    username = get_username(user)

def get_username(user):
    try:
        username = user.get_username()
    except AttributeError:
        username = user.username

    return username
# views.py

class UserView(APIView):
    def post(self, request):
        try:
            username = request.data.get('username')
            password = request.data.get('password')
            user_obj = Username.objects.get(username=username, password=password)
            # 根据 user,签发 token ---》三部分:头,荷载,签名
            # 使用 djagnorestframework-jwt 模块提供的签发 token 的函数,生成 token
            payload = jwt_payload_handler(user_obj)  # 通过user对象 ---》{username:lqz,id:1,过期时间}
            token = jwt_encode_handler(payload)  # 根据 payload---》得到 token:头.荷载.签名
            return Response({'code': 0, 'user': username, 'msg': '登录成功', 'token': token})
        except Exception as e:
            raise APIException('用户名或密码错误 %s' %e)

7 自定义 jwt 的认证类

# auth.py

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.utils import  jwt_decode_handler
import jwt
from .models import UserName


class JWTAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_TOKEN')
        # 验证 token
        try:
            payload = jwt_decode_handler(token)
        except jwt.ExpiredSignature:
            raise AuthenticationFailed('签名过期')
        except jwt.DecodeError:
            raise AuthenticationFailed('解析出错')
        except jwt.InvalidTokenError:
            raise AuthenticationFailed('无效令牌错误')
        print(payload)

        user = UserName.objects.filter(username=payload.get('username')).first()
        if user:
            return (user, token)
        else:
            return {'code': 10001, 'msg': '认证失败'}
# views.py

class PublishView(APIView):
    authentication_classes = [JWTAuthentication, ]
    def get(self, request):
        info = Publish.objects.all()
        pser = PublishSerializer(instance=info, many=True)
        return Response({'code': 0, 'number': len(pser.data), 'data': pser.data})

8.jwt源码分析

8.1 双token认证

用户正在 app 或者应用中操作 token 突然过期,此时用户不得不返回登陆界面,重新进行一次登录,这种体验性不好,于是引入双 token 校验机制

实现原理:首次登陆时服务端返回两个 token ,accessToken 和 refreshToken,accessToken 过期时间比较短,refreshToken 时间较长,且每次使用后会刷新,每次刷新后的 refreshToken 都是不同

refreshToken 假设7天,accessToken 过期时间5分钟

正常使用 accessToken 即可,如果 accessToken 过期了,重新发请求,携带 refreshToken 过来,能正常返回,并且这次响应中又带了 acessToken

8.2 签发

# 签发的 token,有过期时间,默认过期时间是 300s,配置一般设为 7 天
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
}

# django 中顶格写的代码,都会执行

# 登录接口,路由匹配成功,执行obtain_jwt_token---》post请求---》ObtainJSONWebToken的post方法
	path('login/', obtain_jwt_token),
    
    
# ObtainJSONWebToken的post方法 继承APIView
    def post(self, request, *args, **kwargs):
        # 实例化得到序列化类
        serializer = self.get_serializer(data=request.data)
        # 做校验:字段自己,局部钩子,全局钩子
        if serializer.is_valid():
            # user:当前登录用户
            user = serializer.object.get('user') or request.user
            # 签发的token
            token = serializer.object.get('token')
            # 构造返回格式,咱们可以自定制---》讲过了
            response_data = jwt_response_payload_handler(token, user, request)
            response = Response(response_data)
            if api_settings.JWT_AUTH_COOKIE:
                expiration = (datetime.utcnow() +
                              api_settings.JWT_EXPIRATION_DELTA)
                response.set_cookie(api_settings.JWT_AUTH_COOKIE,
                                    token,
                                    expires=expiration,
                                    httponly=True)
            #最终返回了咱们定制的返回格式
            return response

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
    
    
    
 # 如何得到user,如何签发的token----》在序列化类的全局钩子中得到的user和签发的token
	-JSONWebTokenSerializer---全局钩子---validate
    	#前端传入,校验过后的数据---》{"username":"lqz","password":"lqz1e2345"}
        def validate(self, attrs):
        credentials = {
            # self.username_field: attrs.get(self.username_field),
            'username':attrs.get('username')
            'password': attrs.get('password')
        }

        if all(credentials.values()):
            # auth 模块,authenticate 可以传入用户名,密码如果用户存在,就返回用户对象,如果不存就是None
            # 正确的用户
            user = authenticate(**credentials)

            if user:
                # 校验用户是否是活跃用户,如果禁用了,不能登录成功
                if not user.is_active:
                    msg = _('User account is disabled.')
                    raise serializers.ValidationError(msg)
				# 荷载----》通过user得到荷载   {id,name,email,exp}
                payload = jwt_payload_handler(user)
				
                return {
                    # jwt_encode_handler通过荷载得到token串
                    'token': jwt_encode_handler(payload),
                    'user': user
                }
            else:
                msg = _('Unable to log in with provided credentials.')
                raise serializers.ValidationError(msg)
        else:
            msg = _('Must include "{username_field}" and "password".')
            msg = msg.format(username_field=self.username_field)
            raise serializers.ValidationError(msg)



### 重点:
	1 通过user得到荷载:payload = jwt_payload_handler(user)
    2 通过荷载签发token:jwt_encode_handler(payload)


## 了解:
	# 翻译函数,只要做了国际化,放的英文,会翻译成该国语言(配置文件配置的)
	from django.utils.translation import ugettext as _
	msg = _('Unable to log in with provided credentials.')

8.3 签发

# JSONWebTokenAuthentication---->父类BaseJSONWebTokenAuthentication----》authenticate方法
    def authenticate(self, request):
        # 前端带在请求头中的 token 值
        jwt_value = self.get_jwt_value(request)
        # 如果没有携带 token,就不校验了
        if jwt_value is None:
            return None

        try:
            # jwt_value 就是 token
            # 通过 token,得到荷载,中途会出错
            # 出错的原因:
            	-篡改token
                -过期了
                -未知错误
            payload = jwt_decode_handler(jwt_value)
        except jwt.ExpiredSignature:
            msg = _('Signature has expired.')
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg = _('Error decoding signature.')
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed()

        # 如果能顺利解开,没有被异常捕获,说明 token 是可以信任的
        # payload 就可以使用,通过 payload 得到当前登录用户
        user = self.authenticate_credentials(payload)
		# 返回当前登录用户,token
        return (user, jwt_value)
	# jwt_value = self.get_jwt_value(request)
    
    def get_jwt_value(self, request):
        # 拿到了前端请求头中传入的 jwt dasdfasdfasdfa
        # auth=[jwt,asdfasdfasdf]
        auth = get_authorization_header(request).split()
        # 'jwt'
        auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()

        if not auth:
            # 请求头中如果没带,去 cookie 中取
            if api_settings.JWT_AUTH_COOKIE:
                return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
            return None

        if smart_text(auth[0].lower()) != auth_header_prefix:
            return None

        if len(auth) == 1:
            msg = _('Invalid Authorization header. No credentials provided.')
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = _('Invalid Authorization header. Credentials string '
                    'should not contain spaces.')
            raise exceptions.AuthenticationFailed(msg)

        return auth[1]

    
 # 认证类配置了,如果不传 jwt,不会校验,一定配合权限类使用
 # 它这个认证类:只要带了 token,request.user 就有只,如果没带 token,不管了,继续往后走

十八:权限管理模式

1.RBAC的介绍和使用

# RBAC  是基于角色的访问控制(Role-Based Access Control )在 RBAC  中,
-权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。
这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便

# RBAC权限管理的模式,最适合公司内部的管理系统,不适合对外互联网用户的系统
	-用户:用户表
    -角色(部门):角色表(部门表)
    -权限:权限表
    
    -所有权限都是存在权限表中一条条的记录(发工资,招聘员工,开员工,发布新版本,开董事会)
    -权限是授予角色的(部门的),一个个角色,就是一条条记录(开发角色,hr角色,股东角色)
    -用户:一个个用户是用户表中一条条记录,用户属于某个部门
    
    -三个表之间的关系
    	-用户和角色关系:多对多,中间表
        -角色和权限关系:多对多,中间表
        -5张表了
        	-用户表
            -角色表
            -权限表
            -用户和角色关联表
            -角色和权限关联表
            
            
    -举例子:
    用户表:
    id     姓名 
    1      张三
    2      里斯
    角色表
    id    角色名称
    1      hr角色
    2      股东角色
    3      开发角色
    权限表
    id   权限名词
    1     发工资
    2     招人
    3     提交代码
    
    #####张三要有提交代码的权限
    用户和角色中间表:
    id   角色id   用户id
    1     3       1 
    权限和角色的中间表
    id    角色id   权限id
    1       3       3
    
    
    
 # django的后台管理admin就自带了rbac的权限,通过auth模块实现的,比普通rbac更高级一些
	-本来5张表
    -django是6张表,用户和权限的多对多关系表(一个用户可以分配多个权限,一个权限可以给多个用户)
    	-6张表了
        	-用户表
            -角色表
            -权限表
            -用户和角色关联表
            -角色和权限关联表
            -用户和权限的多对多关系表
   -启用了admin和auth,这6张表就迁移进去了
		auth_user # 用户表
        auth_group # 角色,组,部门表
        auth_permission # 权限表
        auth_user_groups # 用户和角色中间表
        auth_group_permissions # 角色跟权限中间表
        auth_user_user_permissions#用户和权限的中间表
        
  -之前很多公司写后台管理使用dajngo,使用django的admin二次开发,不用写权限了,快速加功能即可

 -体验django admin的rbac的权限控制

2.其它权限控制的介绍

# ACL(Access Control List,访问控制列表)
	将用户或组等使用者直接与对象的权限对接。
    -用户表,权限表,中间  给用户授予某些权限即可
    
# RBAC(Role-Based Access Control,基于角色的访问控制)
	将用户与角色对接,然后角色与对象的权限对接
# RBAC+ACL django,公司用的比较多啊

# ABAC(Attribute-Based Access Control,基于属性的访问控制)
ABAC(Attribute-Based Access Control,基于属性的访问控制)
又称为PBAC(Policy-Based Access Control,基于策略的访问控制)
CBAC(Claims-Based Access Control,基于声明的访问控制)

传统的ACL、RBAC的架构是
{subject,action,object},
而ABAC的架构是
{subject,action,object,contextual}且为他们添加了parameter(参数)。

subject属性:比如用户的年龄、部门、角色、威望、积分等主题属性。

action属性:比如查看、读取、编辑、删除等行为属性。

object属性:比如银行账户、文章、评论等对象或资源属性。

contextual属性:比如时段、IP位置、天气等环境属性。


权限表
id   权限名  
1     开除员工

id   权限名     属性
1    开除员工   女
1    开除员工   男

3.casbin的使用

安装:pip install casbin

3.1 mycasbin.py

import casbin

e = casbin.Enforcer("./model.conf", "./policy.csv")

sub = "lqz"  # 想要访问资源的用户
obj = "book"  # 将要被访问的资源
act = "get"  # 用户对资源进行的操作


# 自己写acl的控制
# 当前用户id,去权限和用户表查询即可,有记录就是有权限

# 自己写rbac
# 当前用户id,找到他的角色,根据角色拿出权限,判断当前访问有没有


if e.enforce(sub, obj, act):
    # 允许alice读取data1
    print('有权限')
else:
    # 拒绝请求,抛出异常
    print('没有权限')

3.2 model.conf

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

3.3 policy.csv

p,alice,data1,read
p,bob,data2,write
p,lqz,book,get

4.simpleui的介绍和使用

# django admin自带了权限控制,但是是前后端混合的,我们可以二次开发,开发出公司内部的自动化运行,自动化测试,人事管理系统,订单系统。。。。样子不好看
# 对 django admin 进行美化
	-xadmin(不用了,过时了)
    -simpleui(正红)

# 基于 drf+vue 自己写前后端分离的权限管理
# go-vue-admin

# 使用 simpleui:https://simpleui.72wo.com/simpleui/
# 安装 pip install django-simpleui

# 现在阶段,一般前后端分离比较多:django+vue
	-带权限的前端端分离的快速开发框架
    -django-vue-admin
    -自己写

4.1 使用步骤

# 1 安装
	pip install django-simpleui
    
# 2 在 app 中注册

# 3 调整左侧导航栏----》
	- menu_display对应menus name
    - 如果是项目的app,就menus写app
    - 菜单可以多级,一般咱们内部app,都是一级
    - 可以增加除咱们app外的其它链接---》如果是外部链接,直接写地址,如果是内部链接,跟之前前后端混合项目一样的写法:咱们的案例---》show 的路由
SIMPLEUI_CONFIG = {
    'system_keep': False,
    'menu_display': ['图书管理', '权限认证', '张红测试'],  # 开启排序和过滤功能, 不填此字段为默认排序和全部显示, 空列表[] 为全部不显示.
    'dynamic': True,  # 设置是否开启动态菜单, 默认为False. 如果开启, 则会在每次用户登陆时动态展示菜单内容
    'menus': [
        {
            'name': '图书管理',
            'app': 'app01',
            'icon': 'fas fa-code',
            'models': [
                {
                    'name': '图书',
                    'icon': 'fa fa-user',
                    'url': 'app01/book/'
                },
                {
                    'name': '出版社',
                    'icon': 'fa fa-user',
                    'url': 'app01/publisssh/'
                },
                {
                    'name': '作者',
                    'icon': 'fa fa-user',
                    'url': 'app01/author/'
                },
                {
                    'name': '作者详情',
                    'icon': 'fa fa-user',
                    'url': 'app01/authordetail/'
                },
            ]
        },
        {
            'app': 'auth',
            'name': '权限认证',
            'icon': 'fas fa-user-shield',
            'models': [
                {
                    'name': '用户',
                    'icon': 'fa fa-user',
                    'url': 'auth/user/'
                },
                {
                    'name': '组',
                    'icon': 'fa fa-user',
                    'url': 'auth/group/'
                },
            ]
        },
        {

            'name': '张红测试',
            'icon': 'fa fa-file',
            'models': [
                {
                    'name': 'Baidu',
                    'icon': 'far fa-surprise',
                    # 第三级菜单 ,
                    'models': [
                        {
                            'name': '爱奇艺',
                            'url': 'https://www.iqiyi.com/dianshiju/'
                            # 第四级就不支持了,element只支持了3级
                        }, {
                            'name': '百度问答',
                            'icon': 'far fa-surprise',
                            'url': 'https://zhidao.baidu.com/'
                        }
                    ]
                },
                {
                    'name': '大屏展示',
                    'url': '/show/',
                    'icon': 'fab fa-github'
                }]
        }
    ]
}
	
    
    
# 4 内部 app,图书管理系统某个链接要展示的字段---》在admin.py 中----》自定义按钮
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    list_display = ('nid', 'name', 'price', 'publish_date', 'publish')

    # 增加自定义按钮
    actions = ['custom_button']

    def custom_button(self, request, queryset):
        print(queryset)

    custom_button.confirm = '你是否执意要点击这个按钮?'
    # 显示的文本,与django admin一致
    custom_button.short_description = '测试按钮'
    # icon,参考element-ui icon与https://fontawesome.com
    # custom_button.icon = 'fas fa-audio-description'
    # # 指定element-ui的按钮类型,参考https://element.eleme.cn/#/zh-CN/component/button
    custom_button.type = 'danger'
    # # 给按钮追加自定义的颜色
    # custom_button.style = 'color:black;'
    
    
    
# 5 app 名字显示中文,字段名字显示中文
	-新增,查看修改展示中文,在表模型的字段上加:verbose_name='图书名字',help_text='这里填图书名'
	-app名字中文:apps.py---》verbose_name = '图书管理系统'

    
# 6 其它配置项
	SIMPLEUI_LOGIN_PARTICLES = False  #登录页面动态效果
    SIMPLEUI_LOGO = 'https://avatars2.githubusercontent.com/u/13655483?s=60&v=4'# 图标替换
    SIMPLEUI_HOME_INFO = False  # 首页右侧 github 提示
    SIMPLEUI_HOME_QUICK = False # 快捷操作
    SIMPLEUI_HOME_ACTION = False # 动作

4.2 settings.py

import time

SIMPLEUI_CONFIG = {
    'system_keep': False,
    'menu_display': ['我的首页', '图书管理','权限认证', '多级菜单测试', '动态菜单测试'],  # 开启排序和过滤功能, 不填此字段为默认排序和全部显示, 空列表[] 为全部不显示.
    'dynamic': True,  # 设置是否开启动态菜单, 默认为False. 如果开启, 则会在每次用户登陆时动态展示菜单内容
    'menus': [
        {
            'name': '我的首页',
            'icon': 'fas fa-code',
            'url': '/index/',

        },
        {
            'app': 'app01',
            'name': '图书管理',
            'icon': 'fas fa-code',
            'models': [
                {
                    'name': '用户',
                    'icon': 'fa fa-user',
                    'url': 'app01/userinfo/'
                },
                {
                    'name': '图书',
                    'icon': 'fa fa-user',
                    'url': 'app01/book/'
                },
                {
                    'name': '出版社',
                    'icon': 'fa fa-user',
                    'url': 'app01/publish/'
                },
            ]
        },
        {
            'app': 'auth',
            'name': '权限认证',
            'icon': 'fas fa-user-shield',
            'models': [{
                'name': '用户',
                'icon': 'fa fa-user',
                'url': 'auth/user/'
            }]
        },
        {
            # 自2021.02.01+ 支持多级菜单,models 为子菜单名
            'name': '多级菜单测试',
            'icon': 'fa fa-file',
            # 二级菜单
            'models': [
                {
                    'name': '百度',
                    'icon': 'far fa-surprise',
                    # 第三级菜单 ,
                    'models': [
                        {
                            'name': '爱奇艺',
                            'url': 'https://www.iqiyi.com/dianshiju/'
                            # 第四级就不支持了,element只支持了3级
                        },
                        {
                            'name': '百度问答',
                            'icon': 'far fa-surprise',
                            'url': 'https://zhidao.baidu.com/'
                        }
                    ]
                },
                {
                    'name': 'lqz',
                    'url': 'https://www.wezoz.com',
                    'icon': 'fab fa-github'
                }]
        },
        {
            'name': '动态菜单测试',
            'icon': 'fa fa-desktop',
            'models': [{
                'name': time.time(),
                'url': 'http://baidu.com',
                'icon': 'far fa-surprise'
            }]
        }]
}


SIMPLEUI_LOGIN_PARTICLES = False
SIMPLEUI_HOME_INFO = False

4.3 admin.py

from django.contrib import admin

# Register your models here.

from .models import *


@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    list_display = ('id', 'title', 'price')

    # 增加自定义按钮
    actions = ['make_copy', ]
    def make_copy(self, request, queryset):
        print('adsfasdf')
    make_copy.short_description = '自定义按钮'
    # # icon,参考element-ui icon与https://fontawesome.com
    make_copy.icon = 'fas fa-audio-description'
    # # 指定element-ui的按钮类型,参考https://element.eleme.cn/#/zh-CN/component/button
    make_copy.type = 'danger'
    make_copy.confirm = '你是否执意要点击这个按钮?'



admin.site.register(UserInfo)
admin.site.register(Publish)

5.simpleui集成监控大屏

# 从 gitee 上找到开源的前端页面 ---》集成到项目中即可 ---》 https://gitee.com/lvyeyou/DaShuJuZhiDaPingZhanShi?_from=gitee_search

# 本质就是前后端混合的项目

十九:drf回顾

# djangorestframework
	-视图:执行流程多了东西
    	-两个视图基类:
        -5个视图扩展类
        -9个视图子类
        -ViewSetMinxin,ModelViewSet,ReadOnlyModelViewSet
    -路由
    	-三种写法
        	-自动生成(两个类)
            -action装饰器
    -序列化类
    	-字段类,字段属性(read_only,write_only)
    	-Serializer:每个字段都要写
        -ModelSerializer:跟表有映射关系
        	-model,fields,extra_kwargs
        -局部钩子,全局钩子
        -save和update
        -自定义序列化的字段:两种方式
    -认证,权限,频率
    	-认证类,
        -权限类:acl,rabc
        -频率类
        -局部使用和全局使用
    -过滤,排序
    	-配置排序和过滤即可
        -内置的
        -第三方
        -自己写的
    -全局异常
    	-写个函数,配置一下
    -接口文档
    	-md,word,第三方。。。。
    -jwt:
    	-签发和认证

二十:考试

附件1:鸭子类型

# 指的是面向对向中,子类不需要显式的继承某个类,只要有某个的方法和属性,那就属于这个类

# 假设有个鸭子类 Duck 类,有两个方法,run,speak 方法
# 假设又有一个普通鸭子类 PDuck,如果它也是鸭子,它需要继承 Duck 类,只要继承了鸭子类,什么都不需要写,普通鸭子类的对象就是鸭子这种类型;如果不继承,普通鸭子类的对象就不是鸭子这种类型
#假设又有一个唐老鸭子类 TDuck,如果它也是鸭子,它需要继承 Duck 类,只要继承了鸭子类,什么都不需要写,唐老鸭子类的对象就是鸭子这种类型;如果不继承,唐老鸭子类的对象就不是鸭子这种类型

# python 不推崇这个,它推崇鸭子类型,指的是
不需要显式的继承某个类,只要我的类中有 run 和 speak 方法,我就是鸭子这个类

# 有小问题:如果使用 python 鸭子类型的写法,如果方法写错了,它就不是这个类型了,会有问题
# python为了解决这个问题:
	-方式一:abc 模块,装饰后,必须重写方法,不重写就报错
    -方式二:drf 源码中使用的:父类中写这个方法,但没有具体实现,直接抛异常

附件二:django的配置文件每个配置项的作用

# 必须大写才是配置项
# django 项目要启动,要先加载配置文件,如果配置文件报错,项目运行不起来

from pathlib import Path
# 项目根路径
BASE_DIR = Path(__file__).resolve().parent.parent

# 密钥,自动生成的,很复杂,django 中涉及到加密的都使用它,没有不行
SECRET_KEY = 'django-insecure-4i$6azd399fl$vh9%*7#oald(i&ik(5#i_-y)v&z3g+cxsd$@-'

# 如果是 True,项目是调试模式,好处是抛异常在浏览器直接能看到,如果路径不存在,也会提示有哪些路径
DEBUG = True
# 允许项目部署的地址(后期项目上线,这里写服务器的地址),debug 是 False,这个必须加,不加就报错
ALLOWED_HOSTS = ['*']


# 所有的app,django大而全,就是因为它提供了很多内置的 app
INSTALLED_APPS = [
    'django.contrib.admin', # 后台管理 admin
    'django.contrib.auth', # 权限,6 个表
    'django.contrib.contenttypes',
    'django.contrib.sessions',# session 认证相关 ---》django_session 表
    'django.contrib.messages', # 消息框架(不了解)
    'django.contrib.staticfiles',# 静态文件
    'app01.apps.App01Config', # app01
    'rest_framework',
]
# 中间件
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',  #
    'django.contrib.sessions.middleware.SessionMiddleware', # session 相关
    'django.middleware.common.CommonMiddleware',	# 把路径跟路由匹配,如果匹配不成功把路径加/ 再匹配,如果匹配成功,让该请求重定向到带/的地址
    'django.middleware.csrf.CsrfViewMiddleware', # csrf相关
    'django.contrib.auth.middleware.AuthenticationMiddleware', # 认证相关
    'django.contrib.messages.middleware.MessageMiddleware',  # 消息框架,学到 flask,闪现相同的功能
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]


# 根路由,所有请求进来,先去根路由匹配---》内部可能有路由分发
ROOT_URLCONF = 'drf_day08.urls'

# 模板相关
TEMPLATES = [
    
]

# 后期项目项目上线,uwsgi运行这个application,测试阶段使用manage.py 运行项目
WSGI_APPLICATION = 'drf_day08.wsgi.application'

# 数据库配置 【可以使用多数据库】
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

# auth的认证相关
AUTH_PASSWORD_VALIDATORS = [
	
]

# 国际化
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True

# 静态文件相关
STATIC_URL = '/static/'

# 所有的自增主键都用 BigAutoField
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

# drf 中的各项自定义配置 
REST_FRAMEWORK = {

}

附件三:内置认证类,权限类,频率类

# 内置的认证类:
	-BasicAuthentication
    -RemoteUserAuthentication
    -SessionAuthentication:session认证,建议自己写
    	-如果前端带着cookie过来,经过session的中间件,如果登录了,在request.user中就有当前登录用户
        -drf没有限制是否登录
        -加了这个认证,如果没有登录,就不允许往后访问了
    -TokenAuthentication:jwt认证,建议自己写
    -建议自己写
    
    
# 内置权限类
	-BasePermission:所有的基类
    -AllowAny:允许所有
    -IsAuthenticated:是否登录,登录了才有权限
    	-认证类,如果登录了,返回(当前登录用户,None), 如果没有登录返回 None
    -IsAdminUser:auth的user表中is_staff字段是否为True,对后台管理有没有权限
    
    
# 内置的频率类:
	-BaseThrottle:所有的基类
    -SimpleRateThrottle:咱们写频率类都继承它,少写代码
    -AnonRateThrottle:如果你想按 ip 地址限制,只需要在配置文件中配置
      'DEFAULT_THROTTLE_RATES': {
        'anon': '3/m',
    	}
    -UserRateThrottle:如果你想按用户 id 限制,只需要在配置文件中配置
     'DEFAULT_THROTTLE_RATES': {
        'user': '3/m',
    	}

附件四:接口文档

1.直接使用 word 或 md 编写

2.搭建本地接口文档管理平台、使用第三方平台(收费)

3.使用 django 自动生成接口文档(了解即可)

# 接口文档,需要有的东西
	-描述
    -地址
    -请求方式
    -请求编码格式
    -请求数据详解(必填,类型)
    -返回格式案例
    -返回数据字段解释
    -错误码
    
# 在公司里的写法
	-1 直接使用 word 或者 md 编写
    -2 使用接口文档平台,在接口文档平台录入(Yapi(百度开源的自己搭建),第三方平台(收费),自己开发接口文档平台)
    	-https://www.showdoc.com.cn/item/index
        - 不想花钱,没有能力开发,就使用开源的YAPI,https://zhuanlan.zhihu.com/p/366025001
        - https://www.liuqingzheng.top:3000/
        	输入邮箱:306334678@qq.com
			输入密码:admin
    -3 项目自动生成:
    	-swagger---》drf-yasg---》官方推荐使用
        -coreapi----》咱们讲
    	
        -1 下载:pip3 install coreapi
        -2 路由中配置:
        from rest_framework.documentation import include_docs_urls
        urlpatterns = [
            path('docs/', include_docs_urls(title='站点页面标题'))
        ]
        -3 在视图类中加注释
        	-在类上加注释
        	-在类的方法上加注释
        	-在序列化类或表模型的字段上加  help_text,required。。。。
        -4 在配置文件中配置
            REST_FRAMEWORK = {
            	'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
            }
        -5 访问地址:http://127.0.0.1:8000/docs
        
        # 注意:
        	-直接在视图类中写注释
            -在序列化类上写 help_text
            -注释要写在
        

附件五:断点调试使用

# pycharm 软件
# 程序是 debug 模式运行,可以在任意位置停下,查看当前情况下变量数据的变化情况

# pycharm 来调试程序
	-以 debug 形式运行
    -在左侧空白处,点击加入断电 (红圈)
    -step over    单步调试
    -step into    进入到函数内部运行
    -快速调到下一个断电,绿色箭头
posted @ 2023-01-30 20:10  亦双弓  阅读(199)  评论(0)    收藏  举报