kaixin
Published on 2023-04-19 / 27 Visits
0

Django-API

Django-API

Django 创建新的app程序
python  manage.py startapp  新app名字
切记继承的关键:子类继承父类,如果子类没有这个方法,那么就从父类找,每次找内容都是从子到父,不会跨过子类直接找父类

基本使用

同源策略

服务器正常执行,正常的返回结果,但是结果被前端拦截
浏览器的同源策略,浏览器基本的安全功能(支持js)
所有的浏览器使用的同源策略(保护服务端的数据)
同源:
    域名,协议,端口相同 (内部只要有一个不同,那么就不同源)
​
可以理解为:
    发送ajax请求的网址ip和端口,与浏览器导航上的不同,就会出现不同源的现象
    
    发起请求的ip or 端口 与 发送给服务器的端口 ip 是否同源
    例如:
    http://127.0.0.1:8000/ 发起请求的ip 端口 协议 客户端
    $.ajax({
        url:'http://127.0.0.1:8000/...'  同源不会被浏览器拦截
    })
    
    不同源:
    file:///C:/Users/wkx/Desktop/1.html 发起请求的ip 端口 协议 客户端
    $.ajax({
        url:'http://127.0.0.1:8000/...'  不同源会被浏览器拦截
    })
    协议不同,端口不同,ip不同
​
​
解决方式:
    1.jsonp
    2.cors(服务器给浏览器说一声,数据可以发送)
    3.前端代理(vue ...)
​
cors 将缺少的响应头给设置了
给返回的相应头中设置 * 代表全部的全部可以访问
    res['Access-Control-Allow-Origin'] = '*' or 设置 'ip端口'
    res['Access-Control-Allow-Headers'] = '*'
​
当发送复杂请求时:
会出现两次请求,在数据发送之前进行遇见,只有预检通过后,在发送一次请求用于数据的传输
​
请求方式options
检查通过传输数据,不通过不发数据
​

Restful规范

协议
    http 协议
    https 协议 内部存在数据加密 保证数据安全

域名:
	后端 api接口中体现api标识
	api.xxx.com
	
版本
	url 体现版本:
	http://api.xxx.com/v1
	http://api.xxx.com/>version=v1
	http://v1.xxx.com/ 在二级域名中体现版本
	http://api.xxx.com/
	请求头中: accept:application/json;rsion=v1
	
路径
	http://api.xxx.com/v1/名词
	http://api标识/版本标识/名词(具有代表代表性)
	
请求的方式
	get  http://api.xxx.com/v1/users/ # 以get形式请求说明获取用户列表
	get  http://api.xxx.com/v1/users/?id=1 # 以get请求形式添加参数只获取单条用户数据
	post  http://api.xxx.com/v1/users # 添加一个用户
	pui	  http://api.xxx.com/v1/users # 修改用户数据
	patch   http://api.xxx.com/v1/users # 删除用户数据
	delete   http://api.xxx.com/v1/users # 修改用户的局部数据

	# 一个url 发送不同的请求方式 获取不同的效果

搜索条件
	当存在搜索条件时,使用get形式 ? 请求参数 分页内容 指定内容
	 http://api.xxx.com/v1/users?limit=10  指定返回记录数据
        
返回数据
	针对请求方式不同,返回数据结构不同
	http://api.xxx.com/v1/users
	例如:
	/user/   get  返回的是一个用户列表 [{'id':1},{'id':2},...]
	/user/?id=1  get  返回的是单条用户数据 {'id':1}
	/user/    post  添加一条用户数据 {'id':'新添加id','name':xxx}
	/user/d+  delect  删除数据   返回 null
	/user/d+   put  更新数据   返回更新数据的当条的全部内容  {'id':'id','name':xxx,...}
	/user/d+  patch 更新局部数据 返回更新数据的当条的全部内容  {'id':'id','name':xxx,...}
	 
    返回的内容
        正确
        {'code':'返回码','data': [{'id':1},{'id':2},...]}
        错误
        {'code':'返回码','error':'详细的错误信息'}
        
 状态码
 	
	200  ok
	201  post put patch  用户新建或者修改数据成功
	202  表示一个请求已经后台排队 (异步任务)
	204  表示用户删除数据成功
	400  用户发送请求错误 服务器没有进行新建或者修改数据操作
	401  表示用户请没有认证 密码 用户名 token
	403  表示用户没有被授权(无权访问)
	404  用户请求数据不存在
	406  用户请求的发送数据格式不对  json格式 发成xml格式
	410  请求资源被永久删除,不会在得到
	422  创建对象时发生验证错误
	500  服务器出现错误
	
错误处理
	需要将错误信息进行返回
	{
		code:'返回码',
		error:'错误定义'
	}

FBV 和 CBV

FBV  函数 function base views
	路由的对应的url 对应这函数

CBV  类开发 class base views
	通过类名执行
	类名.as_view()方法,本质就是fbv 在这一上层的分装
	在内部获取请求方式,进行反射调用CBV的类中的方法进行执行

安装使用

1.
pip install djangorestframework
需要安装指定版本
django 3.1以上 和python 3.5 以上 安装 3.12.4
django3.0以下 和 python 3.8 以下 安装 3.11.2

2.
在配置文件中进行配置
本质上restful属于一个别人写好的app程序需要在配置文件中进行注册
INSTALLED_APPS = [
	....
	'rest_framework' # 注册restful 就是一个完整的app程序
]

在配置文件中写上restful配置变量
REST_FRAMEWORK = {
    
}

3.
路由配置
from django.urls import path
from app import views

urlpatterns = [
    path('api/user/', views.Users.as_view()), # 调用cbv中的as_view
]


视图编写
from rest_framework.views import APIView
from rest_framework.response import Response


class Users(APIView): # 继承rest_framework中的APIView类

    def get(self, request, *args, **kwargs):
        return Response({'code': 1008, 'data': 1}) # 返回值也是用rest_framework中的response

本质上rest_framwork继承了django自带的View API类,但是在当前的View中进行了更上次一层的封装
在rest_framwork 中的继承了 View, 在路由调用rest_framwork 中的方法as_view()时 使用super().as_view() 也就是说本质上调用了View类中的as_view()方法,而rest_framwork中的as_view()就是对继承的View中的进一步封装(内部免除了csrf的认证)

rest_framwork API类就是 中的dispatch方法比 View Api 进行封装了更多的功能

基本语法

数据获取

因为是对原来的Viwe进行了封装,同时也对请求的request也进行了封装

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


class Users(APIView):

    def get(self, request, *args, **kwargs):
        # 获取的参数 url 127.0.0.1:8000/api/user/1
        # 路由1 'api/user/<int:name>'
        # 路由2 'api/user/(?P<pk>\d+)'
        kwarag # 获取的url参数 {'name':1} / {'pk':1} 字典
        
        # 路由1 api/user/(\d+) 使用正则表达式的路由 并且没有命名
        args  # 获取的参数(1,) 元组
        
        
        # 使用类对象调用
        print(self.request.POST) # 解析请求体重的json格式数据 返回字典
        print(self.request.GET) # 解析url ?name=123 的数据
        print(self.request.method) # 获取请求方式
        print(self.request.body) # 解析请求体重的json格式数据 返回字节
        print(self.headers) # 调用的headers, {'Allow': 'GET, HEAD, OPTIONS', 'Vary': 'Accept'}
        # 使用原request调用 同上(与下方相同_request)
        print(request.POST)
        print(request.GET)
        print(request.body)
        print(request.method)
        print(request.headers) # 打印的是全部headers头部内容
        
        # 使用封装后的request对象调用 同上(与上个request)
        print(request._request.POST)
        print(request._request.GET)
        print(request._request.body)
        print(request._request.method)
        print(request._request.headers) # 打印的是全部headers头部内容
        return Response({'code': 1008, 'data': 1})


另一种方式(封装好的获取数据的方式)
request.query_params  # 本质上调用了 self._request.GET
request.data  # 对请求体中的只进行读取,进行json反序列化(根据请求头中的不同,自动进行对数据做处理)

版本控制

1.通过url中get 传参方式传递版本(常用)

url :127.0.0.1:8000/api/user/?version=1 # url 形式 *****
django drf 进行继承的版本方式 共5种
1.支持通过url以get形式传参形式(默认固定需要vresion,是可以修改的需要在rest frame 的配置文件中进行修改)
	1.单独视图类设置
	from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import QueryParameterVersioning	 # 1.restframe版本功能类
    class Users(APIView):
    versioning_class = QueryParameterVersioning # 2.使用后只有当前视图类具有版本效果

    def get(self, request, *args, **kwargs):
        print(request.version) # 获取版本
        return Response({'code': 1008, 'data': 1})
    
  	url :127.0.0.1:8000/api/user/?version=1  # version 这个参数是不能更改的必须这样传,如果传入的参数不是version,request.version 就是none

    2.全局视图类设置
    需要在配置文件中setting.py 关于restframe 的配置中设置
	REST_FRAMEWORK = {
    # restframe 会读取配置文件,将version版本类设置到每一个视图类中(执行时进行了反射)
    'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.QueryParameterVersioning'
    }
    
    3.其他参数配置
    REST_FRAMEWORK = {
        'VERSION_PARAM':'v' # 修改当前url中get方式中传入版本的key的名字(默认时version改为v)
        'DEFAULT_VERSION':'v1', # 不传递?version=v1时,默认request.version获取的版本就是v1
        'ALLOWED_VERSIONS':['v1','v2','v3'], # 版本限制,只能传入当前的这个几个版本,如果不是就会报错:版本无效的错误
    }
    
    '''
    	源码:在initial函数中调用了类API中determine_version(自己编写视图类中没有找父类)方法,对versioning_class变量对应的类进行实例化,并且调用了实例化对象中determine_version方法获取url get请求中的version版本值(同时内部还读取了difult_version默认版本,以及判断是否设置allowed_versions版本限制,对着3个值进行了判断处理,然后返回版本)    
    '''

2.通过在url路径中传递版本(常用)

url :127.0.0.1:8000/api/v2/user/ # 将版本当做路径的一部分传递版本 ***推荐方式
需要在视图类中和url中进行调整

1.url的设置
注意:参数参数名称必须是 version
path('api/<str:version>/user/', views.Users.as_view()) # 非正则
re_path(r'^api/(?P<v>\w+)/user/$', views.Users.as_view()), # 正则 \w匹配一个 \w+匹配多个

2.视图类设置
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import URLPathVersioning # 1. 使用URl中获取版本类


class Users(APIView):
    versioning_class = URLPathVersioning # 2.使用versioning_class 赋值

    def get(self, request, *args, **kwargs):
        print(request.version)  # 获取版本
        return Response({'code': 1008, 'data': 1})
    

3.配置文件中也可以设置一些参数
REST_FRAMEWORK = {
        'VERSION_PARAM':'v' # 将version 改为v,如果设置了v,而路由动态参数不改变就会出现找不到的错误
        'ALLOWED_VERSIONS':['v1','v2','v3'], # 版本限制,只能传入当前的这个几个版本,如果不是就会报错:版本无效的错误
    	'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning' # 设为全局配置
    }

3.通过请求头传递版本

url : 127.0.0.1:8000/api/user/ # 正常的路由方式

# 注意:需要在请求头中进行设置固定参数
请求头key:Accept   请求头val:application/json; version=1.0

1.url设置正常设置
path('api/user/', views.Users.as_view()) 

2.视图类
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import AcceptHeaderVersioning # 1.导入请求头获取版本类


class Users(APIView):
    versioning_class = AcceptHeaderVersioning # 2.使用从请求头中获取版本

    def get(self, request, *args, **kwargs):
        print(request.version)  # 获取版本
        return Response({'code': 1008, 'data': 1})

3.前端请求头中进行设置请求头
请求头key:Accept   请求头val:application/json; version=1.0


配置中可以设置参数:
REST_FRAMEWORK = {
        'VERSION_PARAM':'v' # 将version 改为v,请求头中也需要改为v
        'ALLOWED_VERSIONS':['v1','v2','v3'], # 版本限制,只能传入当前的这个几个版本,如果不是就会报错:版本无效的错误
    	'DEFAULT_VERSIONING_CLASS':'rest_framework.AcceptHeaderVersioning' # 设为全局配置
    }

4.通过2级域名传递版本信息

想要测试域名的设置

1.找到C:\Windows\System32\drivers\etc\hosts文件
修改当前127.0.0.1 的对应关系
127.0.0.1  v1.wkx.com

2.在django的配置文件中设置
ALLOWED_HOST = ['*']


URL : v1.wkx.com:8000/api/user/  # 默认将127.0.0.1 变为了   v1.wkx.com

url设置:
path('api/user/', views.Users.as_view())

视图类设置
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import HostNameVersioning # 1.导入二级域名版本类


class Users(APIView):
    versioning_class = HostNameVersioning # 2.使用二级域名版本类

    def get(self, request, *args, **kwargs):
        print(request.version)  # 获取版本
        return Response({'code': 1008, 'data': 1})

5.通过namespace传递版本信息

主要是通过url的路由分发设置

1.视图类
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import NamespaceVersioning # 1.导入


class Users(APIView):
    versioning_class = NamespaceVersioning # 2.使用

    def get(self, request, *args, **kwargs):
        print(request.version)  # 获取版本
        return Response({'code': 1008, 'data': 1})
    
    
2.url设置
主路由
path('api/v1/',include('app.urls',namespace='v1')) # 分发路由

子路由
path('user/', views.Users.as_view()) # 从路由


# 当访问 
api/v1/user/  可以获取版本信息

版本反向生成url

需要配合使用版本类进行使用,如果不使用的情况下,会进行报错

使用request.versioning('路由的name','request') # 就可以反向生成url

视图:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import URLPathVersioning # 使用当前在url内动态获取当前的版本类,上述5中都可以使用

class Users(APIView):
    versioning_class = URLPathVersioning

    def get(self, request, *args, **kwargs):
        print(request.version)  # 获取版本
        # 获取的版本 v1
        print(request.versioning_scheme.reverse('v1', request=request))  # 可以反向生成url(需要根据url设定值name,并且传入当前的request)
        # 获取的urlhttp://127.0.0.1:8000/api/v1/user/
        return Response({'code': 1008, 'data': 1})

url:需要对url设置name值
path('api/<str:version>/user/', views.Users.as_view(),name='v1'),

# 注意:
	如果使用版本反向生成url,1.需要配置版本类,2.在url中设置name别名 底层就是根据django方向生成url

认证

作用:
	根据用户发来的请求,从中获取请求的凭证,获取到用户的信息,用户登录没有登录的作用


为了区别对待,一些接口可以不需要凭证就能访问,一些接口必须要凭证才能访问
例如:
	商城页面:当用户登录后,才能将商品添加购物车,并且结账
			如果没有登录,那么就会提示当前用户,必须登录后才能添加购物车

认证类一个就可以 + jwt 认证 就可以(为什么一个就可以,因为如果认证成功,就会直接返回,内部循环就会终止,多个认证类没有意义)

与jwt 一样,在登录时生成一个随机码,发送给前端,前端进行保存,每次访问都要携带

1.快速使用

# 补充 : 登录部分的随机生成使用的uuid 用来携带到前端进行使用
import uuid
def post(self, request, *args, **kwargs):
        print(request.data)
        name = 'wkx'
        pwd = '123'
        if request.data.get('name') == name and request.data.get('pwd') == pwd:
            name_uuid = uuid.uuid4()
            return Response(
                {'code': 200, 'msg': '登录成功', 'data': {'usernanme': request.data.get('name'), 'token': name_uuid}})
        return Response({'code': 400, 'msg': '用户名密码错误', 'data': None})




# 1.需要导入认证父类和认证异常类
    from rest_framework.authentication import BaseAuthentication  # 1.导入restframe的认证父类
    from rest_framework.exceptions import AuthenticationFailed  # 1.认证类的异常


    以url get 方式将登录生成的认证的值存放 也可以放在header头部中(需要前端将值放到请求头中或者url get 方法中)
    url :http://127.0.0.1:8000/api/v1/pay/?token=7cf2bb20-ea20-4394-b109-55a22c548ca6


# 2.需要创建一个类进行继承认证父类
	重写父类的两个方法

    authenticate # 认证逻辑编写的地方,返回两个值 这个两个值分别赋值给rquest.user='第一个值' request.auth = '第二个值'

    authenticate_header # 如果认证失败会将 return 携带到响应头中 WWW-Authenticate  Bearer realm=API(不重要)

    class Authentication(BaseAuthentication):

        def authenticate(self, request):  # 负责认证 具体认证方法
            a = '7cf2bb20-ea20-4394-b109-55a22c548ca6'
            token = request.query_params.get('token') #  从url中取token值
            token = request.headers.get('Token') # 从请求头中获取
            if not token:
                raise AuthenticationFailed({'code': 1002, 'msg': '未认证'}) # 认证不通过,会将错误信息给前端 终止
            if not token == a:
                raise AuthenticationFailed({'code': 1002, 'msg': '认证失败'})
            # 返回的值 被赋值了request中的属性user and auth (必须返回两个值)
            # request.user = '用户对象'
            # request.auth = 'token'
            return '用户对象','token' # 返回元组 (None,None) 无论返回什么都可以 必须是两个值 要不然就会报错

        def authenticate_header(self, request):  
            return 'Bearer realm=API'
    
    
# 3.api类中的使用
    class PayView(APIView):

        authentication_classes = [Authentication,认证1,认证2.... ]  # 使用认证类(支持多个),如果认证不通过,那么不会进入接口的方法中

        def get(self, request, *args, **kwargs):
            print(request.user) # '用户对象'
            print(request.auth) # 'token'
            return Response({'code': 1, 'data': 123456})

2.当认证类返回none时

# 1.认证none情况说明与配置
class Authentication(BaseAuthentication):  
    def authenticate(self, request):  
		return Nnoe # 如果返回none 说明跳过当前的认证类,直接进行下一个认证
    def authenticate_header(self, request):  
        return 'Bearer realm=API'
    
class PayView(APIView):
    # 可以是多个认证类
    authentication_classes = [Authentication,认证2,认证3,认证4 ]
 
注意:
	如果每一个认证类都返回none时
    request.user  用户对象就是一个匿名用户 AnoymousUser
    request.auth  None

可以在配置文件中设置
REST_FRAMEWORK = {
    # 为什么时匿名函数,因为在源码中,会对配置文件中加()执行
    'UNAUTHENTICATED_USER':lambda:None,
     'UNAUTHENTICATED_TOKEN':lambda:None,
}
如果认证返回none那么在对request.user赋值 none request.auth 赋值none



# 2. 返回none的使用场景案例例如:
def authenticate(self, request):
    a = '当前用户从数据库中查到的认证token值'
	token = request.headers.get('Token') # 从请求头中获取
    if not token:
		return Nnoe 
	if not token == a:
        raise AuthenticationFailed({'code': 1002, 'msg': '认证失败'})
 
class PayView(APIView): # 接口api
    # 可以是多个认证类
    authentication_classes = [Authentication,认证2,认证3,认证4 ]
    
    def get(self,request,...)
    	if not request.user:
            return {'code':200,'data':[11,22,33]}
        return {'code':200,'data':[110,220,330]}
    
# 这个api 作用就是,在你认证时,如果没有认证,不给你抛出异常,而是返回正常的结果,认证过的也给你返回结果
认证: 返回的结果最新的
未认证: 返回结果是旧记录
也就是登录和未登录的区别,像商城一样

3.认证的全局配置

# 全局配置
REST_FRAMEWORK = {
	'DEFAULT_AUTHENTICATION_CLASSES':['认证类的路径1','认证类的路径2']
}

# 如果其他的api类不想使用全局的
authentication_classes = [] # 覆盖全局配置认证

4.源码

在内部对原view api类进封装时,对认证类authentication_classes变量进行了内部进行  变量 =   [auth() for auth in authentication_classes],将authentication_classes变量中的认证类s全部进行了实例化(如果api类中没有认证类变量,那么就会从基层的APIView中去找,内部定义了一个authentication_classes指向配置文件中的DEFAULT_AUTHENTICATION_CLASSES配置) 封装了 request中


在 self.initial() 执行后 内部调用了self.perform_authentication(request),内部直行了request.user()方法,内部将进行了调用,执行了 自定义认证类中(父级认证类),重写的authenticate方法...

如果认证成功就会终止return,所以认证类多个没有意义

权限

权限: 
	读取认证中获取的用户信息,判断用户是否有权限访问,不同的权限可以访问的接口不同

用户的id 或者 用户对象怎么让前端携带到后端
1. 使用 在登录接口时生成一个 关于用户的随机验证码 或者内部进行存储用户id 或者对象的 加密随机码
2. 使用jwt 将用户的id 进行加密
等等..... 

当获取到用户id时,可以在数据库中进行查看当前的用户是否可以访问
可以 返回 True
不可以 返回 False

1.快速使用

# 1. 导入rest_frame 中的权限父类
from rest_framework.permissions import BasePermission


# 2. 创建一个类进行继承 重写权限父类的方法
class BaseP(BasePermission):
    # 注意 使用权限时,需要配合认证类进行使用,源码中如果想返回message变量,需要认证类存在
    message = {'code':1002,'msg':'无权访问'} # 无权的情况 源码会读取message 将变量返回

    def has_permission(self, request, view):
        '''编写对用户的权限的判断 具体逻辑自己定义根据什么来进行定义
        request : 请求的对象
        view : 当前访问的api接口的类
        作用:对当前的访问的url是否有权限 力度小
        '''
        name = 1
        if name == 2:  # 如果当前name = 1 那么就有权限 否则就没有权限
            return True  # 有权限,api才能访问
        else:
            return False  # 无权访问, api无权访问

    def has_object_permission(self, request, view, obj):
        '''
        request.user :当前用户
        obj :当前查询 删除 更新的对象
        在视图类内部如果调用了(单条详细 删除 更新)get_object内部调用了has_object_permission方法
        作用:单条详细查询 删除数据 更新数据 操作中当前用户是否对这条数据有权限 力度大(将权限力度控制到每一条数据权限上)
        '''
        return True


# 3.在 接口类中 进行配置
class PayView(APIView):
    # 可以存在多个权限判定的类,那么全部的权限类都访问通过了,才能访问当前接口
    permission_classes = [BaseP,权限类2,权限类3 ] # 使用权限类

    def get(self, request, *args, **kwargs):
        return Response({'code': 1, 'data': 123456})

2.权限全局设置

# 全局配置
REST_FRAMEWORK = {
	'DEFAULT_PERMISSION_CLASSES':['权限的路径1','权限类的路径2']
}

# api 接口禁用权限组件
authentication_classes = [] # 覆盖全局配置认证

3.源码

源码中将classes 的全部的权限类加() 进行实例化 循环执行内部has_permission方法进行权限的判断
如果权限通过那么就可以访问到api 如果权限不通过

注意:
	关于权限类返回的message 变量
	message = {'code':1002,'msg':'无权访问'}
 	源码中认证组件的存在(如果没有认证组件那么就会报错Authentication credentials were not provided.未提供认证用户信息),才会将当前权限为false情况 返回message变量,
 	因为源码中的这段代码
 	需要存在认证类的存在(你认证通过了,但是权限没有通过)
 	
 	if request.authenticators and not request.successful_authenticator:
 	  	raise exceptions.NotAuthenticated()

限流

简单理解为是一种限制用户访问的内置组件(组件)
限制用户访问频率

登录用户的访问频率限制
未登录用户的访问频率限制 使用ip限制

主要需要利用缓存进行设置
# 1.安装redis缓存
	pip install django-redis
    
# 2.需要在项目中对settings.py进行配置
# 配置缓存
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',  # 执行的redis环境包
        'LOCATION': 'redis://127.0.0.1:6379',  # 当前redis的启动的ip端口进行链接
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',  # 客户端类环境包
            # 'PASSWORD': '',  # 密码
        }
    }
}

1.快速开始

# 1.导入限流的父类 与django中设置的缓存客户端对象
from rest_framework.throttling import SimpleRateThrottle # 这个类帮助我们写了很多代码比较方便
from django.core.cache import cache as  cache_redis # 导入redis缓存对象
from rest_framework import exceptions # 导入rest frame中的异常类


# 2.编写类继承限流父类
class ThrottledException(exceptions.APIException): # 自定义异常类
        '''自定义的异常类 用来抛出限流类的限流信息'''
        pass

class MyS(SimpleRateThrottle):
    '''自定义限流类'''
    cache = cache_redis  # 当前类变量设置redis缓存对象设置

    # 当前类变量都来自于继承的父类中主要作用构造唯一标识
    cache_format = 'throttle_%(scope)s_%(ident)s'  # 构建缓存中的key
    scope = 'user'  # 表示

    # 当前变量主要进行设置频率限制,当前字典的key 需要和scope变量进行对应(源码设置好了通过THROTTLE_RATES['scope'])取值
    # 10/m 1分钟访问10次  's' 'sec' 'm' 'min' 'h' 'hour' 'd' 'day'
    THROTTLE_RATES = {'user': '10/m'}
	
    1.唯一标识
    def get_cache_key(self, request, view):
        '''重写父类的方法作用(返回唯一标识)如果唯一标识需要修改,就在当前方法中进行修改'''
        if str(request.user) != 'AnonymousUser' and request.user:  # 判断当前是否时登录用户(1.不能时匿名用户 2.必须是正常用户)
            ident = 1  # 是获取他的id
        else:
            ident = self.get_ident(request)  # 不是获取他的ip(当前方法在父类中从请求头中获取)
        return self.cache_format % {'scope': self.scope, 'ident': ident}  # 返回唯一标识

    2.当限流时返回限流的结构重构
    def throttle_failure(self):
        '''如果超过当前的限制次数,那么就会调用这个方法,将限制时间返回给前端页面'''
        wait = self.wait()  # 调用父类的方法 计算过期时间的比较
        detail = {
            'code': 1005,
            'data': '访问限制',
            'detail': '需要等待%ss才能访问' % int(wait)
        }
        raise ThrottledException(detail)


# 3.在api中进行使用
class PayView(APIView):
    throttle_classes = [MyS, ] # 在api 中进行使用
    def get(self, request, *args, **kwargs):
        print(cache_redis)
        return Response({'code': 1, 'data': 123456})
    
    
    
'''
1.使用的当前的redis进行设置限制信息的存储(存储在redis中的key val 会自动删除)这个比利用中间件方便的多
2.需要重写 父类中的 get_cache_key方法 主要就是自定义生成一个当前用户的唯一标识
3.重写父类的 throttle_failure 方法 主要是当达到限制条件就会触发 将结果返回给页面中
4.需要配置一定的参数
cache 配置对象的参数
cache_format 存储在redis对象中的 key的构造
scope  名称
THROTTLE_RATES 限制条件 {'scope':'限制条件'} 当前的变量需要根据scope进行获取限制条件(所以与scope有关联) 
'''

2.多个限流类

多个限流类中都有一个allow_request方法 
返回true 表示当前限流允许访问,接着执行限流类
返回为falas 表示当前限流类不可以访问 接着执行下述的限流类,将全部的限流类的时间计算在一起
抛出异常的话 后面的限流类就不会执行

如果定义多个限流类,让他同一的将限流时间返回需要在接口中定义方法
api 接口中定义 统一返回时间多个限流类中的时间最大的值(不推荐使用,推荐使用上述的在定义的限流类中进行设置)
在api 接口中重新定义的父类api 的方法,原方法  raise exceptions.Throttled(wait) 单纯的抛限流时间最长的值,而重新的方法显示的更为明确
    def throttled(self, request, wait):
        wait 就是当前全部限流类的时间总和
        detail = {
            'code': 1005,
            'data': '访问限制',
            'detail': '需要等待%ss才能访问' % int(wait)
        }
        raise ThrottledException(detail)

3.限流的全局配置

# 一般情况下不会对全局进行设置,只对部分比较重要的接口进行设置(根据特殊情况而定)
REST_FRAMEWORK = {
	'DEFAULT_THROTTLE_CLASSES':['限流类的路径1','限流类的路径2'], # 设置限流类中全局设置
	'DEFAULT_THROTTLE_RATES':{ # 当前限流类中的限流设置 可以设置多个需要根据自定义的限流类 中的scope 标识进行设置
		{'user': '10/m'},  user 就是当前 scope变量 标识
		.....1,
		.....2,
	}
}


# api 接口禁用 覆盖全局配置
DEFAULT_THROTTLE_CLASSES = [] 

4.源码

源码中的 throttle_durations变量就是存储这 循环每一个对象执行.allow_request方法 将限流不通过的 限流类中的.wait()方法将限制时间进行获取.添加到throttle_durations列表中,在判断throttle_durations列表中有没有值,如果没有值什么都不做(说明没有超过频率限制),如果有值就获取当前事件最大的值,在将值 进行异常抛出异常

数据效验序列化(serializert)

作用:
	1.对数据的校验(django 调用的form和modelform)
	2.对数据库查到的对象进行序列化
	
用户通过请求发来的数据通过serializert 进行格式化的校验

1.数据校验

1.开始使用(当前已经覆盖了全部的可能校验的方式) Serializer

from 表单一样
配置修改:
	LANGUAGE_CODE = 'zh-hans' 将表单错误现象变为中文
	查看django2.0中的from表单认证字段全部的内容
    
1.serializert自定义的类的编写
    from rest_framework import serializers  # 1.导入数据校验类(底层使用from表单认证时一样的)
    from django.core.validators import EmailValidator  # django自带的email认证规则
    from rest_framework import exceptions  # rest内部的异常类
    
2.编写自定义的验证类
    # 自定义邮箱re认证类
    class RegexValidator(object):
        def __init__(self, base):
            self.base = base  # 传入的验证规则

        def __call__(self, value):
            import re
            match_obj = re.match(self.base, value)
            if not match_obj:
                raise serializers.ValidationError('邮箱验证失败')


# 2.自定义类进行继承父类编写校验规则
    class UserSerializers(serializers.Serializer):
        level_choices = [(0, 'vip'), (1, 'vips'), (2, 'vipss')]	
		
        # 多选框字段choices 对应[(id,'名称')] 必须传入当前返回的值不然验证不通过
        level = serializers.ChoiceField(label='角色', choices=level_choices) 
        
        # 最小长度 与最大长度	
        username = serializers.CharField(label='姓名', min_length=3, max_length=10)  
       
        # int类型的范围
        age = serializers.IntegerField(label='年龄', min_value=0, max_value=200)  
        
        # 1.内部自带邮箱认证规则字段
        email1 = serializers.EmailField(label='邮箱1')  
        
        # 2.使用自带django的re认证类进行验证
        email2 = serializers.CharField(label='邮箱2', validators=[EmailValidator, ])  
        
        # 3.自定义re认证类规则验证
        email3 = serializers.CharField(label='邮箱3', validators=[RegexValidator(r'^\w+@\w+\.\w+$')])  
        # 4.在类内部使用钩子函数指定验证规则
        email4 = serializers.CharField(label='邮箱4')  

        def validate_email4(self, value):
            '''email4钩子函数'''
            import re
            if re.match(r'^\w+@\w+\.\w+$', value):
                return value
            raise exceptions.ValidationError('邮箱格式有误')
            
# 3.在api接口中进行使用
	class Users(APIView):
		
        def post(self, request, *args, **kwargs):
            print(request.data)  # 接收前端传入的值

            data_dic = UserSerializers(data=request.data)
            '''
            data_dic.errors 验证失败的字段都存储在这里
            data_dic.validated_data 验证成功的结果都在这里
            '''
            if not data_dic.is_valid():  # 校验失败(必须都要验证通过,如果有一个失败就会返回失败的校验结果)
                return Response({'code': 1004, 'error': data_dic.errors})  # 返回校验失败的结果
            
            print(data_dic.validated_data) # 验证成功的结果进行增删改查
            return Response({'code': 1002, 'mag': data_dic.validated_data})  # 返回校验成功的数据(都需要成功)

2.验证前端数据ModelSerializer(orm操作)

与 modelfrom一样
ModelSerializer 根据数据库的字段进行认证比Serializer 自己编写字段代码要少

通过数据库表的字段进行认证
1.表设置
    class User(models.Model):

        username = models.CharField(verbose_name='用户名',max_length=32)
        pwd = models.CharField(verbose_name='密码',max_length=64)
        
2.导包
    from rest_framework import serializers  # 1.导入数据校验类(底层使用from表单认证时一样的)
    from rest_framework import exceptions  # rest内部的异常类
    from app import models
    
3.自定义ModelSerializer
	# 2.自定义类进行继承父类编写校验规则
    class UserSerializers(serializers.ModelSerializer):
        email4 = serializers.CharField(label='邮箱4')  # 表中的字段不够用使用自定字段

        class Meta:
            model = models.User  # 导入认证数据库user
            fields = ['username','email4']  # 验证数据库的字段,添加一个自定义认证字段
            extra_kwargs = {  # 额外添加参数,在内部字段变为serializer认证字段时,添加的参数
                'username': {'min_length': 6, 'max_length': 32}
            }

        def validate_email4(self, value):
            '''email4钩子函数'''
            import re
            if re.match(r'^\w+@\w+\.\w+$', value):
                return value
            raise exceptions.ValidationError('邮箱格式有误')
  
4.在api接口中进行使用
	class Users(APIView):

    def post(self, request, *args, **kwargs):
        print(request.data)  # 接收前端传入的值
        data_dic = UserSerializers(data=request.data)
        
        if not data_dic.is_valid():  # 校验失败(必须都要验证通过,如果有一个失败就会返回失败的校验结果)
            return Response({'code': 1004, 'error': data_dic.errors})  # 返回校验失败的结构
        
        # 将自定义字段从中删除 数据库中没有字段存在如果进行save 就会报错
        data_dic.validated_data.pop('email4')
        
        # 将数据添加到数据库中 在save(可以将数据库中的字段设置默认值pwd='123') 将用户输入的值同时将默认值添加到数据库中
        user_obj = data_dic.save(pwd='666666') #  返回值新添加的user的对象
        
        return Response({'code': 1002, 'mag': data_dic.validated_data})  # 返回校验成功的数据(都需要成功)
    
   
当前ModelSerializer也是可以进行1对多和1对1的进行存储的只需要将字段给添加就可以
多:[1,2,3]
1:1
ModelSerializer 具有对第三表进行存储的功能

2.orm对象序列化(orm)

1.简单

通过orm从数据库中获取到的queryset 或者对象序列化为json格式数据
1.导入包
from rest_framework import serializers  # 1.导入数据校验类(底层使用from表单认证时一样的)
from app import models

2.定义自定类
# 2.自定义类进行继承父类编写校验规则
class UserSerializers(serializers.ModelSerializer):
    class Meta:
        model = models.User  # 导入数据库
        fields = ['username', 'pwd']  # 验证数据库的字段

3.查询api结果进行序列化
class Users(APIView):

    def get(self, request, *args, **kwargs):
        '''获取user列表'''
        queryset = models.User.objects.all() # user 表
        ser = UserSerializers(instance=queryset, many=True) # many=True 获取的多条数据 如果是一个对象many=false
        print(ser.data) # 序列化之后的结果(将orm对象获取的全部数据,进行json格式化返回给前端)
        return Response({'code': 2000, 'data': ser.data})
    
 """
 内部支持 序列化嵌套与字段自定义
 """

2.1自定义字段

1.表结构
    class Role(models.Model):
        '''角色表'''
        name = models.CharField(verbose_name='角色', max_length=32, null=True)


    class Depart(models.Model):
        '''部门表'''
        title = models.CharField(verbose_name='部门', max_length=32, null=True)


    class User(models.Model):
        level_choices = ((1, '普通会员'), (2, 'vip'))
        level = models.IntegerField(verbose_name='级别', choices=level_choices, null=True)
        username = models.CharField(verbose_name='用户名', max_length=32)
        pwd = models.CharField(verbose_name='密码', max_length=64)
        departs = models.ForeignKey(verbose_name='用户or部门', to=Depart, on_delete=models.CASCADE, null=True, blank=True)
        roles = models.ManyToManyField(verbose_name='用户 or 角色', to=Role)

        
        
2.导包
from rest_framework import serializers  # 1.导入数据校验类(底层使用from表单认证时一样的)
from app import models
from django.forms.models import model_to_dict # django内部转为字典

3.自定义序列化类
class UserSerializers(serializers.ModelSerializer):
    '''source 在序列化器参数字段参数设置(source='表模型字段名') 具有执行当前表对象.字段  获取数据赋值给变量(底层通过反射进行对象.什么内容..) '''

    # 当前的字段为choices类型((1,vip),(2,普通员工)) 内部会通过 对象.get_choices类型字段_display() 进行执行
    # source='get_level_display' 内部会执行get_level_display() 就会获取choices的中文名称
    level_text = serializers.CharField(
        source='get_level_display')

    # 关联部门表,fk 1对多的关系 获取当前user表中的部门的中文(通过执行user表中的字段source = departs.title 获取部门的中文)
    # depart字段在user表中与部门表是1对多的关系表
    depart = serializers.CharField(source='departs.title')

    # 多对多字段显示(比较复杂的显示) 这个字段时可以编写钩子函数的
    roles = serializers.SerializerMethodField()

    class Meta:
        model = models.User  # 导入数据库
        fields = ['username', 'pwd', 'level_text','depart','roles']  # 验证数据库的字段

    def get_roles(self,obj):
        '''roles 的钩子方法从对象.tokto进行获取全部的角色信息'''
        print(obj) # 当前user 表的对象
        data_list = obj.roles.all() # 全部角色
        return [model_to_dict(item,['id','name']) for item in data_list]

# 4.api 接口序列化使用
class Users(APIView):

    def get(self, request, *args, **kwargs):
        '''获取user列表'''
        queryset = models.User.objects.all()  # user 表
        ser = UserSerializers(instance=queryset, many=True)  # many=True 获取的多条数据 如果是一个对象many=false
        print(ser.data)  # 全部的序列化数据
        return Response({'code': 2000, 'data': ser.data})

3.序列化类嵌套

针对表和表之间的关联 fk  m2m
# 1.表结构
    class Role(models.Model):
        '''角色表'''
        name = models.CharField(verbose_name='角色', max_length=32, null=True)


    class Depart(models.Model):
        '''部门表'''
        title = models.CharField(verbose_name='部门', max_length=32, null=True)


    class User(models.Model):
        level_choices = ((1, '普通会员'), (2, 'vip'))
        level = models.IntegerField(verbose_name='级别', choices=level_choices, null=True)
        username = models.CharField(verbose_name='用户名', max_length=32)
        pwd = models.CharField(verbose_name='密码', max_length=64)
        departs = models.ForeignKey(verbose_name='用户or部门', to=Depart, on_delete=models.CASCADE, null=True, blank=True)
        roles = models.ManyToManyField(verbose_name='用户 or 角色', to=Role)
        
# 2.导包
from rest_framework import serializers  # 1.导入数据校验类(底层使用from表单认证时一样的)
from app import models
from django.forms.models import model_to_dict # django内部转为字典


# 3.序列化类
定义部门学序列化类
class DepartModel(serializers.ModelSerializer):
    class Meta:
        model = models.Depart
        fields = '__all__'  # 显示全部字段


定义角色序列化类
class RolesModel(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = '__all__'  # 显示全部字段


定义显示的user表中的序列化类
class UserSerializers(serializers.ModelSerializer):
    '''source 在序列化器参数字段参数设置(source='表模型字段名') 具有执行当前表对象.字段  获取数据赋值给变量(底层通过反射进行对象.什么内容..) '''

    # 当前的字段为choices类型((1,vip),(2,普通员工)) 内部会通过 对象.get_choices类型字段_display() 进行执行
    # source='get_level_display' 内部会执行get_level_display() 就会获取choices的中文名称
    level_text = serializers.CharField(
        source='get_level_display')

    departs = DepartModel()  # fk
    roles = RolesModel(many=True)  # m2m 要显示多条many=True

    class Meta:
        model = models.User  # 导入数据库
        fields = ['username', 'pwd', 'level_text','departs','roles']  # 获取序列化的字段信息

        
# 4.api 接口序列化使用
class Users(APIView):

    def get(self, request, *args, **kwargs):
        '''获取user列表'''
        queryset = models.User.objects.all()  # user 表
        ser = UserSerializers(instance=queryset, many=True)  # many=True 获取的多条数据 如果是一个对象many=false
        print(ser.data)  # 全部的序列化数据
        return Response({'code': 2000, 'data': ser.data})

3.序列化和数据校验 整合在一起使用

导包
from rest_framework.views import APIView
from rest_framework.response import Response

from rest_framework import serializers  # 1.导入数据校验类(底层使用from表单认证时一样的)
from app import models


序列化数据校验
# 定义部门学序列化类
class DepartModel(serializers.ModelSerializer):
    class Meta:
        model = models.Depart
        fields = '__all__'  # 显示全部字段


# 定义角色序列化类
class RolesModel(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = '__all__'  # 显示全部字段


# 定义显示的user表中的序列化类
class UserSerializers(serializers.ModelSerializer):
    '''
    在对serializer类对用户前端传入的数据进行验证时
    many=False  显示一条
    many=True 显示多条
	partial=True 代表局部更新(不用强制性将serializers中的全部字段填写)
	
	设置serializer中字段设置
    read_only=True 不用写
    write_only=True 不用读
    如果不设置read_only和write_only 那么当前字段及强制写在查询时必须显示
    '''

    level_text = serializers.CharField(
        source='get_level_display', read_only=True)  # 添加是不强制写

    # 如果使用serializer嵌套 不是read_only=True 一定要自定义create 和update方法 同时要对嵌套中的字段进行限制
    # id read_only false 数据的校验 title read_only true 序列化显示
    departs = DepartModel(many=False, read_only=True)  # 添加是不强制写
    roles = RolesModel(many=True, read_only=True)  # 添加是不强制写
    email = serializers.EmailField(write_only=True)  # 自定义的字段 查询时不用强制显示

    class Meta:
        model = models.User  # 导入数据库
        fields = ['username', 'pwd', 'level_text', 'departs', 'roles', 'email']  # 获取序列化的字段信息
        extra_kwargs = {
            'pwd': {'read_only': True} # 对密码不强制写
        }

    def validated_username(self, val):
        '''对name字段的验证'''
        return val

接口
class Users(APIView):

    def get(self, request, *args, **kwargs):
        '''获取用户列表'''
        queryset = models.User.objects.all()  # user 表
        ser = UserSerializers(instance=queryset, many=True)  # many=True 获取的多条数据 如果是一个对象many=false
        print(ser.data)  # 全部的序列化数据
        return Response({'code': 2000, 'data': ser.data})

    def post(self, request, *args, **kwargs):
        '''添加用户'''
        res = UserSerializers(data=request.data)
        if not res.is_valid():
            return Response({'code': 1004, 'data': res.errors})  # 只要有错误验证不通过

        res.validated_data.pop('email')  # 数据库没有邮箱
        instance = res.save(pwd='123')  # 添加默认值字段密码
        print(instance)  # 新增加的对象 User object (4)
        # res.data 进行了一次序列化操作,将当前添加的对象给返回
        return Response({'code': 1002, 'data': res.data})

4.源码

序列化部分:
	在内部当进行实例化时会根据参数many=True and False 进行条件生成
	如果只获取一个对象,那么直接就会进行序列化
	如果是多个对象(多个值),那么就会调用many_init() 方法将每一个对象进行循环序列化
	在调用的 data方法就是执行了将instance=queryset对象的全部的值进行循环获取对应的参数
	to_representation 内部进行处理将将数据从数据库中进行获取,整理为字典形式返回data 方法
	对象.data 就是获取校验过的正确被序列化的值
	
数据校验:
	is_valid内部的校验都是由run_validation进行校验完成的
	内部的to_internal_value 方法是对自定义的钩子函数进行校验(内置的校验规则)
	run_validators 是对字段中定义validators 进行的校验
	
	
一种承包商的感觉
将大的步骤分给了小的承包商进行一步一步的进行操作

视图

是对APIviwe的一层封装
APIview 继承了django view
新增了 : 免除csrf 请求封装 版本 认证 权限 限流 数据校验数据序列化

1.GenericAPIview(不是重点)

class GenericAPIview(APIview): # 继承了APIview
	pass
GenericAPIview 当前在apivie基础上添加了一些功能 get_queryset get_object
# 注意:在开发中不会直接使用GenericAPIView,它只是作为一个中间商的角色为继承它的子类提供方法
1. 导入方式
from rest_framework.generics import GenericAPIView


2.新增的方法
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from app import models
from rest_framework import serializers

class MySerializers(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = '__all__'
        
class Users(GenericAPIView):
    queryset = models.User.objects.filter(status=True)
    serializer_class = MySerializers

    def get(self, request, *args, **kwargs):
        # 读取类变量中的queryset字段 并执行获取数据库对象
        queryset = self.get_queryset()

        # 获取serializer 的实例并且实例化 (就是读取了serializer_class字段进行实例化)
        # 相当于MySerializers(intance=queryset, many=True)
        ser = self.get_serializer(intance=queryset, many=True)
        
        print(ser.data)
        return Response({'code': 2000, 'data': 111})
    
# self.get_queryset() or self.get_serializer 对设置的类变量进行实例化,调用方法获取内部处理好的实例化对象 方便调用

3.作用
'''
GenericAPIView 的主要作用就是
 - 从数据库中获取数据
 - 对序列化类进行序列化
将变量提取到类类变量中,方便定义,如果调用了get_serializer  get_queryset 就会进行读取
不会直接继承GenericAPIView
作为一个中间人的作用,完成一些功能,方便继承它的子类调用它新增的方法
提供了get_object 也就是在数据库对象.get(id=1) 的单条数据查询
'''

2.GenericViewSet

主要的作用就是将方法拆开,独立的实现当前方法自己功能 查看一条就是一条数据 更新就是更新 删除就是删除

1.导入
from rest_framework.viewsets import GenericViewSet
	内部继承了GenericAPIview 和 ViewSetMixin

2.新增加的功能
ViewSetMixin 类的主要作用就可以在url中写上对应关系
	
    # 1.之前的视图编写方式和路由编写方式
	urls.py: url
	# 只能获取get 获取用户列表  post 添加用户
    path('api/<str:version>/user/', views.Users.as_view()),
    # get 获取单独用户 delete 删除单独用户 put修改单独用户 patch修改单独用户
    path('api/<str:version>/user/<int:pk>', views.Users.as_view()),
    
	views.py:视图  每一个方法都可能对应了两个逻辑,单条处理函数多条处理
    from rest_framework.views import APIView
    
    class Users(APIView):
        def get(self, request, *args, **kwargs,pk=None):
            if pk:
                ....
            return Response({'code': 2000, 'data': 111})
        def post(self, request, *args, **kwargs):
            return Response({'code': 2000, 'data': 111})
        def put(self, request, *args, **kwargs,pk=None):
             if pk:
                ....
            return Response({'code': 2000, 'data': 111})
        def delete(self, request, *args, **kwargs,pk=None):
             if pk:
                ....
            return Response({'code': 2000, 'data': 111})
        
        
  # 2.继承了GenericViewSet类 那么ViewSetMixin路由的就需要填写对应关系
	主要的作用就是将方法拆开,独立的实现当前方法自己功能 查看一条就是一条数据 更新就是更新 删除就是删除
    
    url:
    # 如果发送的是get 请求就会定位到 api接口中的list方法中 如果发送的post方法那么就会定位到 create方法中
        path('api/<str:version>/user/', views.Users.as_view({
            'get': 'list',
            'post': 'create'
        })),
        # 路由关系的对应
        path('api/<str:version>/user/<int:pk>', views.Users.as_view({
            'get':'retrieve',
            'put':'update',
            'dalete':"destory"
        })),
	
    api接口类
    
    from rest_framework.viewsets import GenericViewSet
    
    class Users(GenericViewSet):

        def list(self, request):
            return Response({'code': 2000, 'data': 111})

        def create(self, request):
            return Response({'code': 2000, 'data': 111})

        def retrieve(self, request, pk):
            return Response({'code': 2000, 'data': 111})

        def update(self, request,pk):
            return Response({'code': 2000, 'data': 111})

        def destory(self, request, pk):
            return Response({'code': 2000, 'data': 111})

3.五大视图(重点)

1.导入方式
from rest_framework.mixins import (ListModelMixin, CreateModelMixin, RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin)

2.每个类都实现了什么(增删改查)

'''
这5大视图内部帮助我们进行了增删改查的一系列的操作
前提需要使用GenericViewSet类在url中设置对应关系
GenericViewSet 继承了GenericAPIview 和 ViewSetMixin两个类
而这5大类在内部操作时都离不开两个类变量
queryset 与 serializer_class 类变量(这两个类变量是有GenericViewSet父类进行提供的)
主要根据这两个类变量进行获取数据查询和数据验证操作
'''
	1.ListModelMixin 实现了.../url get方式 获取全部的数据列表
    	内部实现了list方法
    2.RetrieveModelMixin 实现.../url/1/    get方式 获取单条数据
    	内部实现了retrieve方法
    3.DestroyModelMixin 实现.../url/1/ delete方法 get方式 单条删除
    	内部实现了destroy方法
    4.CreateModelMixin 实现 .../url post方式 对数据的增加
   		内部实现了create方法 将数据进行增加
        '''
   			1.用户提交的数据 request.data
   			2.调用serializers进行数据校验
   			3.保存到数据库(如果数据不够多的情况下,在save替用户补充数据)
   			4.在serializers中如果fields中有id字段(在添加时id不用添加 默认时 read_only=True)
   		'''
        在类中存在一个钩子方法,这个方法可以在自己类中进行重写,替用户补充数据
        def perform_create(self, serializer):
        	serializer.save() 
       将数据提交到后台,保存到数据库,并且将添加的数据信息返回给前端
    
    5.UpdateModelMixin 实现了.../url/1/ patch请求和put请求 局部更新和全部更新

4.对5大视图类重写父类中的方法

1.假设如果 list和retrieve 需要使用的serializer序列化类 与 create 使用的序列化类不一样,怎么办?
    # 1. 可以在serializer类中对字段进行    read_only 和 write_only 进行区分

    # 2.可以对 GenericViewSet 继承的父类中的  get_serializer_class 进行重写
    需要编写两个serializer类在不同条件下进行返回 例如
        def get_serializer_class(self):
            '''重写父类的方法,解决使用不同的serializer序列化类
            post 请求使用的serializer序列化类
            与
            get delete 使用的不同就可以重写这个方法加上一层判断
            '''
            if self.request.method == 'POST':
                return MySerializers
            return MySerializers2
    
    
2.如果用在进行post请求添加数据时数据不够多的情况下,在save替用户补充数据
	# CreateModelMixin 中的  perform_create 负责保存数据
	 def perform_create(self, serializer):
        '''
        CreateModelMixin类中的create方法调用的perform_create保存方法
        解决如果数据不够多的情况下,在save替用户补充数据
        '''
        serializer.save(departs=1) # 对为创建的用户添加一个字段部门id=1
 
3.如果用户进行更新,需要对用户更新一些其他的信息,可以重写perform_update
	# UpdateModelMixin 中的 perform_update方法时更新中save到数据的方法
    def perform_update(self, serializer):
        '''
        UpdateModelMixin 中的 perform_update
        可以每次更新时可以进行为用户更新一些字段
        如果不重写,那么用户更新什么就写什么
        '''
        serializer.save(tiem=time) # 用户更新数据的时间进行更新

5.多视图怎么使用区分

1.接口与数据库没有操作:使用APIview
2.接口中需要对数据库进行增删改查 使用5大视图因为内部已经进行增删改查
	利用视图中提供的钩子自定义完善功能
	重写某个方法完善功能

6.5大视图url编写和视图编写

url.py:
    
# 如果发送的是get 请求就会定位到 api接口中的list方法中 如果发送的post方法那么就会定位到 create方法中
    # 需要api 接口继承 GenericViewSet类才能使用路由的对应关系
    path('api/<str:version>/user/', views.Users.as_view({
        'get': 'list', http://127.0.0.1:8000/api/v1/user/ 发get请求获取全部列表
        'post': 'create' http://127.0.0.1:8000/api/v1/user/ 发post请求加上参数添加一条数据
    })),
    # 路由关系的对应
    path('api/<str:version>/user/<int:pk>/', views.Users.as_view({
        'get': 'retrieve', http://127.0.0.1:8000/api/v1/user/1/ 发get请求获取id=1数据
        'put': 'update', http://127.0.0.1:8000/api/v1/user/1/ 发put请求更新id=1数据
        'delete': "destroy", http://127.0.0.1:8000/api/v1/user/1/ 发destroy请求删除id=1数据
        'patch':'partial_update' http://127.0.0.1:8000/api/v1/user/1/ 发put请求更新id=1局部数据
    })),

注意:
使用这种方式需要继承GenericViewSet类,5大视图内部使用的方法也是路由也是使用路由对应关系的,如果想使用5大视图急需要和GenericViewSet进行配合使用


view.py
    '''
    put 全部更新 (serializers提供的全部字段,必须提供全,不然就会报错某个字段时必填项)
    partial_update 局部更新 (serializers提供部分字段,不用全部提供,少提供了也不会报错)
    '''

from rest_framework.viewsets import GenericViewSet
from app import models
from rest_framework import serializers
from rest_framework.mixins import (ListModelMixin, CreateModelMixin, RetrieveModelMixin, DestroyModelMixin,
                                   UpdateModelMixin)


class MySerializers(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = ['id', 'username', 'pwd']


class MySerializers2(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = ['id', 'username', 'pwd']


class Users(GenericViewSet, 
            ListModelMixin, 
            RetrieveModelMixin, 
            DestroyModelMixin, 
            CreateModelMixin, 
            UpdateModelMixin):
    
    queryset = models.User.objects.all() # GenericViewSet 父类中的类变量
    serializer_class = MySerializers # GenericViewSet 父类中的类变量

    def perform_create(self, serializer):
        '''
        CreateModelMixin类中的create方法调用的perform_create保存方法
        解决如果数据不够多的情况下,在save替用户补充数据
        '''
        serializer.save(departs=1)

    def get_serializer_class(self):
        '''重写GenericViewSet父类的方法,解决使用不同的serializer序列化类
        post 请求使用的serializer序列化类
        与
        get delete 使用的不同就可以重写这个方法加上一层判断
        '''
        if self.request.method == 'POST':
            return MySerializers
        return MySerializers2

    def perform_update(self, serializer):
        '''
        UpdateModelMixin 中的 perform_update
        可以每次更新时可以进行为用户更新一些字段
        如果不重写,那么用户更新什么就写什么
        '''
        serializer.save()
        
        
注意:
    1.使用5大视图(5大视图就是针对这增删改查)需要url进行对应关系,需要继承GenericViewSet类
    2.使用5大视图 需要用到queryset对象 和 serializer对象 也需要继承GenericViewSet类来提供
    3.使用5大视图可以重写内部的钩子方法,增加业务的需求量
    4. 切记编写url对应关系 与 queryset对象 和 serializer对象 

搜索条件filter

通过get进行查询,进行返回
api/v1/user?a=1&b=10

使用这个搜索组件需要使用到
类变量:queryset 和 serializer_class
那么就需要使用到 GenericViewSet 使用到这个类就需要使用到 url映射关系
需要设置url映射关系,就要使用到5大视图

所以后面的全部组件都和五大视图与路由的映射关系都有关

自定义Filter

用的比较少,项目比较小时进行使用

1.导入模块
from rest_framework.filters import BaseFilterBackend

2.编写自定义的fiter组件
继承BaseFilterBackend类
class Filter1(BaseFilterBackend):
    '''
    1.先读api类中的类变量queryset对象
    2.将queryset对象传给filter_queryset(内部可以加条件)
    3.会将第一个类中queryset对象的值在给到filter_backends = [filter1,filter2...] 中的的二个元素以此类推
    4.而第2个类开始,每个filter接收到queryset对象都是上一个传递下来的queryset对象
    5.除了post中的create不会在源码中执行Filter类,其他的5中方式都会执行
    6.其他的api读取到的queryset对象都是由filter类过滤下来的
    '''
	例如:http://127.0.0.1:8000/api/v1/user/4/?user_id=4
    def filter_queryset(self, request, queryset, view):
        user_id = request.query_params.get('user_id') # 获取url get方式传入的参数
        if not user_id:  # 如果能在git方法中查询到当前的userid条件
            return queryset  # 如果没有这个条件,就查询到queryset对象给前端页面
        return queryset.filter(id=user_id)  # 就会根据这个条件进行返回对象


3.在api接口中进行使用

class Users(GenericViewSet, ListModelMixin, RetrieveModelMixin, DestroyModelMixin, CreateModelMixin, UpdateModelMixin):
    queryset = models.User.objects.all()
    serializer_class = MySerializers
    # 当进行查询时,就会进行先调用当前filter_backends的filter类调用filter_queryset
    filter_backends = [Filter1,filter2,filter3]

# 如果定义了filter类那么返回queryset都是根据filter在url 中 get参数中过滤下来的

第三方Filter

通用的使用方式属于第三方的组件
1.安装
pip install django-filter

2.在项目配置文件中进行注册
INSTALLED_APPS = [
	django_filters
]

1.第三方搜索案例(简单)

1.导入模块
from django_filters.rest_framework import DjangoFilterBackend

2.在api类中设置类属性
class Users(GenericViewSet, ListModelMixin, RetrieveModelMixin, DestroyModelMixin, CreateModelMixin, UpdateModelMixin):
    # 将导入的第三方进行配置到filter_backends变量上
    filter_backends = [DjangoFilterBackend] 
    # 查询字段的条件 例如:xxx/?id=1 or xxx/?username='xxx' or xxx/?username='xxx'&id=1
    filterset_fields = ['id','username']

    queryset = models.User.objects.all()
    serializer_class = MySerializers
    # 当进行查询时,就会进行先调用当前filter_backends的filter类调用中的filter_queryset方法进行检索,并将检索过的queryset返回,使用的就是当前检索过的queryset对象进行序列化数据给前端

2.第三方搜索(复杂)

需要定义自定义的类进行设置搜索条件,使用复杂的搜索
补充内容:
    '''
    field_name是搜索的字段名字/ lookup_expr搜索的属性
    lookup_expr属性常见的选择/构造的条件:
    exact  等于
    iexact 等于

    contains 包含
    icontains 包含

    startswith 以什么开头
    istartswith 以什么开头

    endswith 以什么结尾
    iendswith 以什么结尾

    gt  大于等于
    gte 大于等于
    lt 小于等于
    lte 小于等于

    in 在什么里面
    range  is in range
    isnull 是否为空

    regex  正则表达式搜索
    iregex
    '''

1.导入类
from django_filters.rest_framework import DjangoFilterBackend
from django_filters import FilterSet, filters # 导入继承的第三方包

2.编写复杂的搜索条件类

class MyFilterSet(FilterSet):
    '''自定义第三方搜索参考了serializers的使用方式
    可以进行自定义字段进行搜索
    例如:
    '''
    # NumberFilter 传入数字
    # field_name,表中的字段  lookup_expr 表示对字段搜索的范围(生成sql查询条件) gte 代表大于等于 也就是id>=1的搜索条件
    # 格式会相互对应可以配置更复杂的搜索条件
    min_id = filters.NumberFilter(field_name='id',lookup_expr='gte')

    class Meta:
        model = models.User  # 当前的搜索就针对user表进行搜索
        fields = ['id','min_id'] # 可以通过url 传id进行搜索
        # xxx/url/?min_id = 3 条件 [id>=3]


3.给api进行配置

class Users(GenericViewSet, ListModelMixin, RetrieveModelMixin, DestroyModelMixin, CreateModelMixin, UpdateModelMixin):
    # GenericViewSet内部的filter类变量 添加第三方的类变量(就是用到第三方类变量)
    filter_backends = [DjangoFilterBackend] 
    filterset_class = MyFilterSet # 将自定义的filterset类进行配置上

3.复杂搜索

class MyFilterSet(FilterSet):
    # 每个字段都具有不同的效果 filters.字段
    # url?min_id=2   -> id >= 2
    min_id = filters.NumberFilter(field_name='id',lookup_expr='gte')

    # url?name = wkx  -> not(username='wkx') exclude=True 代表条件取反的意思(除了wkx获取其他全部的)
    name = filters.CharFilter(field_name='username',lookup_expr='exact',exclude=True)

    # url/?depart=xx  -> depart__tile like %xx% 使用sql了模糊搜索
    departs = filters.CharFilter(field_name='departs__title', lookup_expr='contains')

    # url? token = true  token is null
    # url? token = false token is not null  当前的搜索条件就是取反
    level = filters.BooleanFilter(field_name='level',lookup_expr='isnull')

    # 一个字段多个条件 MultipleChoiceFilter 接收多个参数 可以传入choices参数的
    # url/?levels=1&levels=2-> sql  level=1 or level = 2(内部必须有数据否则报错,内有校验机制)
    levels = filters.MultipleChoiceFilter(field_name='level',lookup_expr='exact',choices=models.User.level_choices)

    # BaseInFilter 一个参数传入接收个值 in
    # url/? ids =18,12  sql底层查询条件就是 id in [18,22]
    ids = filters.BaseInFilter(field_name='id',lookup_expr='in')

    # 最小值和最大值搜索 返回搜索,在底层sql中 将这个范围的全部搜索到
    # url/range_id_max=1 & range_id_min=10 -> 生成sql语句 id between 1 and 10
    range_id = filters.NumericRangeFilter(field_name='id',lookup_expr='range')

    # 排序
    # url/?ord=id   - > order by id aes 根据id进行正序
    # url/?ord=-id - > order by id desc 根据id进行倒叙
    # fields可以传入多个数据库的字段
    ord = filters.OrderingFilter(fields=['id'])

    # 自定义方法
    # method 自定义的函数 filter_size
    # 定义自定义方法需要 filter_自定义字段的名字(self,queryset,name,value) 内部可以根据名字进行反射
    # filter_size 方法根据url中的get参数进行limit 1 搜索数据
    # url?size=1 -> limit 1
    # required=False 可传入参数,也可以不传 (是否必须去传get查询参数)
    # distinct=False 代表不去重
    size = filters.CharFilter(method='filter_size',distinct=False,required=False)

    class Meta:
        model = models.User  # 当前的搜索就针对user表进行搜索
        fields = ['id','min_id','name','departs','level','levels','ids','range_id','ord','size'] # 可以通过url 传id进行搜索 等全部内部的字段进行搜索
        # xxx/url/?min_id = 3 条件 [id>=3]

    # 自定义的过滤条件(如果第三方自带的不够用就使用自定义的)
    def filter_size(self,queryset,name,value):
        '''
        :param queryset: 查询的对象
        :param name:
        :param value: url 中的get对应的查询参数
        :return: 根据参数处理过的返回的queryset值
        '''
        int_value = int(value)
        return queryset[0:int_value]

内置Filter

第三方组件fiter已经将内置的全部的方法给包含了
内置存在两个 OrderingFilter(排序的filter搜索) SearchFilter(支持模糊搜索)
1.导入包

from rest_framework.filters import OrderingFilter

2.在api中进行配置

class Users(GenericViewSet, ListModelMixin, RetrieveModelMixin, DestroyModelMixin, CreateModelMixin, UpdateModelMixin):
    filter_backends = [OrderingFilter]
    # 在url中get方式 必须传入的参数就是order 其他参数不行
    # ?order=id 正序
    # ?order = -id 倒叙
    ordering_fields = ['id','username']
    
    
# 这个内置的内置的搜索    SearchFilter 还是可能会用到的
1.导包
from rest_framework.filters import SearchFilter

2.api 配置
class Users(GenericViewSet, ListModelMixin, RetrieveModelMixin, DestroyModelMixin, CreateModelMixin, UpdateModelMixin):
    filter_backends = [SearchFilter]
    # 这个方式是模糊搜索, search_fields 根据这设置的字段,会从表中的字段进行搜索
    # 例如:?search='wkx' 那么先从id中找,在到username中找 找到就返回queryset对象
    search_fields = ['id', 'username']

全局配置

# 使用通用的搜索时才会放到全局使用
REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS':['搜索filter类路径1','搜索filter类路径2']
}

分页

分页提供了多条方式 3中方式
如果使用的apiview视图
那么需要自己手动实例化和调用相关的方法进行分页
如果使用apiview的派生类
不需要进行实例和调用,内部已经处理好了

继承apiview使用分页

1.1.PageNumberPagination 基本用法

******** 第一个PageNumberPagination ********

1.导包
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.pagination import PageNumberPagination

2.在api中进行使用
class MySerializers(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = ['id', 'username', 'pwd']


class Users(APIView):
    def get(self, request, *args, **kwargs):
        queryset = models.User.objects.all().order_by('id')

        # 2.实例化分页类
        page = PageNumberPagination()
        # 3.调用分页类中的paginate_queryset方法将queryset传入返回一个分完页queryset对象
        paginaet_queryset = page.paginate_queryset(queryset, request, self) 
        # 4.进行校验(完成后就是当前根据url get传入参数的分页的对象显示在前端)
        ser = MySerializers(instance=paginaet_queryset, many=True)
        return Response(ser.data)

3.url怎么传入分页数据的(如果没有对PageNumberPagination进行重新设置)那么默认get参数就是page
当配置上当前的分页组件后,http://127.0.0.1:8000/api/v1/user/ 访问也是第一页的数据
http://127.0.0.1:8000/api/v1/user/?page=1 # 当前就是访问第一页

4.怎么设置分页显示的数量需要写配置文件
REST_FRAMEWORK = {
    # 配置当前参数,当前进行传入url get参数时显示的的数据数量
    'PAGE_SIZE':2
}

1.2.PageNumberPagination 高级用法

上面的基本用法都是固定设置设置的,如何控制最大方便的开放用户的使用 让用户设置显示多少条数据

# 1.导入类
from rest_framework.pagination import PageNumberPagination


# 2.自定义分页类
url/?page=1&size=10 # 当使用这个自定义的类时可以配置相应的参数,当前代表就是 第一页 显示10条数据
class MyPageNumberPagination(PageNumberPagination):
    page_size = 2 # 如果url不对size进行传入 url?page=1 那么当前页就显示2条
    page_size_query_param = 'size' # page显示第几页,size就是每页显示多少条
    max_page_size = 100 # size显示最大的值100条数据 url/?page=1&size=1100 限制size数量大 限制100条
    
    
# 3.在api中使用与上述节简单的方式一样 只不过使用的是继承PageNumberPagination的自定义类

2.1.LimitOffsetPagination基本使用

传入参数的方式是limit的方式,从querset中获取limit指定的数量
url?limit=2 # 那么当前就是从querset中获取2条数据 和sql中的limit性质是一样的
url?limit=2&offset=1 # 那么就是从offset定位到querset的第一条数据往后获取两条

# 1.导入类
from rest_framework.pagination import  LimitOffsetPagination
from app import models
from rest_framework import serializers
from rest_framework.views import APIView
from rest_framework.response import Response

# 在api中进行使用
class MySerializers(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = ['id', 'username', 'pwd']


class Users(APIView):
    def get(self, request, *args, **kwargs):
        queryset = models.User.objects.all().order_by('id')

        # 2.实例化分页类
        page = LimitOffsetPagination()
        # 3.调用当前的分页方法(处理queryset对象)
        paginaet_queryset = page.paginate_queryset(queryset, request, self)  # 返回分完的queryset
        print(paginaet_queryset)
        # 4.进行校验
        ser = MySerializers(instance=paginaet_queryset, many=True)
        return Response(ser.data)
   

3.在配置文件中设置的参数
REST_FRAMEWORK = {
    # 配置当前参数,当前进行传入url get参数时显示的的数据数量
    'PAGE_SIZE':2 默认limit的显示多少条数据
}

3.CursorPagination使用

CursorPagination不能直接使用,需要自定义类进行配置使用

1.导入类
from rest_framework.pagination import CursorPagination
from app import models
from rest_framework import serializers
from rest_framework.views import APIView

2.自定义类
class MyCursorPagination(CursorPagination):
    ordering = 'id'  # queryset对象根据什么字段排序 根据id排序
    page_size_query_param = 'size'  # size参数可以控制分页显示多少
    page_size = 2  # 每页显示多少条件默认显示
    max_page_size = 100  # 最大显示数量

3.api类中进行使用
class MySerializers(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = ['id', 'username', 'pwd']


class Users(APIView):
    def get(self, request, *args, **kwargs):
        queryset = models.User.objects.all().order_by('id')

        # 2.实例化分页类
        page = MyCursorPagination()
        # 3.调用当前的分页方法(处理queryset对象)
        paginaet_queryset = page.paginate_queryset(queryset, request, self)  # 返回分完的queryset
        # 4.进行校验
        ser = MySerializers(instance=paginaet_queryset, many=True)
        # 必须使用page对象中的response对象进行返回数据才能获取到数据以及上一页url和下一页url
        return page.get_paginated_response(ser.data)
# 必须使用get_paginated_response对象进行返回数据,才能看到当前的返回对象的值

注意:
http://127.0.0.1:8000/api/v1/user/?cursor=1 # 不可以传入cursor参数的会出现错误无效游标
会返回 next=url 上一页的url   previous= url 下一页的url,只能访问给你url,按照drf的规则进行访问上一页和下一页内容(不能自己去cursor 随意去写)

例如:
{
    "next": "http://127.0.0.1:8000/api/v1/user/?cursor=cD0z", # 下一页的加密数据url
    "previous": null, # 上一页 加密数据url
    "results": [ # 数据
        {
            "id": 2,
            "username": "嘿嘿",
            "pwd": "12sss3"
        },
        {
            "id": 3,
            "username": "wkxxxxx",
            "pwd": "123"
        }
    ]
}

继承派生类使用分页

主要通过GenericViewSet中间商类进行实现的效果
1.需要进行自定义分页类
2.在api中进行使用

1.PageNumberPagination使用

1.导入包
from rest_framework.viewsets import GenericViewSet
from app import models
from rest_framework import serializers
from rest_framework.mixins import ListModelMixin
from rest_framework.pagination import PageNumberPagination
from collections import OrderedDict
from rest_framework.response import Response

2.定义自定义的分页类
class MyCursorPagination(PageNumberPagination): # 分页类有三种,只需要进行修改当前父类

    page_size_query_param = 'size'  # size参数可以控制分页显示多少
    page_size = 10  # 每页显示多少条件默认显示
    max_page_size = 100  # 最大显示数量

    def get_paginated_response(self, data):
        '''可以重写分页父类中的方法,根据自定义进行返回数据内容'''
        '''
        data :返回的数据
        '''
        return Response(OrderedDict([
            ('code',1002), # 多添加一个状态码
            ('count', self.page.paginator.count), # 数据的总数
            ('next', self.get_next_link()), # 数据的上一页
            ('previous', self.get_previous_link()), # 数据的下一页
            ('results', data) # 当前访问的数据

        ]))
    
3.api类使用
class MySerializers(serializers.ModelSerializer):
    '''序列化类'''
    class Meta:
        model = models.User
        fields = ['id', 'username', 'pwd']


class Users(ListModelMixin, GenericViewSet):
    # 出现报错:提示数据对象没有排序,可以使用order进行排序
    queryset = models.User.objects.all().order_by('-id') 
    serializer_class = MySerializers
    # 在派生类中只需要将分页类设置上,内部已经进行了调用实例化,将分页返回
    # 使用分页类,内部不仅仅将访问的分页数据返回,同时数据的总和 上一页 下一页的url
    '''
    {
    "count": 6,
    "next": null,
    "previous": null,
     "results": [数据]
    }    
    '''
    pagination_class = MyCursorPagination # 配置分页类

 4.全局配置
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS':'分页的类的路径'
}

路由

1.继承apiview使用的是传统的方式使用
path('api/<str:varsion>/user', views.Users.as_view())

2.继承派生类的路由需要填写对应关系
path('api/<str:version>/user/', views.Users.as_view({'get': 'list'})),

3.drf 内部的自动生成路由的组件(仅对派生类启作用)
    1.导包
    from rest_framework import routers
    2.实例化生成对应接口
    # 实例化
    router = routers.SimpleRouter()
    # 调用传入 1.url的路径 2.当前对应的类
    router.register(r'api/user', views.Users) # 指定生成对关系

    3.加入到路由中
    urlpatterns = []
    urlpatterns += router.urls

4.使用路由分发
 router = routers.SimpleRouter()
 # 调用传入 1.url的路径 2.当前对应的类
 router.register(r'user', views.Users) # 指定生成对关系
urlpatterns = [
path('api/',include(router.urls)) # 如果api 和版本前缀太麻烦可以使用路由分发
]

解析器

from rest_framework.parsers import JSONParser,FormParser,MultiPartParser,FileUploadParser

JSONParser: 必须携带 application/json请求头
FormParser: 必须携带 application/x-www-form-urlencoded 表单的请求头
MultiPartParser 是multipart/form-data 请求头  可以携带文件也可以携带数据
FileUploadParser  只能上传文件,不能上传数据 需要进行上传2进制文件 需要对请求头进行修改
Content-Type : */*
Content-Disposition : attachment;filename=文件名.jpg

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser,FileUploadParser


class Users(APIView):
    # js请求头  form表单请求头  可以上传文件的请求头
    parser_classes = [JSONParser, FormParser, MultiPartParser] # 默认存在的解析器

    def get(self, request, *args, **kwargs):
        print(request.content_type) # 获取请求的头部携带Content-Type
        print(request.data) # 获取请求的参数
        return Response({'data': 1001})

 默认解析器:
    JSONParser, FormParser, MultiPartParser
    
如果需要处理特殊的格式,可以继承
内部BaseParser进行解析自定义类进行特殊格式的解析

备注

关于下列4中组件只需要配置一次就可以
1.版本控制
2.用户认证
3.权限
4.限流

常用
数据校验serializer组件
视图组件
搜索组件

Django-API补充

关闭调试功能

使用rest_framework 一定要在django app程序中进行注册,不然会报错
尽量的使用序列化器,非常好用,认证渲染都可以。底层继承了modelfrom类


# 关闭API调试界面
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES':('rest_framework.renderers.JSONRenderer', )
}

API处理响应数据的工具类

通过类的dict属性,可以将init初始化的变量整合为一个字段进行返回


class BaseResponse:

    def __init__(self):
        self.code = None
        self.data = None
        self.error = None

    @property
    def dict(self):
        '''__dict__ 会将全部的初始化属性当成一个字典返回'''
        return self.__dict__
    
res = BaseResponse()
res.dict # 直接调用


# 案例配合接口进行使用
def post(self, request, *args, **kwargs):
        res = BaseResponse()
        ser_obj = RegisterSerializers(data=request.data) # 使用序列化器进行校验 
        if ser_obj.is_valid(): # 通过 赋值200状态码,将数据返回
            ser_obj.save()
            res.code = 200
            res.data = ser_obj.data
        else: # 失败赋值401状态码 将错误信息返回
            res.code = 401
            res.error = ser_obj.errors
            
        return Response(res.data)
# 登录注册采用序列化认证的方式
# 前端注册的用户密码通过前端加密传入后端,后端进行保存 post
# 登录验证时,通过前端加密的密码与后端数据库存储的密码进行对比登录

个人配置设置

1.创建一个专门存放配置信息的(mysql,redis)
local_settings.py # 敏感信息
例如存放 数据库的信息和缓存信息(数据库的密码与缓存的密码信息)

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite5',
    }
}

2.在django的配置文件中导入当前文件原来的配置文件的配置覆盖

try:
    from .local_settings import *
except ImportError:
	pass        

# 目的防止配置信息外泄

链接mysql


django3 比较支持 mysqlclient

pip install mysqlclient

创建数据库时一定设置数据的字节码,不然存放中文报错
CREATE DATABASE 数据库名字 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

使用api可以关闭的app

image-20231113104459117.png

对QueryDict值进行修改

data = request.data # 将前端传入的值获取
_mutable = data._mutable # 获取他的_mutable属性
data._mutable = True # 当_mutable 设置true 当前的querydict才能被修改
data['excerpt'] = 'chenxinming' # 设置值
data._mutable = _mutable
#### 清除html的符号 #####
summary = re.sub(r'<.*?>', u'', data['content'])[0:400] # 清除标签
''.join(summary.split()).replace('&nbsp;','') # 清除文本的空白,在清除html的空格符号

分离模式下用户认证

前后端分离没有办法像前后端不分离一样进行将用户登录的状态存放在session或者cookie中,生成特殊的token进行记录,证明用户登录状态。

1.将token(令牌)写到redis中
	uuid 或者 token携带或者token 写的如redis中
    将token令牌放到redis中,再将token放在请求头中,每次携带进行认证。
2.将toekn(令牌)写在数据库中
	# 消耗性能
3.将token(令牌)携带到请求头中,每次都会携带
	jwt携带 最简单的一种方式

代码案例:    
基于redis请求池存放token并且认证token的基于djangoAPI的认证组件
conn = redis.Redis(connection_pool=POOL)
class MyAuth(BaseAuthentication):
    def authenticate(self, request):
        # 将token放在请求头中在进行redis中获取比对
        token = request.META.get('HTTP_AUTHENTICATE', '')
        # 当前请求头中没有uuid进行抛出错误
        if not token:
            raise AuthenticationFailed('没有携带token')
        user_id = conn.get(str(token))
        # 请求头中有uuid但是在redis中没有,抛出过期
        if user_id is None:
            raise AuthenticationFailed('token已经过期')
        # 请求头中存在token并且在redis中没有过期,获取对象
        user_obj = Account.objects.filter(id=user_id).first()
        # 将用户对象和token返回
        return user_obj, token
    
# 需要在登录认证时,必须带,如果不需要带认证就不用带认证

在登录时,登录通过
	1.将 用户的id和token存放在redis中
    	token : id
        会将token返回给前端
    2.前端会在请求头中携带token,在认证时
    	通过携带的token从redis中获取redis存放的token,
        将获取的用户id在从mdoel中获取对象
        组件返回
        
# 登录时用户的认证信息可以存放在redis中进行缓存,每次访问,获取进行验签