drf入门到精通

发布时间 2023-06-01 15:59:05作者: 雀雀飞了

drf入门到精通

1.Web应用模式

在开发Web应用中,有两种应用模式:

  1. 前后端不分离

  2. 前后端分离

2.API接口

通过网络,规定了前后台信息交互规则的url链接,也就是前后台信息交互的媒介

Web API接口和一般的url链接还是有区别的,Web API接口简单概括有下面四大特点

  • url:长得像返回数据的url链接

  • 请求方式:get、post、put、patch、delete

    • 采用get方式请求上方接口
  • 请求参数:json或xml格式的key-value类型数据

    • ak:6E823f587c95f0148c19993539b99295
    • region:上海
    • query:肯德基
    • output:json
  • 响应结果:json或xml格式的数据

    • 上方请求参数的output参数值决定了响应数据的格式
    • 数据
    # xml格式
    https://api.map.baidu.com/place/v2/search?ak=6E823f587c95f0148c19993539b99295&region=%E4%B8%8A%E6%B5%B7&query=%E8%82%AF%E5%BE%B7%E5%9F%BA&output=xml
      
    # json格式
    https://api.map.baidu.com/place/v2/search?ak=6E823f587c95f0148c19993539b99295&region=%E4%B8%8A%E6%B5%B7&query=%E8%82%AF%E5%BE%B7%E5%9F%BA&output=json
    {
        "status":0,
      	"message":"ok",
        "results":[
            {
                "name":"肯德基(罗餐厅)",
                "location":{
                    "lat":31.415354,
                    "lng":121.357339
                },
                "address":"月罗路2380号",
                "province":"上海市",
                "city":"上海市",
                "area":"宝山区",
                "street_id":"339ed41ae1d6dc320a5cb37c",
                "telephone":"(021)56761006",
                "detail":1,
                "uid":"339ed41ae1d6dc320a5cb37c"
            }
          	...
    		]
    }
    

3.接口测试工具:Postman

Postman是一款接口调试工具,是一款免费的可视化软件,同时支持各种操作系统平台,是测试接口的首选工具。还有postwoman、apifox

Postman可以直接从官网:https://www.getpostman.com/downloads/下载获得,然后进行傻瓜式安装。

  • 工作面板

  • 简易的get请求

  • 简易的post请求

  • 案例:请求百度地图接口

# get请求,没有请求体,post等请求有
# post请求,有多种编码格式
	-urlencoded 格式    --->默认格式  b'xx=xx&yy=yy'
  -form-data 格式     --->传文件格式,也可以带数据,选form-data有下拉框选file
    	----------------------251971281264627087498320--  带数据,带文件
  -json格式(用的最多) --->只能传数据,选raw,然后下拉框json   b'{"name":"lqz","age":18}'
    
    
# 如果是json形式提交数据 ---> django中想取,从request.POST取出到的为空,只能从request.body中取出,取出来的是二进制的字节类型数据,需要json.loads反序列化。
# Django 的内置解析器可以处理 “multipart/form-data” 格式的请求体。但是,如果请求体中包含的数据格式不正确或者数据非常大,可能会导致解析出错,从而引起 request.body 报错。

# day1 作业
'''
# postman装好
# 写三个接口
	-接收urlencoded格式的请求,post接口,把接收到的数据,后端打印
	-接口form-data,把文件保存到本地,数据打印出来
	-接收json形式,把数据打印出来
		-json.loads(request.body)
'''
# 路由
from django.conf.urls import url
from app01 import views
urlpatterns = [
    url(r'^drf01/$', views.drf01),
    url(r'^drf02/$', views.drf02),
    url(r'^drf03/$', views.drf03),
]

# 视图
import os
from django.http import JsonResponse
def drf01(request):
    name = request.POST.get('name')
    print(name)
    return JsonResponse('drf01', safe=False)
  
def drf02(request):
    file = request.FILES.get('cpdd')
    print(file, type(file), file.name)
    # type:<class 'django.core.files.uploadedfile.InMemoryUploadedFile'>
    
    """
    class InMemoryUploadedFile(UploadedFile):
    		def __init__(self, file, field_name, name, content_type, size, charset, content_type_extra=None):
        super().__init__(file, name, content_type, size, charset, content_type_extra)
        self.field_name = field_name
        """
          
    if file is not None:
        # 打开一个文件进行写入操作
        file_path = os.path.join('static',file.name)
        with open(file_path, 'wb+') as f:
            # 将文件分块读取并写入
            # for chunk in file.chunks():
            #     f.write(chunk)
            f.writelines(file)
        # 返回上传成功的信息
        return JsonResponse('File has been uploaded.', safe=False)
    else:
        # 返回文件上传失败的信息
        return JsonResponse('No file was selected.', safe=False)
      
'''
    for chunk in file.chunks() 是在 Django 中使用的方法,它可以将文件分段读取,而不是将整个文件加载到内存中。
    这对于上传和处理大型文件非常有用,因为它可以减少内存的使用。         
    for line in writelines() 是 Python 的内置方法。它用于将一个可迭代的序列写入文件中。                
'''


def drf03(request):
    name = json.loads(request.body)
    print(name)
    return JsonResponse(name)

4.Restful规范

# RESTful是一种定义API接口的设计风格,AIP接口的编写规范,尤其适用于前后端分离的应用模式中

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

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

# 10条规范
	1 数据的安全保障,通常使用https协议进行传输
  
  2 url地址中带接口标识:一般这样
    	- https://api.baidu.com
      - https://www.baidu.com/api
        
  3 多版本共存,url地址中带版本信息
    	- https://api.baidu.com/v1/login/
			- https://api.baidu.com/v2/login/
        
  4 数据即是资源,均使用名词:
      url地址尽量使用名词
      # 接口一般都是完成前后台数据的交互,交互的数据我们称之为资源
      - https://api.baidu.com/users
      -  https://api.baidu.com/books
      - https://api.baidu.com/book
      - 注:一般提倡用资源的复数形式,在url链接中不要出现操作资源的动词,错误示范:https://api.baidu.com/delete-user
      # 特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义
      - https://api.baidu.com/place/search
      - https://api.baidu.com/login
        
  5  资源操作由请求方式决定
    	#操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作
      - https://api.baidu.com/books   - get请求:获取所有书
      - https://api.baidu.com/books/1 - get请求:获取主键为1的书
      - https://api.baidu.com/books   - post请求:新增一本书书
      - https://api.baidu.com/books/1 - put请求:整体修改主键为1的书
      - https://api.baidu.com/books/1 - delete请求:删除主键为1的书
        
  6  url地址中带过滤条件 ?后带过滤条件
		  - https://api.baidu.com/books   - get请求表示查询所有图书
    	- https://api.baidu.com/books?name_contains=红:要查名字中有红的图书
    	- 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:指定筛选条件
        
        
   7 响应状态码(http响应中带状态码)
  		# https://www.sohu.com/a/278045231_120014184 趣味图解HTTP状态码的含义 
			# http常见的响应状态码:https://blog.csdn.net/meng2lin/article/details/128955775
    		-1xx:请求正在处理
        -2xx:请求成功 200  201
        -3xx:重定向
        -4xx:客户端错误
        -5xx:服务的错误
    	- http的响应的数据中带状态码(公司自己规定的)
    		- {code:100}
        
   8 返回的数据中带错误信息
			- {code:101,msg:用户名或密码错误}
    	- {code:100,msg:成功}
        
   9 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范
      GET  /books:返回资源对象的列表(数组)
    		-[{name:金品梅梅,price:88},{name:西游记,price:88}]
        -{code:100,msg:成功,data:[{name:金品梅梅,price:88},{name:西游记,price:88}]}
      GET /books/1:返回单个资源对象
        -{name:金品梅梅,price:88}    
        -{code:100,msg:成功,data:{name:金品梅梅,price:88}}
      POST /books:返回新生成的资源对象
        -{id:4,name:金品梅梅,price:88}  
        -{code:100,msg:成功}
      PUT /books/4:返回完整的资源对象
        -{id:4,name:金品梅梅,price:188}  
        -{code:100,msg:修改成功}
      DELETE /books/4: 返回一个空文档      
      	-{code:100,msg:删除成功}
           
   10 返回的结果中带url链接

5.序列化和反序列化

# API接口开发,最核心最常见的一个过程就是序列化,所谓序列化就是把数据转换格式,可以分两个阶段
# 序列化: 把我们识别的数据转换成指定的格式提供给别人。json.dumps
	例如:我们在django中获取到的数据默认是模型对象(queryset),但是模型对象数据无法直接提供给前端或别的平台使用,所以我们需要把数据进行序列化,变成字符串或者json数据,提供给别人。
  queryset = models.Book.objects.all()

# 反序列化:把别人提供的数据转换/还原成我们需要的格式。 json.loads
	例如:前端js提供过来的json数据,对于python而言就是字符串,我们需要进行反序列化换成模型类对象,这样我们才能把数据保存到数据库中
  request.data
    
# 序列化:drf称为 read      
# 反序列化:drf称为 write   

6.drf介绍和快速使用

# book表为例,写5个接口(后面你写的所有接口,都是这5个,及其变形)
	-查询所有图书
  -新增一本图书
  -修改一本图书
  -查询一本图书
  -删除一本图书
from django.urls import path
from app01.views import BookView,BookDetailView
urlpatterns = [
       # 查询所有书籍
       path('books/', BookView.as_view()),
       # 查询某本书籍,使用转换器
       path('books/<int:pk>/', BookDetailView.as_view()),

]
class BookView(View):
    # 查询所有书籍
    def get(self, request):
        book_lst = Book.objects.all()
        books = []
        for book in book_lst:
            books.append({'name': book.name, 'price': book.price})
        return JsonResponse({'status': '200', 'msg': 'success!', 'result': books})

    # 新增某本书籍
    def post(self, request):
        # 默认urlencoded格式和form_data格式
        # name = request.POST.get('name')
        # price = request.POST.get('price')
        # Book.objects.create(name=name, price=price)
        # return JsonResponse({'status': '200', 'msg': 'success!'})
        # json格式
        book_dic = json.loads(request.body)
        Book.objects.create(name=book_dic['name'], price=book_dic['price'])
        return JsonResponse({'status': '200', 'msg': 'success!', 'result': request.data})


class BookDetailView(View):
    # 查询某本书籍
    def get(self, request, pk):
        book_obj = Book.objects.filter(pk=pk).first()
        book = {'name': book_obj.name, 'price': book_obj.price}
        return JsonResponse({'status': '200', 'msg': 'success!', 'result': book})

    # 修改某本书籍
    def put(self, request, pk):
        # json格式
        book_dic = json.loads(request.body)
        book_obj = Book.objects.filter(pk=pk).update(name=book_dic['name'], price=book_dic['price'])
        return JsonResponse({'status': '200', 'msg': 'success!', 'result': book_obj})  # 影响的行数

    # 删除某本书
    def delete(self, request, pk):
        book_obj = Book.objects.filter(pk=pk).delete()
        return JsonResponse({'status': '200', 'msg': 'success!', 'result': book_obj})

6.1 drf介绍

# django 中有个app:djangorestframework。drf帮助我们快速实现符合resful规范的接口

# 下载:(有个坑)
pip3.9 install djangorestframework==3.2.12

# 如果你是django2, 直接这样装,装最新drf,他们不匹配---》pip会自动把django卸载,安装最新django,安装最新drf

# django3 ,这样没有任何问题

pip3.8 install djangorestframework --upgrade 强制更新


# 补充一点点东西:
	- 如果写了一个包或app,想给别人用-->把你写的包,放到pypi上别人pip install 安装--->使用

6.2 drf快速使用(不需要会)

路由

from app01.views import BookView
# 导入一个类
from rest_framework.routers import SimpleRouter
# 类实例化一个对象
router = SimpleRouter()
# 注册
router.register('books', BookView, 'books')
urlpatterns = [
]
# 添加进路由
urlpatterns += router.urls

视图

from .serializer import BookSerializer
from rest_framework.viewsets import ModelViewSet # 包括5个扩展类GenericViewSet
class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

序列化类 serializer.py

from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

7.drf之APIView源码分析

7.1 基于APIView的5个接口

视图类

from rest_framework.views import APIView  # APIView的父类是View
from .serializer import BookSerializer  # drf提供了序列化类
from rest_framework.response import Response
class BookView(APIView):
    # 查询所有书籍
    def get(self, request):
        book_lst = Book.objects.all()
        ser = BookSerializer(instance=book_lst,many=True)  # 序列化
        return Response({'status': '200', 'msg': 'success!', 'result': ser.data})

    # 新增某本书籍
    def post(self, request):
        ser = BookSerializer(data=request.data)  # 反序列化
        if ser.is_valid():  # 数据校验,不合法的数据会禁止
            ser.save()  # 保存到数据库中
        return Response({'status': '200', 'msg': 'success!'})


class BookDetailView(APIView):
    # 查询某本书籍
    def get(self, request, pk):
        book_obj = Book.objects.filter(pk=pk).first()
        ser = BookSerializer(instance=book_obj, many=False)  # 序列化
        return Response({'status': '200', 'msg': 'success!', 'result': ser.data})

    # 修改某本书籍
    def put(self, request, pk):
        book_obj = Book.objects.filter(pk=pk)
        ser = BookSerializer(instance=book_obj,data=request.data) # 反序列化
        if ser.is_valid():  # 数据校验,不合法的数据会禁止
            ser.save()  # 保存到数据库中
        return Response({'status': '200', 'msg': 'success!'})

    # 删除某本书
    def delete(self, request, pk):
        book_obj = Book.objects.filter(pk=pk).delete()
        return Response({'status': '200', 'msg': 'success!', 'result': book_obj})

序列化类

from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

路由

urlpatterns = [
    path('books/', BookView.as_view()),
    path('books/<int:pk>/', BookDetailView.as_view()),
]

7.2 CBV源码分析

# cbv写法:
	1 视图中写视图类,继承View,写跟请求方式同名的方法
    	class BookView(View):
            def get(self,request):
                return 四件套
  2 在路径用写
    	path('books/', BookView.as_view())
        
# 如上写法,为什么能够执行

# 前置条件:前端请求,一旦路径匹配成功,就会执行  BookView.as_view()(request传入,)
# 入口在  BookView.as_view()--->执行结果---》View中有个as_view类的绑定方法
    @classmethod
    def as_view(cls, **initkwargs):
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            res=self.dispatch(request, *args, **kwargs)
            return res
        return view
    
# 执行结果是view的内存地址: 请求来了,执行view(request)
	path('books/', view)
    
# 执行 View类中的as_view方法中的内层的view函数,路由匹配成功,本质是在执行
	self.dispatch(request, *args, **kwargs)
    # self是谁的对象?BookView的对象
    # 去BookView中dispatch,找不到,去父类,View中找到了   
    
# View这个类的dispatch
    def dispatch(self, request, *args, **kwargs):
        # request.method.lower() 如果是get请求,‘get’ 在这个列表里面
        if request.method.lower() in self.http_method_names:
            # handler=getattr(BookView的对象,'get')   
            # handler就是BookView类中的get方法
            handler = getattr(self, request.method.lower())
        else:
            handler = self.http_method_not_allowed
        # 执行 BookView类中的get方法 (request)
        return handler(request, *args, **kwargs)
    
# 最终本质跟写fbv的执行流程一样
# 最终结论:什么请求方式,就会执行视图类中的什么方法

7.3 APIView执行流程分析

# 有了drf,后期都写CBV,都是继承APIView及其子类
# 执行流程:
	-入口:path('books/', BookView.as_view())---》请求来了,执行BookView.as_view()(request)
  -as_view 是谁的? APIView的as_view
        @classmethod
        def as_view(cls, **initkwargs):
            # super()代指的是:父类对象  View类的对象
            # View的as_view(**initkwargs)----》执行结果是view,是View类的as_view方法中的view
            view = super().as_view(**initkwargs)
            view=csrf_exempt(view)  # 局部禁用csrf,
            return view
        
  -path('books/', View类的as_view中的view,只是去掉了csrf的认证)
	# -请求来了,执行 【View类的as_view中的view,只是去掉了csrf的认证(request)】
  -执行:self.dispatch(request, *args, **kwargs),  self要从根上找
	-self.dispatch 是APIView的dispatch,源码如下
        def dispatch(self, request, *args, **kwargs):
            # request新的(rest_framework.request.Request), request老的(django.core.handlers.wsgi.WSGIRequest)
            request = self.initialize_request(request, *args, **kwargs)
            self.request = request
            try:
                # 执行了认证,权限和频率
                self.initial(request, *args, **kwargs)
                # 在执行视图类方法之前,去掉了csrf认证,包装了新的request,执行了认证频率和权限
                # 执行请求方式字符串对应的方法
                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
       
# 总结:
	1  以后只要继承APIView的所有视图类的方法,都没有csrf的校验了
  2  以后只要继承APIView的所有视图类的方法中的request是新的request了
  3  在执行视图类的方法之前,执行了三大认证(认证,权限,频率)
  4  期间出了各种错误,都会被异常捕获,统一处理
               
# 补充(装饰器语法糖):
	fbv,局部禁用csrf,如何写?
    @csrf_exempt
    def index(request):
        pass  
  原理是(装饰器本质):index=csrf_exempt(index)
# 作业:写一个装饰器,装饰在fbv上,这个fbv可以接受前端的编码格式可以是urlencoded,form-data,json,取数据,都是从request.data中取

# 装饰器
def load_json(func):
    def inner(request,*args,**kwargs):
        try:
            request.data = json.loads(request.body)  # 如果是json格式就成功,如果是其它格式直接报错
        except Exception as e:
            request.data = request.POST
        res = func(request,*args,**kwargs)
        return res
    return inner
@load_json
def testbook(request):
    # 默认urlencoded格式和form_data格式
    # name = request.POST.get('name')
    # price = request.POST.get('price')
    # Book.objects.create(name=name, price=price)
    # return JsonResponse({'status': '200', 'msg': 'success!'})
    # json格式
    # book_dic = json.loads(request.body)
    # Book.objects.create(name=book_dic['name'], price=book_dic['price'])
    print(request.data)
    # Book.objects.create(name=request.data['name'],price=request.data['price'])
    # Book.objects.create(**request.data) 
    return JsonResponse({'status': '200', 'msg': 'success!', 'result': request.data})

8 Request类的源码分析

# APIView+Response写个接口
  
def __getattr__(self, attr):
    """
    If an attribute does not exist on this instance, then we also attempt
    to proxy it to the underlying HttpRequest object.
    """
    try:
        return getattr(self._request, attr)
      # attr是一个字符串,如果你拿method,那么attr='method'
    except AttributeError:
        return self.__getattribute__(attr)
      
    @property
    def query_params(self):
        """
        More semantically correct name for request.GET.
        """
        return self._request.GET

# 总结:
	1 新的request有个data方法包装成属性,以后只要是在请求body体中的数据,无论什么编码格式,无论什么请求方式
  2 取文件还是从:request.FILES
  3 取其他属性,跟之前完全一样 request.method  ....
  -from rest_framework.request import Request
  		-原理是:新的Request重写了__getattr__,通过反射获取老的request中的属性
  4 request.GET 现在可以使用 request.query_params
    @property # 将方法包装成属性
    def query_params(self):
    return self._request.GET
    
    
    
# 源码中找出来 
	-老的request在新的request._request
  -照常理来讲,如果取method,应该request._request.method,但是我现在可以request.method
    
# 魔法方法之 __getattr__, . 拦截(点拦截),对象.属性 当属性不存在时,会触发类中 __getattr__的执行
# get请求能不能在body体中带数据
	-之前不允许带,前段时间研究出的结果是能。

9.序列化组件介绍

1. 序列化,序列化器会把模型对象(queryset,单个对象)转换成字典,经过Response以后变成json字符串
2. 反序列化,把客户端发送过来的数据,经过request.data以后变成字典,序列化器得到ser,ser.save()保存到数据库,可以把字典转成模型
3. 反序列化,完成数据校验功能,ser.is_valid() ---->三层:字段自己的,局部钩子,全局钩子

10 序列化类的基本使用

# 1 创建book表模型
# 2 写查询所有图书的接口:APIView+序列化类+Response

# 面试题:BigIntegerField跟IntegerField有什么区别?范围不一样

10.1 查询所有和查询单条

views.py

class BookView(APIView):
    def get(self, request):
        book_list = Book.objects.all()
        # 使用序列化类,完成序列化  两个很重要参数: instance实例,对象   data:数据
        # 如果是多条many=True  如果是queryset对象,就要写
        # 如果是单个对象 many=False,默认是False,可以不写
        serializer = BookSerializer(instance=book_list, many=True)
        # serializer.data  # 把qs对象,转成列表套字典  ReturnList
        # print(serializer.data)
        # print(type(serializer.data)) <class 'rest_framework.utils.serializer_helpers.ReturnList'>
        # return Response(serializer.data)
        return Response({'code': 100, 'msg': '成功', 'data': serializer.data})


class BookDetailView(APIView):
    def get(self, request, pk):
        book = Book.objects.all().get(pk=pk)
        serializer = BookSerializer(instance=book)
        return Response({'code': 100, 'msg': '成功', 'data': serializer.data})

urls.py

urlpatterns = [
    path('books/', views.BookView.as_view()),
    path('books/<int:pk>/', views.BookDetailView.as_view()),
]

序列化类

from rest_framework import serializers

class BookSerializer(serializers.Serializer):
    # 要序列化的字段
    # id = serializers.IntegerField()
    name = serializers.CharField()
    # price = serializers.IntegerField()

总结

# 序列化类的使用
1 写一个类,继承serializers.Serializer
2 在类中写字段,要序列化的字段
3 在视图类中使用:(多条,单条)
    serializer = BookSerializer(instance=book_list, many=True)
    serializer = BookSerializer(instance=book)

10.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)
ListField ListField(child=, min_length=None, max_length=None)
DictField DictField(child=)
# 1.常用字段类 
  IntegerField  CharField   DateTimeField  DecimalField
  ListField和DictField---》比较重要,但是后面以案例形式讲

  • 字段参数(校验数据来用的)

选项参数:(CharField,IntegerField)

参数名称 作用
max_length 最大长度 CharField
min_lenght 最小长度 CharField
allow_blank 是否允许为空 CharField
trim_whitespace 是否截断空白字符
max_value 最小值 IntegerField
min_value 最大值 IntegerField

通用参数:

参数名称 说明
read_only 表明该字段仅用于序列化输出,默认False
write_only 表明该字段仅用于反序列化输入,默认False
required 表明该字段在反序列化时必须输入,默认True
default 反序列化时使用的默认值
allow_null 表明该字段是否允许传入None,默认False
# 2.字段参数(校验数据来用) 
read_only   write_only   很重要,后面以案例讲

11 反序列化之校验

# 反序列化,有三层校验
    -1 字段自己的(写的字段参数:required   max_length 。。。)
    -2 局部钩子:写在序列化类中的方法,方法名必须是 validate_字段名
    	def validate_name(self, name):
            if 'sb' in name:
                # 不合法,抛异常
                raise ValidationError('书名中不能包含sb')
            else:
                return name
    -3 全局钩子:写在序列化类中的方法 方法名必须是 validate
       def validate(self, attrs):
            price = attrs.get('price')
            name = attrs.get('name')
            if name == price:
                raise ValidationError('价格不能等于书名')
            else:
                return attrs
            
  # 只有三层都通过,在视图类中:
	ser.is_valid():  才是True,才能保存

12 反序列化之保存

# 新增接口:
	-序列化类的对象,实例化的时候:ser = BookSerializer(data=request.data)
	-数据校验过后----》调用 序列化类.save()--->但是要在序列化类中重写  create方法
        def create(self, validated_data):
            book=Book.objects.create(**validated_data)
            return book
        
        
# 修改接口
	-序列化类的对象,实例化的时候:ser = BookSerializer(instance=book,data=request.data)
	-数据校验过后----》调用  序列化类.save()--->但是要在序列化类中重写  update方法
        def update(self, book, validated_data):
            for item in validated_data:  # {"name":"jinping","price":55}
                setattr(book, item, validated_data[item])
            book.save()
            return book
        
        
# 研究了一个问题
	在视图类中,无论是保存还是修改,都是调用序列化类.save(),底层实现是根据是否有instance做一个判断
  if self.instance is not None:
    	self.instance = self.update(self.instance, validated_data)
    	assert self.instance is not None, ('`update()` did not return an object instance.')
  else:
      self.instance = self.create(validated_data)
      assert self.instance is not None, ('`create()` did not return an object instance.')

return self.instance

13 5个接口代码

路由

urlpatterns = [
    path('books/', views.BookView.as_view()),
    path('books/<int:pk>/', views.BookDetailView.as_view()),
]

视图

from .models import Book
from .serializer import BookSerializer


class BookView(APIView):
    def get(self, request):
        book_list = Book.objects.all()
        # 使用序列化类,完成序列化  两个很重要参数: instance实例,对象      data:数据
        # 如果是多条many=True  如果是queryset对象,就要写
        # 如果是单个对象 many=False,默认是False
        serializer = BookSerializer(instance=book_list, many=True)
        # serializer.data  # 把qs对象,转成列表套字典  ReturnList
        # print(serializer.data)
        # print(type(serializer.data))
        # return Response(serializer.data)
        return Response({'code': 100, 'msg': '成功', 'data': serializer.data})

    # 新增
    def post(self, request):
        # 前端会传入数据,request.data--->把这个数据保存到数据库中
        # 借助于序列化类,完成 校验和反序列化
        # data 前端传入的数据 {"name":"三国演义","price":88}
        ser = BookSerializer(data=request.data)
        # 校验数据
        if ser.is_valid():  # 三层:字段自己的校验,局部钩子校验,全局钩子校验
            # 校验通过,保存
            print(ser.validated_data)  # validated_data:校验过后的数据
            # 如果没有save,如何保存,自己做
            # Book.objects.create(**ser.validated_data)
            ser.save()  # 会保存,但是会报错,因为它不知道你要保存到那个表中
            return Response({'code': 100, 'msg': '新增成功'})
        else:
            print(ser.errors)  # 校验失败的错误
            return Response({'code': 101, 'msg': '新增失败', 'errors': ser.errors})


class BookDetailView(APIView):
    def get(self, request, pk):
        book = Book.objects.all().get(pk=pk)
        serializer = BookSerializer(instance=book)
        return Response({'code': 100, 'msg': '成功', 'data': serializer.data})

    def put(self, request, pk):
        book = Book.objects.get(pk=pk)
        ser = BookSerializer(instance=book, data=request.data)
        if ser.is_valid():
            ser.save() # 也会报错,重写update
            return Response({'code': 100, 'msg': '修改成功'})
        else:
            return Response({'code': 101, 'msg': '修改失败', 'errors': ser.errors})
          
    def delete(self,request,pk):
        press_lst = Presses.objects.filter(pk=pk).delete()
        return Response({'code': 100, 'msg': '删除成功'})

序列化类

from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from .models import Book


class BookSerializer(serializers.Serializer):
    # 要序列化的字段
    id = serializers.IntegerField(required=False)  # 前端传入数据,可以不填这个字段
    name = serializers.CharField(allow_blank=True, required=False, max_length=8,
                                 min_length=3, error_messages={'max_length': '太长了'})  # allow_blank: 这个字段传了,value值可以为空
    price = serializers.IntegerField(max_value=100, min_value=10, error_messages={'max_value': '必须小于100'})

    # price = serializers.CharField()

    # 局部钩子:给某个字段做个校验
    # 书名中不能包含sb
    # validate_字段名
    def validate_name(self, name):
        if 'sb' in name:
            # 不合法,抛异常
            raise ValidationError('书名中不能包含sb')
        else:
            return name

    def validate_price(self, item):
        if item == 88:
            raise ValidationError('价格不能等于88')
        else:
            return item

    # 全局钩子
    # 价格和书名不能一样  validate
    def validate(self, attrs):
        price = attrs.get('price')
        name = attrs.get('name')
        if name == price:
            raise ValidationError('价格不能等于书名')
        else:
            return attrs

    def create(self, validated_data):
        # validated_data校验过后的数据,字典
        book = Book.objects.create(**validated_data)
        return book

    # def update(self, book, validated_data):
    #     # instance 要修改的对象
    #     # validated_data:前端传入,并且校验过后的数据
    #     book.name = validated_data.get('name')
    #     book.price = validated_data.get('price')
    #     # 一定不要忘了
    #     book.save()
    #     return book
    def update(self, book, validated_data):

        for item in validated_data:  # {"name":"jinping","price":55}
            setattr(book, item, validated_data[item])
            # 等同于下面
            # setattr(book,'name','jinping')
            # setattr(book,'price',55)
            # 等同于
            #     book.name = validated_data.get('name')
            #     book.price = validated_data.get('price')
        book.save()
        return book

表模型

from django.db import models


# Create your models here.

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.BigIntegerField()
    # 面试题:BigIntegerField跟IntegerField有什么区别

14 小补充:数据库三大范式

# 第一范式(1NF):确保每个数据列都是原子性的,也就是确保每个数据列都是不可分割的基本数据项,不含重复的元组。
		-数据表中的每个字段的值必须具有原子性,是不可再拆分的最小数据单元
  	-属性的原子性是主观的,我们要根据实际项目的需求来设计,比如说地址,如果项目没有说要细分为省,市,县,镇这么具体的话,我们一般就可以不拆分。
# 第二范式(2NF):在满足第一范式的前提下,确保非主键字段都完全依赖于主键,也就是每个非主键字段都要与主键字段有关联。

# 	第三范式(3NF):在满足第二范式的前提下,确保非主键字段之间不存在传递依赖关系,即不存在非主键字段决定其他非主键字段的情况。

'''
数据的标准化有助于消除数据库中的数据冗余,但是降低了查询效率,而且可能会使得一些索引失效
'''

15 序列化高级用法之source(了解)

# 1 创建了5个表(图书管理的5个)
   on_delete:
   CASCADE:级联删除,只要删除publish,跟publish关联的book,全都被删除
   SET_DEFAULT:只要删除publish,跟publish关联的book,的publish字段会变成默认值,一定要配合default使用
   SET_NULL:只要删除publish,跟publish关联的book,的publish字段会变成空,一定要配合null=True使用
   models.SET(add):括号中可以放个值,也可以放个函数内存地址,只要删除publish,跟publish关联的book,的publish字段会变成set设的值或执行函数
   models.DO_NOTHING:什么都不做,但它需要跟db_constraint=False配合,表示不建立外键约束,创建逻辑外键,不是物理外键
   # 不建立物理外键的好处?增删查改数据快
   # 缺点:容易出现脏数据
   # 实际工作中,都不建立物理外键,都是建立逻辑外键
    
# 2 对booke进行序列化


# 总结:source的用法
	-1 修改前端看到的字段key值---》source指定的必须是对象的属性
    	book_name = serializers.CharField(source='name')
    # 有个坑,name = serializers.CharField(source='name')会报异常,source='name'字段与name字段相同,需要移除
    # 同一个字段可以序列化多次,但一般不会写
  -2 修改前端看到的value值,---》source指定的必须是对象的方法
    	表模型中写方法
          def sb_name(self):
        	return self.name + '_sb'
        序列化类中
        	book_name = serializers.CharField(source='sb_name')
  -3 可以关联查询(得有关联关系)
    	publish_name = serializers.CharField(source='publish.name')
    # publish是book表里的的外键字段,对应Publish表

16 序列化高级用法之定制字段的两种方式(非常重要)

# 方式一:在序列化类中写
	1 写一个字段,对应的字段类是:SerializerMethodField
  2 必须对应一个get_字段名的方法,方法必须接受一个obj序列化类对象,返回什么,这个字段对应的value就是什么
    
    
# 方式二:在表模型中写
	1 在表模型中写一个方法(可以使用:property修饰就相当于类属性,),方法有返回值(字典,字符串,列表)
  2 在序列化类中,使用DictField,CharField,ListField
    

16.1 序列化类中写

### 16.1 定制字段方式1
class BookSerialzier(serializers.Serializer):
    name = serializers.CharField()
    price = serializers.CharField()
    # 拿出出版社的id和名字和addr,放到一个字典中
    # 方式一:SerializerMethodField来定制,如果写了这个,必须配合一个方法get_字段名,这个方法返回什么,这个字段的值就是什么
    publish_detail = serializers.SerializerMethodField()

    def get_publish_detail(self, book):
        # print(obj) # 要序列化的book对象
        return {'id': book.publish.pk, 'name': book.publish.name, 'addr': book.publish.addr}

    # 练习:拿出所有作者的信息--》多条 [{name:,phone:},{}]
    author_list = serializers.SerializerMethodField()

    def get_author_list(self, book):
        l = []
        for author in book.authors.all():
            l.append({'id': author.pk, 'name': author.name, 'phone': author.phone, 'age': author.author_detail.age})
        return l

16.2 表模型中写

### 16.2 定制字段方式2
class BookSerialzier(serializers.Serializer):
    name = serializers.CharField()
    price = serializers.CharField()
    # 1 序列化类中这样写
    # 2 到表模型中写一个方法,方法名必须叫 publish_detail,这个方法返回什么,这个字段的value就是什么
    publish_detail = serializers.DictField()
    author_list = serializers.ListField()
    
    
###### ###### ###### ###### ###### 表模型###### ###### ###### ###### 
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.CharField(max_length=32)
    publish = models.ForeignKey(to='Publish', on_delete=models.SET_NULL, null=True)
    authors = models.ManyToManyField(to='Author')

    def sb_name(self):
        return self.name + '_sb'

    @property
    def publish_detail(self):
        return {'id': self.publish.pk, 'name': self.publish.name, 'addr': self.publish.addr}

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


17 多表关联反序列化保存

# 序列化和反序列化,用的同一个序列化类
	-序列化的字段:name,price ,   publish_detail,author_list
  -反序列化字段:name,price ,   publish,author
   

17.1 反序列化之保存

视图类

class BookView(APIView):
  # 新增一条
    def post(self, request):
        ser = BookSerialzier(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response({'code': 100, 'msg': '成功'})
        else:
            return Response({'code': 100, 'msg': ser.errors})

序列化类

class BookSerialzier(serializers.Serializer):
    # 即用来做序列化,又用来做反序列化
    name = serializers.CharField(max_length=8)
    price = serializers.CharField()

    # 这俩,只用来做序列化
    publish_detail = serializers.DictField(read_only=True)
    author_list = serializers.ListField(read_only=True)

    # 这俩,只用来做反序列化
    publish_id = serializers.IntegerField(write_only=True)
    authors = serializers.ListField(write_only=True)

    def create(self, validated_data):  # {name:西游记,price:88,publish:1,authors:[1,2]
        authors = validated_data.pop('authors')
        book = Book.objects.create(**validated_data)
        book.authors.add(*authors)
        return book

17.2 反序列化之修改

视图类

class BookDetailView(APIView):
    def put(self, request,pk):
      # 修改一条
        book=Book.objects.get(pk=pk)
        ser = BookSerialzier(data=request.data,instance=book)
        if ser.is_valid():
            ser.save()
            return Response({'code': 100, 'msg': '更新成功'})
        else:
            return Response({'code': 100, 'msg': ser.errors})

序列化类

class BookSerialzier(serializers.Serializer):
    # 即用来做序列化,又用来做反序列化
    name = serializers.CharField(max_length=8)
    price = serializers.CharField()

    # 这俩,只用来做序列化
    publish_detail = serializers.DictField(read_only=True)
    author_list = serializers.ListField(read_only=True)

    # 这俩,只用来做反序列化
    publish_id = serializers.IntegerField(write_only=True)
    authors = serializers.ListField(write_only=True)


    def update(self, instance, validated_data):
        authors = validated_data.pop('authors')
        for item in validated_data:
            setattr(instance, item, validated_data[item])
        instance.authors.set(authors)
        instance.save()
        return instance

18 反序列化字段校验其他

# 视图类中调用:ser.is_valid()---》触发数据的校验
	-4层
    	-字段自己的:max_length,required。。。
      -字段自己的:配合一个函数name = serializers.CharField(max_length=8,validators=[xxx,vvv])
      -局部钩子
      -全局钩子

19 ModelSerializer使用

# 之前写的序列化类,继承了Serializer,写字段,跟表模型没有必然联系
class XXSerialzier(Serializer)
	id=serializer.CharField()
	name=serializer.CharField()
    
 XXSerialzier既能序列化Book,又能序列化Publish


# 现在学的ModelSerializer,表示跟表模型一一对应,用法跟之前基本类似

	1 写序列化类,继承ModelSerializer
  2 在序列化类中,再写一个类,必须叫
    	class Meta:
					model=表模型
          fields=[] # 要序列化的字段
  3 可以重写字段,一定不要放在class Meta
    	-定制字段,跟之前讲的一样
  4 自定制的字段,一定要在fields中注册一下
  5 class Meta: 有个extra_kwargs,为某个字段定制字段参数
  6 局部钩子,全局钩子,完全一致
  7 大部分请情况下,不需要重写 create和update了
  
  
  
  
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['id', 'name', 'price', 'authors', 'publish','publish_detail','author_list']
        extra_kwargs = {
            'publish': {'write_only': True},
            'authors': {'write_only': True},
        }
		# 序列化只读,首要把参数写在这里
    publish_detail = serializers.DictField(read_only=True)
    author_list = serializers.ListField(read_only=True)
    # 反序列化只写,在上面写了就不用在下面写
    # publish = serializers.CharField()
    # authors = serializers.ListField()
# 作业:author的5个接口,-新增或修改author时,把author_detail的内容也新增进去

# 序列化类
class AuthorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Author
        fields = ['id', 'name', 'phone', 'email', 'age']
    email = serializers.CharField(source='author_detail.email')
    age = serializers.IntegerField(max_value=200, source='author_detail.age')

    def create(self, validated_data):
        author_detail = validated_data.pop('author_detail')
        author_detail_obj = AuthorDetail.objects.create(**author_detail)
        validated_data['author_detail_id'] = author_detail_obj.id
        author = Author.objects.create(**validated_data)
        author.save()
        return author

    def update(self, instance, validated_data):
        author_detail = validated_data.pop('author_detail')
     AuthorDetail.objects.filter(pk=instance.author_detail.pk).update(**author_detail)
        Author.objects.filter(pk=instance.pk).update(**validated_data)
        return instance

      
# 视图类
class AuthorView(APIView):
    def get(self, request):
        author_obj = Author.objects.all()
        ser = AuthorSerializer(author_obj, many=True)
        return Response({'code': 200, 'msg': 'success!', 'data': ser.data})

    def post(self, request):
        ser = AuthorSerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response({'code': 200, 'msg': 'success!', 'data': ser.data})
        else:
            return Response({'code': 201, 'msg': 'failure!', 'errors': ser.errors})


class AuthorDDetail(APIView):
    def get(self, request, index):
        author_obj = Author.objects.filter(pk=index).first()
        ser = AuthorSerializer(author_obj)
        return Response({'code': 200, 'msg': 'success!', 'data': ser.data})

    def put(self, request, index):
        author_obj = Author.objects.filter(pk=index).first()
        ser = AuthorSerializer(author_obj, request.data)
        if ser.is_valid():
            ser.save()
            return Response({'code': 200, 'msg': 'success!'})
        else:
            return Response({'code': 201, 'msg': 'failure!', 'errors': ser.errors})

    def delete(self, request, index):
        author_obj = Author.objects.filter(pk=index).first()
        AuthorDetail.objects.filter(pk=author_obj.author_detail.id).delete()
        return Response('okokok')

      
# 路由
urlpatterns = [
    path('authors/', AuthorView.as_view()),
    path('authors/<int:index>/', AuthorDDetail.as_view()),
]


# 反序列化,小知识点点
1.source的反序列化不行吗?不行
2.前端的key可以任意写,因为重写create和update的时候可手动上传

小补充:模块与包的使用

# 模块与包
	-模块:一个py文件,被别的py文件导入使用,这个py文件称之为模块,运行的这个py文件称之为脚本文件
  -包:一个文件夹下有__init__.py 
    
    
# 模块与包的导入问题   

'''
0 导入模块有相对导入和绝对导入,绝对的路径是从环境变量开始的
1 导入任何模块,如果使用绝对导入,都是从环境变量开始导入起
2 脚本文件执行的路径,会自动加入环境变量
3 相对导入的话,是从当前py文件开始计算的,建议在同一个文件夹下用相对导入
4 以脚本运行的文件,不能使用相对导入,只能用绝对导入
'''

20 反序列化校验源码分析(了解)

# 序列化类的校验功能
	-局部钩子:必须validate_字段名
  -全局钩子:validate
    
    
# 入口:
	-ser.is_valid 才做的校验---》入口
  -BookSerializer---》Serializer——-》BaseSerializer---》is_valid---》继承了Field
  -is_valid 方法
        def is_valid(self, *, raise_exception=False):
            # self中没有_validated_data,只有执行完后,才有
            if not hasattr(self, '_validated_data'):
                try:
                    # 核心---》这一句
                    # 想看它的源代码,按住ctrl+鼠标点击是不对的---》只能找当前类的父类
                    #但它真正的执行是,从根上开始找
                    self._validated_data = self.run_validation(self.initial_data)
                except ValidationError as exc:
                    self._validated_data = {}
                    self._errors = exc.detail
                else:
                    self._errors = {}

            if self._errors and raise_exception:
                raise ValidationError(self.errors)

            return not bool(self._errors)
  -self.run_validation(self.initial_data),不能按住ctrl+鼠标点点击,要从根上开始找
  -Serializer的run_validation
        def run_validation(self, data=empty):
             # 局部钩子
            value = self.to_internal_value(data)
            try:
                # 全局钩子
                value = self.validate(value) # BookSerializer只要写了,优先执行它的
            except (ValidationError, DjangoValidationError) as exc:
                raise ValidationError(detail=as_serializer_error(exc))

            return value
    
    -self.to_internal_value(data)---》Serializer类的方法
        def to_internal_value(self, data):
            for field in fields: #序列化类中写的一个个的字段类的对象列表
                # 一个field是name对象,field.field_name字符串 name
                # self是谁的对象:序列化类的对象,BookSerializer的对象  validate_name
                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
                except DjangoValidationError as exc:
                    errors[field.field_name] = get_error_detail(exc)
                except SkipField:
                    pass
                else:
                    set_value(ret, field.source_attrs, validated_value)

            if errors:
                raise ValidationError(errors)

            return ret
    
    
# 总结:
	-ser.is_valid---》走局部钩子的代码---》是通过反射获取BookSerializer中写的局部钩子函数,如果写了,就会执行----》走全局钩子代码---》self.validate(value)--->只要序列化类中写了,优先走自己的
    
# 补充:
  -写在类中得方法,只要写了,就会执行,不写就不执行
  -面向切面编程 AOP             OOP:面向对象编程
  -在程序的前期,中期,后期插入一些代码来执行
  -装饰器:实现aop的一种方式
  -钩子函数:实现aop的一种方式

21 断言

# 断言
assert hasattr(self, 'initial_data'), (
            'Cannot call `.is_valid()` as no `data=` keyword argument was '
            'passed when instantiating the serializer instance.'
        )


# 断定某个东西是我认为的,如果不是就抛异常
# 等同于if判断+抛异常
def add(a, b):
    return a + b

res = add(8, 9)
# assert res == 16, Exception('不等于16')
if not res==16:
    raise Exception('不等于16')
print('随便')

22 drf之请求

# 视图类:APIView
# 序列化组件:Serializer,ModelSerializer
# drf:Request类的对象
# drf:Response

22.1 Request类对象的分析

1).data
    request.data 返回解析之后的请求体数据。类似于Django中标准的request.POST和 request.FILES属性,但提供如下特性:
    包含了解析之后的文件和非文件数据
    包含了对POST、PUT、PATCH请求方式解析后的数据
    利用了REST framework的parsers解析器,不仅支持表单类型数据,也支持JSON数据
2).query_params
	request.query_params与Django标准的request.GET相同,只是更换了更正确的名称而已
3) 其他的属性用起来跟之前一样

22.2 请求能够接受的编码格式

# urlencoded
# form-data
# json
三种都支持
限制只能接受某种或某几种编码格式
from rest_framework.settings import DEFAULT
from rest_framework.parsers import JSONParser
# 限制方式一:在视图类上写---》只是局部视图类有效
	# 总共有三个:JSONParser, FormParser, MultiPartParser
	class BookView(APIView):
    	parser_classes = [JSONParser, FormParser]
        
# 限制方式二:在配置文件中写---》全局有效
    # drf的配置,统一写成它
    REST_FRAMEWORK = {
        'DEFAULT_PARSER_CLASSES': [
            'rest_framework.parsers.JSONParser',
            # 'rest_framework.parsers.FormParser',
            # 'rest_framework.parsers.MultiPartParser',
        ],
    }
    
    
# 全局配置了只支持json,局部想支持3个
	-只需要在局部,视图类中,写3个即可
    class BookView(APIView):
    	parser_classes = [JSONParser, FormParser,MultiPartParser]
	
    
 # 总结:能够处理的请求方式编码
	-优先从视图类中找
  -再去项目配置文件找
  -再去drf默认的配置中找

23 drf之响应

23.1 响应类的对象Response

# return Response({code:100})
-data:响应体的内容,可以字符串,字典,列表
-status:http响应状态码  
	-drf把所有响应码都定义成了一个常量
	
template_name:模板名字,用浏览器访问,看到好看的页面,用postman访问,返回正常数据
	-自定制页面
  -根本不用
headers:响应头加数据(后面讲跨域问题再讲)
	-headers={'name':'lqz'}
content_type:响应编码,一般不用


# 三个重要的:data,status,headers

23.2 响应的格式

# 默认是两种:纯json,浏览器看到的样子
# 注册restframework就可以用网页打开json格式数据
# 限制方式一:在视图类上写---》只是局部视图类有效
# 总共有两个:JSONRenderer,BrowsableAPIRenderer
	from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
    class BookView(APIView):
        renderer_classes = [JSONRenderer]
        
# 限制方式二:在配置文件中写---》全局有效
    # drf的配置,统一写成它
     REST_FRAMEWORK = {
        'DEFAULT_RENDERER_CLASSES': [
            'rest_framework.renderers.JSONRenderer',
            # 'rest_framework.renderers.BrowsableAPIRenderer',
        ],
    }
    
    
# 全局配置了只支持json,局部想支持2个
	-只需要在局部,视图类中,写2个即可
	from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
    class BookView(APIView):
        renderer_classes = [JSONRenderer,BrowsableAPIRenderer]
        
        

24 视图之两个视图基类

# 视图类:
		-APIView:drf最顶层的视图类,继承了原生djagno的View
    	-1 去除了csrf
      -2 包装了新的request
      -3 在执行视图类的方法之前,执行了三大认证
      -4 处理了全局异常
	-GenericAPIView:GenericAPIView继承了APIView
    
    
    
# GenericAPIView
	-类属性:
    	queryset:要序列化的所有数据
    	serializer_class:序列化类
      lookup_field = 'pk' :查询单条时的key值,为实际存在的字段名称
      filter_backends:过滤类
    	pagination_class:分页类
  -方法:绑定给对象的方法(写成方法的目的是解耦合,提高扩展性)
    	-get_queryset():获取所有要序列化的数据【后期可以重写】
      -get_serializer  : 返回序列化类 ser=get_serializer(instance=qs,many=True)
      -get_object :获取单个对象
      -get_serializer_class:仅仅是返回序列化类,不能传任何参数
        serializer_class=self.get_serializer_class()
        serializer_class(instance=qs,many=True)
        
        
        
 #总结:以后继承GenericAPIView写接口
	1 必须配置类属性
    	queryset
      serializer_class
  2 想获取要序列化的所有数据
    	get_queryset()
  3 想使用序列化类:
    	get_serializer
  4 想拿单条
    	get_object
   -以后,如果涉及到数据库操作,尽量用GenericAPIView  	

24.1 使用APIView+序列化类+Response写接口

from rest_framework.views import APIView

from .serializer import BookSerialzier
from rest_framework.response import Response
from .models import Book

# class BookView(APIView):
#     def get(self, request):
#         qs = Book.objects.all()
#         ser = BookSerialzier(qs, many=True)
#         return Response({'code': 100, 'msg': '成功', 'results': ser.data})
#
#     def post(self, request):
#         ser = BookSerialzier(data=request.data)
#         if ser.is_valid():
#             ser.save()
#             return Response({'code': 100, 'msg': '成功'})
#         else:
#             return Response({'code': 100, 'msg': ser.errors})
#
#
# class BookDetailView(APIView):
#     def get(self, request, pk):
#         book = Book.objects.all().get(pk=pk)
#         ser = BookSerialzier(book)
#         return Response({'code': 100, 'msg': '成功', 'results': ser.data})
#
#     def put(self, request, pk):
#         book = Book.objects.get(pk=pk)
#         ser = BookSerialzier(data=request.data, instance=book)
#         if ser.is_valid():
#             ser.save()
#             return Response({'code': 100, 'msg': '更新成功'})
#         else:
#             return Response({'code': 100, 'msg': ser.errors})

补充:序列化类源码分析之——many参数的作用

   # 补充:
	魔法方法:类中使用__开头  __结尾的方法,它不需要主动调用,某种情况下会自动触发
    object类身上的  	
  
  -如果传了many=True,序列化多条----》得到的对象不是BookSerializer对象,而是ListSerialzier的对象中套了一个个的BookSerializer对象
        -如果传了many=False,序列化单条---》得到的对象就是BookSerializer的对象
        
        -类实例化得到对象,是谁控制的?
        def __new__(cls, *args, **kwargs):
            if kwargs.pop('many', False):
                return cls.many_init(*args, **kwargs)
            return super().__new__(cls, *args, **kwargs)

24.2 使用GenericAPIView+序列化类+Response写接口

from rest_framework.generics import GenericAPIView
class BookView(GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerialzier

    def get(self, request):
        qs = self.get_queryset()
        ser = self.get_serializer(qs, many=True)
        return Response({'code': 100, 'msg': '成功', 'results': ser.data})

    def post(self, request):
        ser = self.get_serializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response({'code': 100, 'msg': '成功'})
        else:
            return Response({'code': 100, 'msg': ser.errors})


class BookDetailView(GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerialzier

    def get(self, request, pk):
        book = self.get_object()
        ser = self.get_serializer(book)
        return Response({'code': 100, 'msg': '成功', 'results': ser.data})

    def put(self, request, pk):
        book = self.get_object()
        ser = self.get_serializer(data=request.data, instance=book)
        if ser.is_valid():
            ser.save()
            return Response({'code': 100, 'msg': '更新成功'})
        else:
            return Response({'code': 100, 'msg': ser.errors})
# 作业
# 1 原生django想往响应头加数据,如何加?
def test(request):
    res = HttpResponse('ok')
    res['Access-Control-Allow-Methods'] = 'get,post,hahahahha'
    return res
# HttpResponse、render,redirect本质上都是HttpResponse响应对象

# 2 编写一个post接口,只能接受json编码格式,响应json格式,响应头中加入
	Access-Control-Allow-Origin = "*"
  
class Test(APIView):
    parser_classes = [JSONParser, ]
    renderer_classes = [JSONRenderer, ]

    def post(self, request):
        print(request.data)
        res = Response('ok')
        res['Access-Control-Allow-Origin'] = '*'
        return res

25 5个视图扩展类

from .models import Book
from .serializer import BookSerialzier
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView

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


class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerialzier

    def get(self, request):
        return self.list(request)

    def post(self, request):
        return self.create(request)



class BookDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerialzier

    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)
      
      
# 5 个视图扩展类--->不是视图类---》必须配合GenericAPIView及其子类使用---》不能配合APIView使用
# 5个视图扩展类,每一个类中只有一个方法,完成5个接口中的其中一个,想写多个接口,就要继承多个

26 9个视图子类

# ModelViewSet:
	-继承它后,只需要在视图类中写两行
    	queryset = Book.objects.all()
    	serializer_class = BookSerialzier
    -配置路由,5个接口都有了
    	path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
    	path('books/<int:pk>/', BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
# 一般要重写某个方法
        
        
        
# ModelViewSet 源码分析
	-继承了:      
    		mixins.CreateModelMixin,
             mixins.RetrieveModelMixin,
             mixins.UpdateModelMixin,
             mixins.DestroyModelMixin,
             mixins.ListModelMixin
             GenericViewSet
                -ViewSetMixin :没有见过,重写了 as_view
                -GenericAPIView
                
                
     -只要继承了ModelViewSet,路由写法变了,谁控制它变的:ViewSetMixin
   

# ViewSetMixin 如何控制路由写法变了?
	-BookView.as_view 是在执行,其实是ViewSetMixin的as_view
        @classonlymethod
        def as_view(cls, actions=None, **initkwargs):
            #我们传入的 actions={'get': 'list', 'post': 'create'}
            def view(request, *args, **kwargs):
                self = cls(**initkwargs)
                for method, action in actions.items():
                    # method:get
                    # action:list
                    # 通过反射,self是BookView的对象,取BookView对象中反射list
                    handler = getattr(self, action)
                    # 通过反射:setattr(BookView的对象,'get',list方法)
                    setattr(self, method, handler)
                # APIViwe的dispatch
                return self.dispatch(request, *args, **kwargs)
            return csrf_exempt(view)
	
    
    
# 总结:
	'''
	1 只要继承了ViewSetMixin及其子类,路由写法就变了,必须传actions参数
	2 变成映射关系了:
		path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
    3 以后,只要是books路由匹配成功,的get请求,就会执行视图类BookView的list方法
    
    4 以后视图类中的方法名,可以随意命名
    5 这个类,必须配合视图类使用(APIView,GenericAPIView,9个视图子类),必须放在视图类之前
	'''

27 视图集

python源文件右键--diagrams--show-- python class就会出现继承逻辑图

27.1 总结

# 两个视图基类(-以后,如果涉及到数据库操作,尽量用GenericAPIView)
-APIView(继承Django的View类)
-GenericAPIview(继承APIView)
		-类属性:
        	-queryset:要序列化的数据
          -serializer_class:指定序列化类
          -lookup_field:获取单条数据的查询条件
          -filter_backends:过滤类
    	    -pagination_class:分页类
		  -如何使用:
        	self:一个对象
          self.queryset--->先找对象自己,而自己没有,才用了类的
            
      -类方法:绑定给对象的方法(写成方法的目的是解耦合,提高扩展性)
            -get_queryset:返回所有要序列化的数据
            -get_object:获取单条数据,路由必须:path('/<int:pk>/', PublishDetailView.as_view()),
            -get_serializer:真正的实例化得到序列化类 ser=get_serializer(instance=qs,many=True)
            -get_serializer_class:仅仅是返回序列化类,不能传任何参数
            		serializer_class=self.get_serializer_class()
                serializer_class(instance=qs,many=True)
  
# 5 个视图扩展类(不是视图类,需要配合GenericAPIView及其子类使用,不能配合APIView使用)
-因为五个视图扩展类用到了GenericAPIView类中的属性和方法,所以必须结合GenericAPIView一起使用。需要什么功能就继承GenericAPIView和视图扩展类
from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin	
		class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerialzier

    def get(self, request):
        return self.list(request)

    def post(self, request):
        return self.create(request)

class BookDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerialzier

    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)
      
# 9个视图子类
from rest_framwork.generics import ListAPIView,CreateAPIView,RetrieveAPIView,UpdateAPIView,DestroyAPIView,ListCreateAPIView,RetrieveUpdateDestroyAPIView,RetrieveUpdateAPIView,RetrieveDestroyAPIView
-分别继承GenericAPIView和5个扩展类里的某些,代码只用写queryset和serializer_class

# 视图集:
from rest_framwork.viewsets import ModelViewSet
ModelViewSet
  	-继承了5个接口的扩展类和GenericViewSet
  	-继承它后,只需要在视图类中写两行 queryset = Book.objects.all() serializer_class = BookSerialzier
    -配置路由,5个接口都有了
    	path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
    	path('books/<int:pk>/', BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
    -一般要重写某个方法,可以在保存前后添加某些功能
    
ReadOnlyModelViewSet:继承了两个接口,ListModelMixin、RetrieveModelMixin和GenericViewSet

ViewSetMixin:魔法,不能单独使用,必须配合视图类用,会使路由写法改变,注意查找顺序,写在左边

ViewSet:继承了ViewSetMixin、APIView,以后想继承APIView,但是想路由写法变化,视图类中方法可以任意命名

GenericViewSet:继承了ViewSetMixin、GenericAPIView,以后想继承GenericAPIView,但是想路由写法变化,视图类中方法可以任意命名

28 drf之路由

# 路由写法有多种
	-原始写法
  -映射的写法:path('books/', BookView.as_view({'get': 'list', 'post': 'create'}))
  -自动生成路由
        -必须要继承ViewSetMixin及其子类的视图类,才能用
        -继承了5个视图扩展类+ViewSetMixin的视图类,能自动生成路由(自动映射),生成的路由跟咱们写的这个是一样的
               -path('books/', BookView.as_view(actions={'get': 'list', 'post': 'create'})),
               -path('books/<int:pk>/', BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
            -1.导入路由类 from rest_framwork import SimpleRouter,DefaultRouter
            -2.实例化得到对象 router=SimpleRouter()
            -3.注册路由(视图类)
            router.register('lqz',SMSView,'lqz') # 前缀/视图类/别名,
            # 路径是:http://127.0.0.1:8000/api/v1/lqz/lqz/
            -4.加入到路由中:
            -4.1 urlpatterns+=router.urls
            -4.2 使用include urlpatterns=[
              path('lqz',include(router.urls))
            ]
          
        
-自己写的视图类的方法,如何映射
    	-映射的方式我们会
      -自动生成的方式
					1 继承了 5个视图扩展类+ViewSetMixin的视图类,能自动生成路由(get:list,get:retrieve..)
    			2 我们自己命名的: 方法名:login  send_sms,需要使用装饰器来做
        # 视图类:需要使用装饰器做映射,否则不知道method对应视图中的哪个
        from rest_framework.decorators import action
        class SMSView(ViewSet):
          # url_path不写的话会自动将方法名写在路径中
            @action(methods=['GET'], detail=False, url_path='lqz', url_name='lqz')
            def lqz(self, request):
        # 路由
        			-1.导入路由类 from rest_framwork import SimpleRouter
          		-2.实例化得到对象 router=SimpleRouter()
            	-3.注册路由(视图类)
        					router.register('lqz',SMSView,'lqz') # 前缀/视图类/别名
              -4.加入到路由中:
              		-4.1 urlpatterns+=router.urls
                	-4.2 使用include urlpatterns=[
                    path('lqz',include(router.urls)) # 前面可以不写为空
                  ]
        # 路径是:http://127.0.0.1:8000/api/v1/lqz/lqz/
        
   				3 action装饰器的参数
							methods:请求方式
    					detail:一个True,一个False,用True,表示生成详情的路径 <int:pk>
    					# True,books/1/方法名/
        			# False,books/方法名/
    				url_path:路径名字,需要加上前面的路径一起,如果不加,默认以函数名作为路径名
    				url_name:反向解析使用的名字(用的不多)
    
    
    
    
 		# 路由类,有两个,用法完全一致,区别是DefaultRouter生成的路径多
		SimpleRouter :用的最多
    DefaultRouter
    # DefaultRouter与SimpleRouter的区别是,DefaultRouter会多附带一个默认的API根视图,返回一个包含所有列表视图的超链接响应数据。

补充:django转换器,配置文件作用

#  django转换器:django 2.x以后,为了取代 re_path
		-int    path('books/<str:name>')   ---->/books/1----》name=1---》当参数传入视图类的方法中
    -str
    -path 匹配非空字段,包括分隔符/
    -slug
    -uuid
    
# django配置文件
	1 djagno项目要运行,优先执行配置文件的内容,做一下配置加载工作manage.py,
    	-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drf_day07.settings')
  2 任何一个django项目都有两套配置:
    	-一套是项目自己的,自己有那个配置参数,优先用自己的
      -一套是内置的(django内置的)
  3 配置参数的作用

配置文件settings

from pathlib import Path

# 1  项目的根路径
BASE_DIR = Path(__file__).resolve().parent.parent
# 2 密钥---》djagno中涉及到加密的,大部分都会用这个密钥
SECRET_KEY = 'django-insecure-eh=o(kt_70%8wj4r+le-7*$7t+fn%_2rfh61f09(2^&3q-5vk)'

# 3 是否开启调试模式,上线一定关闭
# 只要是调试模式:
# 访问的路径不存在,他会显示出所有能访问的路径
# 视图类出了异常,浏览器中能看到
DEBUG = False

# 4 项目是要部署在某个服务器上,这个列表写,部署服务器的ip地址, * 表示任意地址都可以
ALLOWED_HOSTS = ['*']

# 5 内置,我们自己写的app
from django.contrib import auth

INSTALLED_APPS = [
    'django.contrib.admin',  # 后台管理---》很多表不是它的,是别的app的
    'django.contrib.auth',  # auth 模块,UsrInfo表----》有6个表
    'django.contrib.contenttypes',  # 有个django_content_type表是,这个app的
    'django.contrib.sessions',  # session相关的
    'django.contrib.messages',  # 消息框架
    'django.contrib.staticfiles',  # 静态文件开启的app
    'app01.apps.App01Config',  # app01
    'rest_framework'  # drf
]

# 6  中间件
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',  # session相关
    'django.middleware.common.CommonMiddleware',  # 公共
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# 7 根路由
ROOT_URLCONF = 'drf_day07.xxx'

# 8 模板文件所在路径
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],  # 必须是个列表
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

# 9 项目上线,运行application,后面再说
WSGI_APPLICATION = 'drf_day07.wsgi.application'

# 10 数据库配置
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

# 密码认证相关,忽略

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# 11 做国际化
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True

# 13 静态文件
STATIC_URL = '/static/'

# 14 表中,默认可以不写id,id主键自增,之前全是AutoField,长度很短
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

29 登陆功能

表模型

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


class UserToken(models.Model):
    token = models.CharField(max_length=64)
    user = models.OneToOneField(to=UserInfo, on_delete=models.CASCADE)

视图类

class UserView(ViewSet):
    @action(methods=['POST'], detail=False)
    def login(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user = UserInfo.objects.filter(name=username, password=password).first()
        if user:
            # 登录成功
            # 1 生成一个随机字符串 token
            token = str(uuid.uuid4())
            # 2 把token存到表中,UserToken表有值就更新,没有值就新增
            UserToken.objects.update_or_create(user=user, defaults={'token': token})
            return Response({'code': 100, 'msg': '登录成功', 'token': token})
        else:
            return Response({'code': 101, 'msg': '用户名或密码错误'})
        
        
        
# 回顾
	UserToken表中有user字段
    拿到了一个UserToken表的对象 user_token
    	user_token.token 就是字符串
        user_token.user  基于对象的跨表查询,拿到的是user对象  user_token.user.password
        user_token.user_id  隐藏了这个字段,是可以用的,它是管理的user对象的id号
    查询功能
     UserToken.objects.filter(user=user对象)
     UserToken.objects.filter(user_id=user.id)

路由

# 方式一:
path('login/', views.UserView.as_view({'post':'login'})),
# 路由如果这样写,是不需要使用action装饰器


# 方式二:自动生成路由---》视图类中一定要用action装饰器
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register('user', views.UserView, 'user')
urlpatterns = [
    path('admin/', admin.site.urls),
]
# http://127.0.0.1:8000/user/login/
urlpatterns += router.urls

30 认证组件

# APIView执行流程
	-在视图视图类的方法之前,执行了三大认证
    	-认证
        
# 认证:登录认证
	-登录认证---》控制,某个接口必须登录后才能访问

from rest_framework.authentication import BaseAuthentication
from .models import UserToken
from rest_framework.exceptions import AuthenticationFailed
# 1 写一个类,继承BaseAuthentication
class LoginAuth(BaseAuthentication):
    # 2 类中重写方法
    def authenticate(self, request):  # 父类中有,一定要重写
        # 3 在方法中,完成登录认证,如果不是登录的,抛异常
        # token携带地方有2处:请求地址和请求头
        # 3.1取出token
        token = request.query_params.get('token')  # 请求地址
        token = request.META.get('HTTP_AUTHORIZATION')  # 请求头
        # 3.2去数据库中根据token,校验有没有数据
        user_token = UserToken.objects.filter(token=token).first()
        if user_token:
            user_obj = user_token.user

        # 4 如果是登陆的,返回登陆用户和token
            return user_obj,token
        else:
            raise AuthenticationFailed('没有登陆,禁止访问')

        # 5 在视图类中,使用认证类(局部使用在类里使用,全局使用在配置文件中配置)
    
# 认证组件使用步骤(固定用法)
		1 写一个类,继承BaseAuthentication
    2 在类中写:authenticate
    3 在方法中,完成登录认证,如果不是登录的,抛异常
    4 如果是登录的,返回登录用户和token
    
    5 在视图类中,使用认证类(局部使用)
    class BookView(APIView):
    	authentication_classes = [LoginAuth, ] 
     
    6 全局使用:
    # 全局使用
    ### 重点:不要在配置文件中,导入莫名其妙的包
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'app01.auth.LoginAuth'
        ],

    }
    
    7 全局使用后,局部禁用
    class UserView(ViewSet):
        # 局部禁用
        authentication_classes = []
    
    8 认证类的使用顺序
    	-优先用视图类配置的
        -其次用项目配置文件
        -最后用drf默认的配置
        
        
        
# 小重点;一旦通过认证,在request中就有当前登录用户
    def get(self, request):
        # 一旦通过认证,在request中就有当前登录用户
        print(request.user.name,'访问了接口')

31 权限组件

# 大家都登录了,但有的功能(接口),只有超级管理员能做,有的功能所有登录用户都能做----》这就涉及到权限的设计了

# 权限设计:比较复杂---》有acl,rbac,abac。。。

# 咱们现在只是为了先讲明白,drf的权限组件如何用,咱们先以最简单的为例
	-查询所有图书:所有登录用户都能访问(普通用户和超级管理员)
    	-其实没有权限控制
  -删除图书,只有超级管理员能访问
    	-给其它用户设置的权限
        
from rest_framework.permissions import BasePermission

class UserPermission(BasePermission):
    def has_permission(self, request, view):
        if request.user.user_type == 1:
            return True
        else:
            # 错误信息的渲染
            self.message='您的权限过低,无法访问'
            return False      
        
        
# 权限类的使用步骤
		1 写一个类,继承BasePermission
    2 在类中写方法:has_permission
    		-如果有权限,就返回True
        -如果没有权限,就返回False
        -错误信息是self.message='字符串'
        
    3 局部使用
    class BookDetailView(APIView):
        permission_classes = [AdminPermission, ]
    4 全局使用
        REST_FRAMEWORK = {
        'DEFAULT_PERMISSION_CLASSES': [
            'app01.permission.AdminPermission'
        ],

        }
    5 局部禁用
    class BookView(APIView):
    	permission_classes = []

32 频率组件

# 限制访问频次
	-比如某个接口,一分钟只能访问5次,超过了就得等
    -按IP地址  限制
    -按用户id  限制

from rest_framework.throttling import SimpleRateThrottle
class FrequencyThrottle(SimpleRateThrottle):
    # 类属性限制频率
    scope = 'blue'
    def get_cache_key(self, request, view):
        # 返回什么就以什么做频率限制:主要是用户id和客户端ip地址
        # ip地址
        # ip = request.META.get('REMOTE_ADDR')
        # return ip
        # 用户id
        id = request.user.id
        print(id)
        return id
      
# 频率类的使用步骤
		1 写个类,继承:SimpleRateThrottle
  # 继承BaseThrottle,重写allow_request,如果频率运行,就返回True,如果频率不允许就返回False
    2 重写某个方法:get_cache_key
    	-可以返回ip或用户id
    	-返回什么,就以什么做频率限制
        
    3 写一个类属性,随意命名一个名
    	scope = 'lqz'
    4 在配置文件中配置:
     'DEFAULT_THROTTLE_RATES': {
        'lqz': '3/m' # 一分钟访问3次
    },     
   5 全局用
    'DEFAULT_THROTTLE_CLASSES': [
        ],
   6 局部用
    class BookView(APIView):
        throttle_classes = [MyThrottle]
    	

32.1 自定义频率类

 # 1 频率类
    - 写一个类,继承SimpleRateThrottle,重写get_cache_key,返回[ip,用户id]什么,就以什么做限制,编写类属性  scope = 字符串,在配置文件中配置
      'DEFAULT_THROTTLE_RATES': {
        '字符串': '3/m',
    }
    -配置在视图类,全局使用(配置在配置文件中)
    
    
# 2 自定义频率类
	-源码中找---》自定义频率类如何写---》写个类,重写allow_request,如果能访问就返回True,不能访问返回False
    -APIView---》dispatch---》
        def check_throttles(self, request):
            throttle_durations = []
            # 在视图类上配置的频率类的列表中一个个频率类的对象
            for throttle in self.get_throttles():
                if not throttle.allow_request(request, self):
                    throttle_durations.append(throttle.wait())

            if throttle_durations:
                durations = [
                    duration for duration in throttle_durations
                    if duration is not None
                ]
                duration = max(durations, default=None)
                self.throttled(request, duration)
# 3 自定义频率类
class MyThrottles():
    VISIT_RECORD = {} # 访问者 ip 字典,格式是{ip1:[时间4,时间3,时间2,时间1],ip2:[时间,时间],ip3:[当前时间]}
    def __init__(self):
        self.history = None
    def allow_request(self, request, view):
        # (1)取出访问者ip
        # print(request.META)
        ip = request.META.get('REMOTE_ADDR')
        import time
        ctime = time.time()
        # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
        if ip not in self.VISIT_RECORD:
            # {ip地址作为key:[当前时间,时间1,时间2,时间3]}
            self.VISIT_RECORD[ip] = [ctime, ]
            return True
        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])
    
    
    
# 3 SimpleRateThrottle源码分析
    -allow_request:必须有的,频率类,必须重写它
    -get_cache_key:没有具体实现,直接抛了异常,需要子类重写
    -wait:必须有的,返回一个数字,告诉前端,还有多长时间能访问
    -------都是为了给allow_request辅助的-----
    get_rate
    parse_rate
    throttle_failure
    throttle_success
    
   
def allow_request(self, request, view):
        if self.rate is None: # 要取init中看,self.rate=3/m,如果自己在频率类中写rate=5/m,我们就不需要写scope和配置文件了
            return True
        # get_cache_key返回了ip:192.168.1.19
        self.key = self.get_cache_key(request, view)
        if self.key is None: # 如果get_cache_key返回None,也不会做频率限制
            return True
        # self.history = self.VISIT_RECORD.get(self.key, [])
        # self.cache 是缓存,相当于咱们的self.VISIT_RECORD
        self.history = self.cache.get(self.key, [])
        self.now = self.timer()# 取出当前时间
        # self.duration:配置的1分钟访问3次,self.duration就是60
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests: # '5/m',num_requests就是5
            return self.throttle_failure()
        return self.throttle_success()

# __init__    
def __init__(self):
  if not getattr(self, 'rate', None): # 如果没有,执行下面的代码
      self.rate = self.get_rate()  
  self.num_requests, self.duration = self.parse_rate(self.rate)

# self.get_rate()  
    def get_rate(self):
        # self.scope  我们自己写的字符串
        # self.THROTTLE_RATES配置文件中的那个字典
        return self.THROTTLE_RATES[self.scope]  # 咱们配置的 3/m
# self.num_requests, self.duration = self.parse_rate(self.rate)---》self.rate是3/s
    def parse_rate(self, rate):
        if rate is None:
            return (None, None)
        # 3/m---->num=3,period=m
        num, period = rate.split('/')
        # num_requests=数字3
        num_requests = int(num)
        # 
        d={'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
        period[0]='m'
        d[m]
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        return (num_requests, duration)
# 作业:使用django中间件实现,按ip地址,一分钟只能访问5次的限制

# 自己写频率,同样的ip,一分钟只能访问3次---》频率类
    # 自定义的逻辑{ip1:[第二次访问时间,第一次访问时间],ip2:[第一次访问时间,],ip3:[当前时间]}
    #(1)取出访问者ip
    #(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
    #(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
    #(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
    #(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败

33 权限源码(了解)

33.1 权限源码

# 继承了APIView,才有的---》执行流程---》dispatch中----》三大认证
	#1  APIView的dispatch的大约497行self.initial(request, *args, **kwargs)
    def initial(self, request, *args, **kwargs):  # 399行
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
        
        
    # 2 读权限:APIView的方法self.check_permissions(request)
        def check_permissions(self, request):
            # permission_classes = [AdminPermission]
            
            # self.get_permissions()我们配置在视图类上permission_classes列表中的认证类,一个个的对象
            # self.get_permissions()  是  [AdminPermission(),]
            for permission in self.get_permissions():
                # 写的权限类,要重写has_permission方法
                # 猜permission是我们写的权限类的对象
                # self 是视图类的对象(BookView,PublisViwe)
                if not permission.has_permission(request, self): # 权限没通过
                    self.permission_denied(
                        request,
                        # self.message 错误文字
                        message=getattr(permission, 'message', None),
                        code=getattr(permission, 'code', None)
                    )
                    
     # 3 读 APIView--->self.get_permissions
        def get_permissions(self):
        	return [permission() for permission in self.permission_classes]
        
        	# 翻译:
        	l=[]
        	for permission in self.permission_classes:
                l.append(permission())
            return l
        
        
# 记住:
	- 写的权限类,一定要写一个方法has_permission,返回True或False
  - 配置再视图类上

33.2 补充

# 1 视图类的方法,必须返回 4件套或drf的Response
# 2 return和raise完全不一样
# 3 视图类继承了
	视图类:ViewSetMixin,ListModelMixin----》查询所有好多代码不用写----》可以自动生成路由
    router.register('book',BookView,'book')
    /book/--->get请求过来被映射了 list---》执行视图类中的list----》
    BookView中写了get方法,根本不会执行
    自动生成路由

34 认证源码(了解)

34.1 认证源码

# 继承了APIView,才有的---》执行流程---》dispatch中----》三大认证
	#1  APIView的dispatch的大约497行self.initial(request, *args, **kwargs)
    def initial(self, request, *args, **kwargs):
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
    # 2 self.perform_authentication(request)
        def perform_authentication(self, request):
        	request.user  # 这是个方法,包装成了数据属性
            
    # 3 Request类的user   220 行
        @property
        def user(self):
            if not hasattr(self, '_user'):
                with wrap_attributeerrors():
                    self._authenticate()
            return self._user
        
   # 4 Request类的self._authenticate()   373 行
    def _authenticate(self):
        # self.authenticators 就是你写的认证类列表---》列表推导式---》[LoginAuth(),],是从Request类传过来,Request类初始化是在APIView的dispatch前面authenticators=self.get_authenticators(),
        '''
        def get_authenticators(self):
        	return [auth() for auth in self.authentication_classes]
        '''
        for authenticator in self.authenticators:
            try:
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                # 抛了异常被捕获了

            if user_auth_tuple is not None:
                # 如果返回了两个值,就会执行这句话
                # self是Request的对象
                self.user, self.auth = user_auth_tuple
                return
        self._not_authenticated()
	
    
  # 5 Request类初始化
	APIView---》dispathc的前面
 # 总结:
	1 认证类,必须写一个方法authenticate
  2 如果认证通过,可以返回None,也可也返回两个值,但是第一个值,尽量是当前登录用户,第二个值一般放token
  3 认证失败,抛异常AuthenticationFailed,继承了APIException,能捕获

34.2 Django中的翻译函数

# 只要做了国际化,会自动翻译成当前国家的语言
from django.utils.translation import gettext_lazy as _
_('hello')

35 过滤

# restful规范中
	-请求地址中带过滤条件
    
# 带过滤的接口只有:查询所有


# 2.1 内置过滤类

from rest_framework.filters import SearchFilter
class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerialzier
    filter_backends = [SearchFilter]
    # 支持这种搜索
    # # http://127.0.0.1:8000/api/v1/books/?search=红
    # search_fields = ['name']

    #http://127.0.0.1:8000/api/v1/books/?search=11  只要name或price中带11都能搜出来
    search_fields = ['name','price']


# 2.2 第三方过滤类

# pip3 install django-filter
from django_filters.rest_framework import DjangoFilterBackend
class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerialzier
    filter_backends = [DjangoFilterBackend]
    # http://127.0.0.1:8000/api/v1/books/?name=红楼梦&price=45
    # 按名字和价格精准匹配
    filterset_fields = ['name', 'price']



# 2.3 自定义过滤类   价格再100----200之间的图书
from .filter import MyFilter
class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerialzier
    filter_backends = [MyFilter]
	# 必须配合一个过滤类
    
from rest_framework import filters
from django.db.models import Q
class MyFilter(filters.BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        # 返回的数据,就是过滤后的数据
        # http://127.0.0.1:8000/api/v1/books/?price=44&name=红楼梦   按名字或价格
        price = request.query_params.get('price')
        name = request.query_params.get('name')
        queryset = queryset.filter(Q(name=name) | Q(price=price))
        return queryset

    
 # 过滤和排序可以一起使用

36 排序

# restful规范中
	-请求地址中带过滤条件
# 排序功能的接口:查询所有


from rest_framework.filters import OrderingFilter

class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerialzier
    filter_backends = [OrderingFilter]  # 排序类
    # 配合一个类属性,按哪些字段可以排序
    # http://127.0.0.1:8000/api/v1/books/?ordering=-price,-id
    # 先按价格倒序排,价格一样,再按id倒叙排
    ordering_fields = ['price','id']

37 分页

# 查询所有接口,过滤和排序了,但是实际上,这个接口,都需要有分页功能
	-分页的展现形式
    	web:下一页点解
      app,小程序:下滑下一页
  -接口都一样,要支持分页
    
    

# drf提供给咱们,三种分页方式
# 基本分页
# 偏移分页
# 游标分页


from .page import MyPageNumberPagination,MyLimitOffsetPagination,MyCursorPagination
# 这种方式用的多
class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerialzier
    # http://127.0.0.1:8000/api/v1/books/?page=2&page_size=3
    # pagination_class = MyPageNumberPagination  # 只能按一种方式分页,不要放到列表中了

    # http://127.0.0.1:8000/api/v1/books/?limit=4&offset=3  # 从第三条数据开始,取4条
    # pagination_class = MyLimitOffsetPagination  # 只能按一种方式分页,不要放到列表中了


    pagination_class = MyCursorPagination  # 只能按一种方式分页,不要放到列表中了

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


## 基本分页
class MyPageNumberPagination(PageNumberPagination):
    # 重写几个类属性 :4个
    page_size = 2  # 每页显示的条数
    page_query_param = 'page'  # page=4 表示第4页
    page_size_query_param = 'page_size'  # page=4&page_size=5 表示查询第4页,每页显示5条
    max_page_size = 5  # 每页最大显示多少条


## 偏移分页
class MyLimitOffsetPagination(LimitOffsetPagination):
    # 重写几个类属性 :4个
    default_limit = 2  # 每页显示多少条
    limit_query_param = 'limit'  # limit=3  这一页取3条
    offset_query_param = 'offset'  # 偏移量是多少  offset=3&limit=2  从第3条开始,拿2条
    max_limit = 5  # 最多取5条


## 游标分页,只能上一页和下一页,不能直接跳到某一页,但是这个的速度快---》app上用它多
class MyCursorPagination(CursorPagination):
    # 重写几个类属性 :3个
    cursor_query_param = 'cursor'  # 查询参数,其实用不到
    page_size = 2  # 每页显示多少条
    ordering = 'id'  # 必须是要分页的数据表中的字段,一般按id来
# 作业:继承APIView,实现过滤排序和分页
class PublishView(ViewSet):
    search_fields = ['name', 'city']

    def list(self, request, *args, **kwargs):
        publish_list = Publish.objects.all()
        # 实例化得到一个过滤对象
        filter_obj = SearchFilter()
        fq = filter_obj.filter_queryset(request,publish_list,view=self)

        # 实例化得到一个排序对象
        order_obj = OrderingFilter()
        order_obj.ordering_fields = ['id']
        oq = order_obj.filter_queryset(request, fq, view=self)

        # 实例化得到一个分页器对象
        basicpage_obj = BasicPagination()
        pq = basicpage_obj.paginate_queryset(oq, request, view=self)
        serializer = PublishSerializer(pq, many=True)
        return basicpage_obj.get_paginated_response(serializer.data)
      
# 补充
# 面向对象封装---》
	-__开头,隐藏了,
    	-内部使用self.__属性/方法
      -外部使用:person._类名__字段名
  -别人,公司内部,不太使用 __
    	-约定俗称,类内部使用 _开头,本意就是隐藏,不给外部使用

补充:修改项目名字

# 1 先找到原文件路径:copy path--Absolute Path,去电脑文件资源管理器进行粘贴,找到后拷贝

# 2 refactor--rename--rename directory文件夹&project项目,终极大法:右键--- replace in files--replace all

# 3 环境变量设置:file--settings--language&framework--django--修改root和settings的路径

# 4 终端运行: python3.9 manage.py runserver

38 全局异常处理

# 对于前端来讲,后端即便报错,也要返回统一的格式,前端便于处理
{code:999,msg:'系统异常,请联系系统管理员'}


# 只要三大认证,视图类的方法出了异常,都会执行一个函数:rest_framework.views import exception_handler

### 注意:exception_handler
    # 如果异常对象是drf的APIException对象,就会返回Response
    # exception_handler只处理了drf的异常,其它的异常需要我们自己处理
    # 如果异常对象不是drf的APIException对象,就会返回None

'''
我们要做的事就是重写该函数(因为源码原则上来说不可以改),然后写在配置文件中,这样代码出异常就走我写的exception_handler来统一格式。
源码可以改,直接在第三方模块中copy该模块到项目中,即走该模块可以魔改,缺点是实时更新
'''
# 补充:
	-函数和方法的区别?
  与类和实例无绑定关系的function都属于函数
  与类和实例有绑定关系的function都属于方法
  函数是封装了一些独立的功能,可以直接调用,能将一些数据(参数)传递进去进行处理,然后返回一些数据(返回值),也可以没有返回值。可以直接在模块中进行定义使用。 所有传递给函数的数据都是显式传递的。
  方法和函数类似,同样封装了独立的功能,但是方法是只能依靠类或者对象来调用的,表示针对性的操作。
  方法是自动传参self,函数是主动传参
    
# 补充:
   - isinstance() 判断一个对象是不是某个类的对象  isinstance(对象,类)
   - issubclass() 判断一个类,是不是另一个类的子类
from rest_framework.response import Response
from rest_framework.views import exception_handler
def common_exception_handler(exc, context):
  # exc:错误对象;context:上下文对象,里面有请求对象(request)和视图类的对象(View)
    # 只要走到这里,一定出异常了,我们正常的项目要记录日志(后面讲)
    # 两种可能:一个是Response对象,一个是None
    res = exception_handler(exc, context)
    if res:
        # 说明是drf的异常,它处理了
        if isinstance(res.data, dict):
            detail = res.data.get('detail')
        else:
            detail = res.data
        return Response({'code': 998, 'msg': detail})
    else:
        # 说明是其它异常,它没有处理
        # return Response({'code': 999, 'msg': '系统异常,请联系系统管理员'})
        return Response({'code': 999, 'msg': str(exc)})
    
### 配置文件
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.excepitons.common_exception_handler',
}

######目的:处理全局异常,返回统一格式

39 接口文档

# 后端把接口写好后
	-登录接口
  -注册接口
  -查询所有图书带过滤接口
# 前端人员需要根据接口文档,进行前端开发

# 前后端需要做对接----》对接第一个东西就是这个接口文档---》前端照着接口文档开发

# 公司3个人,每个人开发了10个接口,3个人都要同时写接口文档

# 接口文档的编写形式
	-1 word,md,编写,大家都可以操作,写完放在git,公司的文档管理平台上
  -2 第三方的接口文档平台(收费)
    	https://www.showdoc.com.cn/
  -3 公司自己开发接口文档平台
  -4 公司使用开源的接口文档平台,搭建
    	-YAPI:百度开源的
      -https://zhuanlan.zhihu.com/p/366025001
  -5 项目自动生成接口文档
    	-coreapi
      -swagger
        
        
# 使用coreapi自动生成接口文档
	-使用步骤:
    	-1 安装:pip3 install coreapi
			-2 加一个路由
        	from rest_framework.documentation import include_docs_urls	
        	urlpatterns = [
            	path('docs/', include_docs_urls(title='站点页面标题'))
        	]
      -3 在视图类上加注释
        
      -4 配置文件中配置:
        'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
      -5 表模型或序列化类的字段上写 help_text--->会显示在接口文档的字段介绍上
        
      -6 访问地址:
        http://127.0.0.1:8000/docs/
        

40 base64编码和解码

# base64并不是一种加密反射,只是编码解码方式
# 字符串可以转成base64编码格式:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 解码成base64

import json
import base64

# d = {'user_id': 1, 'username': "lqz"}
# d_str = json.dumps(d)
# # 对字符串进行bashe64 编码
# res=base64.b64encode(bytes(d_str,encoding='utf-8'))
# print(res)  # eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImxxeiJ9


# 解码
# res=base64.b64decode('TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ=')
# print(res)


# 记住: base64 编码,长度一定是4的倍数。如果不够,用 = 补齐


# base64的用途
'''
1 互联网中,前后端数据交互,使用base64编码
2 jwt 字符串使用base64编码
3 互联网中一些图片,使用base64编码
'''
s = 'iVBORw0KGgoAAAANSUhEUgAAAMcAAADHCAIAAAAiZ9CRAAAHB0lEQVR42u3dwXakSAxEUf//T3v2MwuD4kmkch7Lsl1Q5KUrCHFO//y6udHbj6fArUvVT7Y92tN/fvnJG+K7qB3Pk/d5dajUEYan5cnxFE6LqlSlKlVtVFU7I9QnCcH1XQlP/mrAR21fFLjn76MqValKVatVhetKnQhqXV8dxsA1Rq3iq8umtoIFCapSlapUpaqHZy1MCdT9f1+8o0zj1UnTSVCVqlSlqv+zqnClqTMSxiA8vuClQ23htuYqValKVaoaUfVhM5t77SsmBmLQZGuANx2qUpWqVLVXVfh8la9c8wr5fJWqfEVVvnKwKmrrmwGHceqTW3oqgE42OAwDValKVarao+qQG13qVpy6NsKTEBb6fVPhvutZVapSlarWqaKKb/yRKTzeDeQPKgYN7IsauqtKVapS1QWqaneqeB6i/goPWFTW6StTqKYj+VyqUpWqVLVRVe2+nRo8Uzf5fXDx6gSfT1CFS8t0WVWqUpWqlquqfSTqUZ5D6oy+Qj9c6bA1CA9MVapSlaruVhWea7waGAhzuA+8SQ9r9/AkqEpVqlLVBaqobEE9mYQniVpLTl1s4fwb/1z4+VGVqlSlqgtUHVI0Uy1wON8NlyG8NnB5n+UqValKVao6T1WIqe85JEo53iZPzuPDqyUsOAonXFWqUpWqFqliv1Zbi288aVHHgz/LhQcjKleRz1epSlWqUtV5qqgb+MkGnEKJj8/xdptKok2XuqpUpSpVLVLV99QRFQ5q4CZPMXUOJ+NU+A/EH7lKVapSlapuUUUdN1VhU78T0sEvtoHGJBHz5FBVpSpVqWqjqvAcDTTpVCsdZjiqjK5VMFTAwifQqlKVqlS1TlV4344HGgo3khJA3ANFwCfR7Y+JjapUpSpVbVBFdesD5W94Lx0OjKmZNFVVhJfWQc8sqEpVqlLVR6rmJ5HlveOUqboc7wg+2VdyflSlKlWp6g5V4Vcv/ogSPooOe/O+hoIqQfrqldfduqpUpSpV3aKKWjP89phqivFj/mSK0Dfyb8lVqlKVqlQ1q6rvWZy+roGaf4fO+gbqkz1+zZCqVKUqVV2gKrzf7nslvJPHa+VPdvFJS55MWVSlKlWpaqMqvOENUwKuHP8Rta6f9PjUWqhKVapS1U2qaj11bT1q1XPf7Tr1kBkVjMLLBt9UpSpVqeomVVQPS8UOvM7om1tTY91P9oWHQlWpSlWqWqdqYFIbntnw7roPLt504LG1bwKtKlWpSlUXqKrd2w8MMtnv+44seJqPsJQZSuuqUpWqVHWMqr6ieZJF3wWAf+Rj+wjkSlCVqlSlqkWq8O/XvlxVyw1Uv1y76sJnniYpI2uqKlWpSlWLVHWPG8steV//HsaO2huGXqmpRl83pCpVqUpVN6minuChBpl9qQWvnvF6ZaDKCXehKlWpSlXrVFF5KJxo4lPhbxuKyfl3X+lQOL2qUpWqVLVIFbXVeuowUvSV9bU/p6D0lezhBfk6ratKVapS1cGqQg3U13O4MFQoxJNN2K0P9DWfpXVVqUpVqjpGFV4Q901ha0kLv1qo0v/bv+rt1lWlKlWp6jpV+Ci6j2lNQ5/yyWHwZNuuKlWpSlV3q6LuwAeKgIEeP1y80P1AQzGUq1SlKlWp6hhVeDQJQ8bADfPAA1vUqIDKnU0RWVWqUpWqNqoKb3Sptf+FNirD1YLRAKbamaeqeVWpSlWqukAVhanvCzu8pccffsJ/eTLVhZf6n59UVapSlapWq8KnwpMBKwwQ1C19OLvtq1eoXbzOVapSlapUdZ6q2vyy9hU+EJU+gVKLJrVwSV02+NRcVapSlaquVEV9u4coB5YhHJ9TFieLkvB9/vWKqlSlKlUtUtVXjofLEHa+YT1NLRU+QqYeaMPH+apSlapUtU7VwCM4fa00VQSEn4vSif9OX+B73a2rSlWqUtV5qmozV+pbOVy88OCpd6bu26lMSWXcwuqoSlWqUtVGVVSS6CuIQ7gD3MPWAI+b+KKoSlWqUtWVqqj2Fv/zgUYAL/Rr4PqiW3j1Pv9cqlKVqlS1SFVYH4S9ME4HbwSoywafx+M5uGVioypVqUpVB6uiAkTT8zod+YM66VQ/EobLyerkdVpXlapUpaoNqvoqbOQLuzWo4aPfvvDU9yNkTVWlKlWpapEqKkmEU8++N8Sn3eGVEB4z9SO8f1eVqlSlqr2qDrnJr53ivoFxmDaoyDWwLyRlqkpVqlLValXsuPG34T//oNpkinu4eLXDOKRbV5WqVKUqVeEjUmoY3DcjD4fTh4QnasCgKlWpSlWqCpchzARhwKIqj0+CI1WmJFeCqlSlKlWtVkV99YZTTyqIhDqpfVEj7b5WJRylqEpVqlLVXlVUm9yXJMJynEpseGr51iKbIFWlKlWpapEqNzdwU5Ubv/0DEut6+QvkIFgAAAAASUVORK5CYII='
res = base64.b64decode(s)
with open('code.png', 'wb') as f:
    f.write(res)

41 jwt介绍和原理

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

# cookie,session,token发展历史
	-会话管理
    -cookie:客户端浏览器的键值对
    -session:服务的的键值对(djangosession表,内存中,文件,缓存数据库)
    -token:服务的生成的加密字符串,如果存在客户端浏览器上,就叫cookie
    		-三部分:头,荷载,签名
        -签发:登录成功,签发
        -认证:认证类中认证
    
# jwt:Json web token (JWT),web方向的token认证
	-长得样子:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

41.1使用jwt认证和使用session认证的区别

# 看图
https://www.cnblogs.com/liuqingzheng/articles/17413681.html
  
基于session认证
# session登陆:登陆接口带着用户名和密码到服务端,根据用户名和密码去查询,查询通过后生成一个随机字符串token串,将用户主键和token存放在session表中。这里有一个加锁的问题,比如说有多个人并发登陆,都要存在session表中,我们对表都是写操作,写操作需要加锁,所以效率较低。所以说登陆之后,把该存的数据存到session表中,并且往前端返回一个token串,前端存到cookie中去。

# 访问登陆才能访问的接口(校验):请求携带token串到服务端,服务端从请求头中拿出token串,查询session表,校验得到user对象,存储在request.user中供后台逻辑使用,就是之前写的认证类

# 一个项目在一台电脑上跑起来叫单击运行,如果把这一个项目部署在两台或者是多台机器上,这叫集群部署。多台机器的话访问后端地址会发生改变,这时需要在服务端前挡一个软件Nginx,它可以监听任意端口,然后请求转发。

# 请求转发:来个请求是要转发给不同的人,这样我们前端访问地址都是nginx服务器的地址啊,但它可以给我转发到不同的集群的某个节点上,做到了项目上的精细化部署。

# 你说我项目为什么要做集群化的部署啊?
	- 防止一台机器挂掉后,服务对外不可用
  - 提高并发量
  
# 并发量上来后,但我们连的还是那一个数据库?但去操作session表的时候还是这一个数据库?那它的并发量是不是不会显著的提升?所以数据库配套的也要做集群,这样比较麻烦,所以出来jwt认证

基于jwt认证
# jwt登陆:用户携带用户名和密码,访问我,我们校验通过,给用户用代码生成一个token串,生成之后给前端,存储在cookie中

# 校验: 前端在访问我需要登录的接口之后需要从请求中携带这个token过来,我们不需要在数据库中查,只需要利用签发token的逆运算代码解析token即可。校验得到user对象,存储在request.user中供后台逻辑使用。认证通过继续往后走,认证不通过提示错误

'''
优点:
		- 1 不会有数据库写操作,所以效率极高
		- 2 在做集群时,不需要过多的进行其它安全处理(token认证是一套原理)
		- 3 服务器不需要存token压力小

'''

41.2 三段式

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

#1  header  jwt的头部承载两部分信息:
声明类型,这里是jwt
声明加密的算法 通常直接使用 HMAC SHA256
公司信息
{
  'typ': 'JWT',
  'alg': 'HS256'
}
然后将头部进行base64编码,构成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

# 2 payload 载荷就是存放有效信息的地方。
当前用户信息,用户名,用户id
exp: jwt的过期时间,这个过期时间必须要大于签发时间

定义一个payload:
{
  "sub": "1234567890",
  "name": "John Doe",
  "user_id":99
}
然后将其进行base64编码,得到JWT的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9


# signature  JWT的第三部分是一个签证信息,这个签证信息由三部分组成:

header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

42 jwt开发流程

# 使用jwt认证的开发流程,就是两部分
	-第一部分:签发token的过程,登录做的
    -用户携带用户名和密码,访问我,我们校验通过,生成token串,返回给前端
  -第二部分:token认证过程,登录认证时使用,其实就是咱们之前讲的认证类,在认证类中完成对token的认证操作
    -用户访问我们需要登陆后才能访问的接口,必须携带我们签发的token串(一般请求头)
    -我们取出token,验证该token是否过期,是否被篡改,是否是伪造的
    -如果正常,说明荷载中的数据,就是安全的,可以根据荷载中的用户id,查询出当前登录用户,放到request中即可。在认证类中放,request.user
    
# 没有认证类的话,就在中间键里做,只要在视图类方法之前完成认证即可

43 drf-jwt快速使用

# djagno+drf框架中,使用jwt来做登录认证

# 使用第三方:
	-django-rest-framework-jwt:https://github.com/jpadilla/django-rest-framework-jwt (停止维护)
  -djangorestframework-simplejwt:https://github.com/jazzband/djangorestframework-simplejwt (替代品)
    
# 我们可以自己封装 :https://gitee.com/liuqingzheng/rbac_manager/tree/master/libs/lqz_jwt


# 安装
	pip3 install djangorestframework-jwt
    
    
# 补充:
	- 密码明文一样,每次加密后都不一样,如何做,动态加盐,动态的盐要保存,跟密码保存在一起
  - 有代码,有数据库,没有登录不进去的系统
    
# 解决不了:
	token被获取,模拟发送请求
    不能篡改
    不能伪造
    - 如何保证token串的安全:设置过期时间
    
# 快速使用
	-签发过程(快速签发),必须是auth的user表(人家帮你写好了)
  		- user表里的密码pwd:pbkdf2_sha256$260000$lk8N5xDenUTcqNVuf5tX5t$l58cQoolP+t7j92o9zOfn4498K3epHywhyFUl8XeHZo=
    	- $符号做分割,第一部分是加密算法,第二部分是过期时间,第三部分是盐,第四部分是加密后的密码
      - 这里面是用make_password函数进行加密
    	- 登录接口--》基于auth的user表签发的,就不需要写额外的代码
  -认证过程
    	- 登录认证,认证类(写好了)
    	- auth模块规定token串要写在请求头中,并且key为Authoriation,value为jwt 空格 token串
    
         
        
# 总结:
	-签发:只需要在路由中配置
        from rest_framework_jwt.views import obtain_jwt_token
        urlpatterns = [
            path('login/', obtain_jwt_token), 
        ]
  -认证:视图类上加
    from rest_framework_jwt.authentication import JSONWebTokenAuthentication
		from rest_framework.permissions import IsAuthenticated # 是否认证通过
    	class BookView(APIView):
            authentication_classes = [JSONWebTokenAuthentication] # 认证类,drf-jwt提供的
            permission_classes = [IsAuthenticated] # 权限类,drf提供的
    -访问的时候,要在请求头中携带,必须叫
    	Authorization:jwt token串

44 drf-jwt定制返回格式

# 登录签发token的接口,要返回code,msg,username,token等信息

# 对返回格式进行定制
	-1 写个函数,函数返回字典格式,返回的格式,会被序列化,前端看到	
    	def common_response(token, user=None, request=None):
            return {
                'code': '100',
                'msg': '登录成功',
                'username': user.username,
                'token': token,
            }

  -2 写的函数配置一下
        JWT_AUTH = {
        'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.jwt_response.common_response',
    }

45 drf-jwt自定义用户表签发

# 1 创建一个用户表
	class User(models.Model):
        username = models.CharField(max_length=32)
        password = models.CharField(max_length=64)
        age = models.IntegerField()
        email = models.CharField(max_length=64)
        # 注意⚠️from django.contrib.auth.models import AbstractUser
        然后继承该类,不是自定义user表,是扩展,还是使用auth的user表,如果这么写不需要自己写签发,用内置即可
'''
from django.contrib.auth.models import AbstractUser

class Customer(AbstractUser):
    USERNAME_FIELD = 'email'
    EMAIL_FIELD = 'email'
    REQUIRED_FIELDS = []

# 需要额外扩充的内容
    ....
可以重新指定 username 字段 USERNAME_FIELD
可以重新指定 email 的字段 EMAIL_FIELD
还有命令行在 createsuperuser 创建超级用户时提醒要输入信息的字段 REQUIRED_FIELDS

'''
# 2 登录接口
	class UserView(ViewSet):
        def login(self, request):
            username = request.data.get('username')
            password = request.data.get('password')
            user = User.objects.filter(username=username, password=password).first()
            if user:
                # 登录成功,签发token--->先背过,
                # 1 通过user,获取payload
                payload = jwt_payload_handler(user)
                print(payload)
                # 2 通过payload,得到token
                token = jwt_encode_handler(payload)
                return Response({'code': 100, 'msg': '登录成功', 'username': user.username, 'token': token})
            else:
                return Response({'code': 101, 'msg': '用户名或密码错误'})
              
              
      -配置文件,过期时间
      'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
# 3 登录接口签发token返回给前端

46 drf-jwt自定义认证类

# drf的认证类定义方式,之前学过
# 在认证类中,自己写逻辑

class JWTAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_TOKEN')
        # 背过
        # 校验token是否过期,合法,如果都通过,查询出当前用户,返回
        # 如果不通过,抛异常
        try:
            payload = jwt_decode_handler(token)
            # 如果认证通过,payload就可以认为是安全的,我们就可以使用
            user_id = payload.get('user_id')
            # 每个需要登录后,才能访问的接口,都会走这个认证类,一旦走到这个认证类,机会去数据库查询一次数据,会对数据造成压力?
            user = User.objects.get(pk=user_id) # 类实例化得到对象,user.id得到id,user.name得到name,没有去数据库中查询,减少数据库压力。但是有坑,有的字段没有放进去
            # 优化后的
            # user = User(username=payload.get('username'), id=user_id)
            # user = {'username':payload.get('username'), 'id':user_id}

        except jwt.ExpiredSignature:
            raise AuthenticationFailed('token过期')
        except jwt.DecodeError:
            raise AuthenticationFailed('解码失败')
        except jwt.InvalidTokenError:
            raise AuthenticationFailed('token认证异常')
        except Exception:
            raise AuthenticationFailed('token认证异常')
        return user, token
    
    
    
    
  # 全局使用,局部使用

47 drf-jwt的签发源码分析

# from rest_framework_jwt.views import obtain_jwt_token
# obtain_jwt_token就是ObtainJSONWebToken.as_view()---》视图类.as_view()

# 视图类
class ObtainJSONWebToken(JSONWebTokenAPIView):
    serializer_class = JSONWebTokenSerializer
    
# 父类:JSONWebTokenAPIView
class JSONWebTokenAPIView(APIView):
    # 局部禁用掉权限和认证
    permission_classes = ()
    authentication_classes = ()
    def post(self, request, *args, **kwargs):
        # serializer=JSONWebTokenSerializer(data=request.data)
        serializer = self.get_serializer(data=request.data)
		# 调用序列化列的is_valid---》字段自己的校验规则,局部钩子和全局钩子
     # 读JSONWebTokenSerializer的局部或全局钩子
        if serializer.is_valid(): # 全局钩子在校验用户,生成token
            # 从序列化类中取出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)
            return response

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
    
    
    
# JSONWebTokenSerializer的全局钩子
class JSONWebTokenSerializer(Serializer):
    def validate(self, attrs):
        # attrs前端传入的,校验过后的数据 {username:lqz,password:lqz12345}
        credentials = {
            'username': attrs.get('usernme'),
            'password': attrs.get('password')
        }

        if all(credentials.values()): # 校验credentials中字典的value值是否都不为空
            # user=authenticate(username=前端传入的,password=前端传入的)
            # auth模块的用户名密码认证函数,可以传入用户名密码,去auth的user表中校验用户是否存在
            # 等同于:User.object.filter(username=username,password=加密后的密码).first()
            user = authenticate(**credentials)
            if user:
                if not user.is_active:
                    msg = _('User account is disabled.')
                    raise serializers.ValidationError(msg)

                payload = jwt_payload_handler(user)

                return {
                    '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)

48 认证源码

# from rest_framework_jwt.authentication import JSONWebTokenAuthentication
# JSONWebTokenAuthentication
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
    def get_jwt_value(self, request):
        # auth=['jwt','token串']
        auth = get_authorization_header(request).split()
        auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
        if not auth:
            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] # token串
    
# 父类中找:BaseJSONWebTokenAuthentication---》authenticate,找到了
class BaseJSONWebTokenAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 拿到前端传入的token,前端传入的样子是  jwt token串
        jwt_value = self.get_jwt_value(request)
        # 如果前端没传,返回None,request.user中就没有当前登录用户
        # 如果前端没有携带token,也能进入到视图类的方法中执行,控制不住登录用户
        # 所以咱们才加了个权限类,来做控制
        if jwt_value is None:
            return None
        try:
            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()
        # 通过payload拿到当前登录用户
        user = self.authenticate_credentials(payload)
        return (user, jwt_value)

    
    
# 如果用户不携带token,也能认证通过
# 所以我们必须加个权限类来限制

class IsAuthenticated(BasePermission):
    def has_permission(self, request, view):
        return bool(request.user and request.user.is_authenticated)

49 权限控制

# ACL(访问控制列表)的权限控制:(针对互联网用户的产品)
	用户表
    id    name      password
    1     zhangsan   123
    
    权限表
    id   user_id    权限
    1     1         评论权限
    2     1         发抖音权限

    张三:[评论权限,发抖音权限]
# RBAC(Role-Based Access Control)基于角色的访问控制:(针对于公司内部项目)
权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便

# django的auth其实就实现了基于角色的访问控制---》通过表控制的
	-auth_user :用户表,存用户信息
    -auth_group:组,角色,存放角色信息
    -auth_permission:权限,存放权限信息
    # 分析:一个组(角色)中,有多个用户,一个用户,属于多种角色----》多对多
    -auth_user_groups:用户和组的多对多中间表
    # 分析:一个组,可能有多个权限,一个权限,也可能属于多个组---》多对多
    -auth_group_permissions:组和权限的多对多中间件
    # django,多了张表:auth_user_user_permissions
    # 分析:一个用户,可以有多个权限,一个权限,可以分配个多个用户---》多对多
    -auth_user_user_permissions:用户和权限多对多中间表
    

49.1 我们开发中做权限控制

# python开发,公司内部项目多,使用基于角色的访问控制的权限,比较多

# python+django 开发出一套公司内部项目,要带rbac的权限控制
	-1 基于django的auth+admin 快速开发,有更多操作,但是没学
  -2 基于django的auth+admin+第三方美化 快速开发
    	-国内的:simple-ui,xadmin(弃用了)
      -国外的:
  -3 基于django+vue+自定制权限 
    	-djagno-vue-admin :第三方开源
        
  -4 完全自己写

49.2 基于django的auth+admin+第三方美化 快速开发

# 1 安装
pip3 install django-simpleui
# 2 配置文件配置,注册app
	INSTALLED_APPS = [
      'simpleui',
      ...
  ]
    
# 3 菜单栏定制
# 4 自定义菜单和页面
# 5 自定义按钮
# 6 切换图标
# 7 首页显示的隐藏


# 其实它可以开发出任意各种功能

49.3 Django-vue-admin演示

# https://gitee.com/liqianglog/django-vue-admin