60、Reatful规范、序列化与反序列化、drf介绍与快速使用、CBV源码分析、APIView源码分析

141 阅读12分钟

Restful规范

1.定义:RESTful是一种定义Web API接口的设计风格(AIP接口的编写规范),尤其适用于前后端分离的应用模式中。
			 这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。
  		事实上,我们可以使用任何一个框架都可以实现符合restful规范的API接口。
2.规范
	1.数据的安全保障:url链接一般都采用https协议进行传输(采用https协议,可以提高数据交互过程中的安全性)
  
  2.接口特征表现:url地址中带接口标识
  	eg:https://api.baidu.com
    		https://www.baidu.com/api
      	微博api接口:https://api.weibo.com/2/common/code_to_location.json
        
  3.多版本共存,url地址中带版本信息
  	eg:https://api.baidu.com/v1
    	 https://api.baidu.com/v2
      
  4.数据即是资源,均使用名词(可复数):接口一般都是完成前后台数据的交互,交互的数据我们称之为资源      ps:url地址尽量使用名词;一般提倡用资源的复数形式,在url链接中不要出现操作资源的动词
  	eg:https://api.baidu.com/books
    		https://api.baidu.com/book
        https://api.baidu.com/users
        """
        特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义
        https://api.baidu.com/place/search
        https://api.baidu.com/login
        """
        
  5.资源操作由请求方式决定(method):操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作
  	eg:
    	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地址中带过滤条件  ?后带过滤条件
  	eg:
    	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:指定筛选条件
      https://api.baidu.com/books?name_contains=红 -->查找名字中含有红的图书
	7.响应状态码(http响应中带状态码)
  	1.http的响应状态码:
      1.xx 请求正在处理	
      2.xx 请求成功(200:常规请求 201:创建成功)
      3.xx 重定向响应(301:永久重定向 302:暂时重定向)
      4.xx 客户端异常(403:请求无权限 404:请求路径不存在 405:请求方法不存在)
      5.xx  服务器异常(500:服务器异常)
		2.http的响应的数据中带状态码(公司自己规定的)
    	--{code:100}
	8.返回的数据中带错误信息
    	eg:  
    	{code:101,msg:用户名或密码错误}
	
  9.返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范
  	GET /books:返回资源对象的列表(数组)-->https://api.baidu.com/books -->{'code':200,msg='获取成功',data = [{},{},...]}
    GET /books/1:返回单个资源对象-->https://api.baidu.com/books/1 -->{'code':200,msg='获取成功',data = {'name':obj.name,'price':obj.price}
    POST /books:返回新生成的资源对象  -->https://api.baidu.com/books -->{'code':200,msg='创建成功',data = {'name':obj.name,'price':obj.price}
    PUT /books/1:返回完整的资源对象 -->https://api.baidu.com/books/1 -->{'code':200,msg='修改成功',data = {'name':obj.name,'price':obj.price}
    DELETE /books/1:返回一个空文档 -->https://api.baidu.com/books/1 -->{'code':200,msg='删除成功'}
                                                                         10.返回的结果中带url链接
			eg:                                                                         {
        "status": 0,
        "msg": "ok",
        "results":[
            {
                "name":"肯德基(罗餐厅)",
                "img": "https://image.baidu.com/kfc/001.png"
            }
            ...
        ]
    }

序列化和反序列化

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

1.序列化: 把我们能够识别的数据转换成指定的格式提供给别人。
  	eg:我们在django中获取到的数据默认是模型对象(queryset),但是模型对象数据无法直接提供给前端或别的平台使用,所以我们需要把数据进行序列化,变成字符串或者json数据,提供给别人。

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

drf介绍和快速使用

需求:写5个接口(book表为例)

-查询所有图书
-新增一本图书
-修改一本图书
-查询一本图书
-删除一本图书
from django.shortcuts import render
from django.http import JsonResponse
# Create your views here.
from django.views import View
from .models import BookViews
import json
class Book(View):
    # 需求:查所有的图书
    def get(self,request):
        # 1.数据格式
        back_dic = {'code':200,'msg':'查看成功'}

        # 2.查看数据
        data = []
        books_obj = BookViews.objects.all()
        for obj in books_obj:
            dic = {'name':obj.name,'price':obj.price}
            data.append(dic)
        back_dic['data'] = data
        return JsonResponse(back_dic)

    # 需求增加一本书
    def post(self,request):
        # 1.数据格式
        back_dic = {'code':200,'msg':'添加成功'}

        # 2.接收数据
        print(request.POST)  # <QueryDict: {'name': ['三国演义'], 'price': ['100']}>
        name = request.POST.get('name')
        price = request.POST.get('price')

        # 3.参数验证
        if not (name and price):
            back_dic['code'] = 1001
            back_dic['msg'] = '名字或价格不能为空'
            return JsonResponse(back_dic)
        # 添加数据
        BookViews.objects.create(name = name,price = price)
        back_dic['data'] = {'name':name,'price':price}
        return JsonResponse(back_dic)

class BookDetail(View):
    # 需求:查看某一本书  eg:id为2
    def get(self,request,pk):
        # 1.数据格式
        back_dic = {'code':200,'msg':'获取成功'}
        book_obj = BookViews.objects.filter(pk=pk).first()
        if not book_obj:
            back_dic['code'] = 1002
            back_dic['msg'] = '这本书不存在'
            return JsonResponse(back_dic)

        # 2.读取数据
        back_dic['data'] = {'name':book_obj.name,'price':book_obj.price}

        return JsonResponse(back_dic)

    # 需求:修改数据    eg:id 将id为2的数据修改
    def put(self,request,pk):

        # 1.数据格式
        back_dic = {'code':200,'msg':'修改成功'}

        book_obj = BookViews.objects.filter(pk=pk).first()
        if not book_obj:
            back_dic['code'] = 1002
            back_dic['msg'] = '这本书不存在'
            return JsonResponse(back_dic)

        # 2.获取数据
        print(request.body)  # b'{"name":"hongloumeng","price":"99"}'
        dic =  json.loads(request.body)
        name = dic.get('name')
        print(name)
        price = dic.get('price')
        print(price)

        # 3.参数验证
        if not (name and price):
            back_dic['code'] = 1001
            back_dic['msg'] = '名字或价格不能为空'
            return JsonResponse(back_dic)
        # 4.修改
        BookViews.objects.filter(pk=pk).update(**dic)
        back_dic['data'] = {'name':name,'price':price}
        return JsonResponse(back_dic)

    # 需求:删除谋一条数据
    def delete(self,request,pk):
        # 1.数据格式
        back_dic = {'code':200,'msg':'删除成功'}

        # 删除
        book_obj = BookViews.objects.filter(pk=pk).first()
        if not book_obj:
            back_dic['code'] = 1002
            back_dic['msg'] = '这本书不存在'
            return JsonResponse(back_dic)
        book_obj.delete()
        return JsonResponse(back_dic)

drf介绍

1.django中有个app,djangorestframework:drf,能够帮助我们快速实现符合resful规范的接口

2.Django Rest_Framework:核心思想是缩减编写api接口的代码

3.安装drf:pip3.8 install djangorestframework --upgrade
	ps:	1.使用django 3.2.12  ;djangorestframework最新版
			2.如果你是django2,直接这样装,装最新drf,他们不匹配--->pip会自动把django卸载,安装最新django,安装最新drf
      3. django3,这样没有任何问题
      
4.补充:
		如果写了一个包,或app,想给别人用--->把你写的包,放到pypi上别人pip install 安装---》使用    ps:https://zhuanlan.zhihu.com/p/624648232

drf快速使用

使用drf快速实现5个接口

路由
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
class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
序列化类
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

drf之APIView源码分析

基于APIView的5个接口

APIView+序列化类+Response写5个接口

视图类

from rest_framework.views import APIView  # # APIView继承了djagno原来的View
from .models import BookViews
from .serializer import BookSerializer
from rest_framework.response import Response

class Book(APIView):
    # 需求:查所有的图书
    def get(self,request):
        back_dic = {'code':200,'msg':'查看成功'}
        book_list = BookViews.objects.all()
        # drf提供了序列化类
        ser = BookSerializer(instance=book_list,many=True) # 序列化
        back_dic['data'] = ser.data
        return Response(back_dic)


    # 需求增加一本书
    def post(self,request):
        back_dic = {'code':200,'msg':'添加成功'}
        ser = BookSerializer(data=request.data) # 反序列化
        if ser.is_valid(): # 数据校验  -->有些不合法的禁止
            ser.save()  # 保存至数据库
        return Response(back_dic)


class BookDetail(APIView):
    # 需求:查看某一本书  eg:id为2
    def get(self,request,pk):
        back_dic = {'code':200,'msg':'获取成功'}
        book = BookViews.objects.filter(pk=pk).first()
        ser = BookSerializer(instance=book,many=False)  # 序列化
        back_dic['data'] = ser.data
        return Response(back_dic)

    # 需求:修改数据    eg:id 将id为2的数据修改
    def put(self,request,pk):
        back_dic = {'code':200,'msg':'修改成功'}
        book = BookViews.objects.filter(pk = pk).first()
        ser = BookSerializer(instance=book,data = request.data)  # 反序列化
        if ser.is_valid(): # 数据校验  -->有些不合法的禁止
            ser.save()  # 保存至数据库
        return Response(back_dic)

    # 需求:删除谋一条数据
    def delete(self,request,pk):
        back_dic = {'code':200,'msg':'删除成功'}
        BookViews.objects.filter(pk=pk).delete()
        return Response(back_dic)

序列化类

from rest_framework import serializers
from .models import BookViews

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

路由

from django.urls import path
from django.contrib import admin
from app01 import views

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

CBV源码分析

CBV写法

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

4.结论:什么请求方式,就会执行视图类中的什么方法

入口分析

1.前提条件:前端请求,一旦路径匹配成功,就会执行:BookView.as_view()(request参数传入)
2.入口在:BookView.as_view()--->View中有个as_view类的绑定方法--->执行结果--->view

1.BookView中没有as_view()方法,所以在父类中找as_view方法

class View:
    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
    @classonlymethod
    def as_view(cls, **initkwargs):
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)    # BookView(**initkwargs)
            return self.dispatch(request, *args, **kwargs)
        return view

解析:父类中找as_view方法的执行结果是view的内存地址--->path('books/', view)-->当请求来了,会执行view(request)

2.执行View类中的as_view方法中的内层的view函数,,路由匹配成功,本质是在执行 self.dispatch(request, *args, **kwargs)

def dispatch(self, request, *args, **kwargs):
    if request.method.lower() in self.http_method_names:
      handler = getattr(self, request.method.lower(), self.http_method_not_allowed)   #  handler=getattr(BookView的对象,'get') ,此时的hander就是BookView类中的get方法
    else:
      handler = self.http_method_not_allowed
    return handler(request, *args, **kwargs)  # 执行 BookView类中的get方法 (request)
    
ps:1.在BookView中找dispatch,找不到;再去去父类View中找,找到了
	 2.最终本质与FBV的执行流程一样

APIView执行流程分析

1.有了drf,后期都写CBV,都是继承APIView及其子类

2.执行流程
	1.入口:path('books/', BookView.as_view())  -->请求来了执行:BookView.as_view()(request,)
  2.自己写的BookView类里没有as_view绑定的方法--->父类APIView里有as_view绑定的方法
  3.请求来了,执行 :(View类的as_view中的view,只是去掉了csrf的认证)(request)
  4.执行父类APIView中有dispatch方法
                                                
3.总结
  	1.以后只要继承APIView的所有视图类的方法,都没有csrf的校验了
    2.以后只要继承APIView的所有视图类的方法 中的request是新的request了
    3.在执行视图类的方法之前,执行了三大认证(认证,权限,频率)
    4.期间除了各种错误,都会被异常捕获,统一处理 

1.自己写的BookView类里没有as_view绑定的方法--->父类APIView里有as_view绑定的方法

@classmethod
def as_view(cls, **initkwargs):
    view = super().as_view(**initkwargs)
    view = csrf_exempt(view)
    return view
  
ps:1.super().as_view(**initkwargs)中,super()指代的是父类对象-->View类的对象
	 2.super().as_view(**initkwargs)-->View的as_view(**initkwargs) -->执行结果是view,是View类中的as_view方法中的view
   3.返回结果是:csrf_exempt(view)--->局部禁用csrf
   4.path('books/', View类中的as_view中的view,只是去掉了csrf的认证)
    
    
"""
补充(装饰器语法糖):
    fbv,局部禁用csrf,如何写?
    @csrf_exempt
    def index(request):
      pass

    本质原理是(装饰器本质):index=csrf_exempt(index)
"""

2.请求来了,执行 :(View类的as_view中的view,只是去掉了csrf的认证)(request)

def view(request, *args, **kwargs):
  self = cls(**initkwargs)
  return self.dispatch(request, *args, **kwargs)  

ps: 1.返回的结果是:self.dispatch(request, *args, **kwargs) :此时的self是BookView产生的对象,其中没有dispatch方法-->BookView类中也没有其中没有dispatch方法--->父类APIView中有dispatch方法
     

3.执行父类APIView中有dispatch方法

    def dispatch(self, request, *args, **kwargs):
        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
      
	ps:
    1.request = self.initialize_request(request, *args, **kwargs):左边的request是新的request(rest_framework.request.Request),右边的request是老的request
    2.self.initial(request, *args, **kwargs):执行了认证,权限和频率

作业

写一个装饰器,装饰在fbv上,这个fbv可以接受前端的编码格式可以是urlencoded,form-data,json,取数据,都是从request.data中取

def outer(fun_name):
    def inner(request,*args,**kwargs):
        if request.method == "GET":
            request.data = request.GET
        elif request.method == 'POST':
            content_type = request.content_type
            if content_type == 'application/json':
                request.data = request.body
            elif content_type == 'multipart/form-data':
                request.data = request.POST.copy()
                request.data.update(request.FILES)
            elif content_type == 'application/x-www-form-urlencoded':
                request.data = request.POST
        else:
            request.data = request.body
        res = fun_name(request,*args,**kwargs)
        return res
    return inner
  
  
# chatgbt:
from functools import wraps
from django.http import HttpResponseBadRequest
import json

def handle_request_data(view_func):
    @wraps(view_func)
    def wrapper(request, *args, **kwargs):
        content_type = request.content_type
        if content_type == 'application/x-www-form-urlencoded' or content_type == 'multipart/form-data':
            # 处理表单数据
            request.data = request.POST.copy()
            request.data.update(request.FILES)
        elif content_type == 'application/json':
            # 处理JSON数据
            try:
                request.data = json.loads(request.body)
            except json.JSONDecodeError:
                return HttpResponseBadRequest("Invalid JSON data.")
        else:
            return HttpResponseBadRequest("Unsupported content type.")
        return view_func(request, *args, **kwargs)
    return wrapper

写一个装饰器,装饰在cbv上,这个cbv可以接受前端的编码格式可以是urlencoded,form-data,json,取数据,都是从request.data中取

from django.shortcuts import render
from django.http import JsonResponse
# Create your views here.
from django.views import View
from .models import BookViews
import json
from django.utils.decorators import method_decorator


def outer(fun_name):
    def inner(request,*args,**kwargs):
        if request.method == "GET":
            request.data = request.GET
        elif request.method == 'POST':
            content_type = request.content_type
            if content_type == 'application/json':
                request.data = request.body
            elif content_type == 'multipart/form-data':
                request.data = request.POST.copy()
                request.data.update(request.FILES)
            elif content_type == 'application/x-www-form-urlencoded':
                request.data = request.POST
        else:
            request.data = request.body
        res = fun_name(request,*args,**kwargs)
        return res
    return inner




class Book(View):
    @method_decorator(outer)   # 类里面所有的方法都加了装饰器
    def dispatch(self, request, *args, **kwargs):
        return super(Book, self).dispatch(request, *args, **kwargs)

    # 需求:查所有的图书
    def get(self,request):
        # 1.数据格式
        back_dic = {'code':200,'msg':'查看成功'}

        # 2.查看数据
        data = []
        books_obj = BookViews.objects.all()
        for obj in books_obj:
            dic = {'name':obj.name,'price':obj.price}
            data.append(dic)
        back_dic['data'] = data
        return JsonResponse(back_dic)

    # 需求增加一本书
    def post(self,request):
        # 1.数据格式
        back_dic = {'code':200,'msg':'添加成功'}

        # 2.接收数据
        print(request.data)  # <QueryDict: {'name': ['三国演义'], 'price': ['100']}>
        name = request.data.get('name')
        price = request.data.get('price')

        # 3.参数验证
        if not (name and price):
            back_dic['code'] = 1001
            back_dic['msg'] = '名字或价格不能为空'
            return JsonResponse(back_dic)
        # 添加数据
        BookViews.objects.create(name = name,price = price)
        back_dic['data'] = {'name':name,'price':price}
        return JsonResponse(back_dic)

class BookDetail(View):
    @method_decorator(outer)   # 类里面所有的方法都加了装饰器
    def dispatch(self, request, *args, **kwargs):
        return super(BookDetail, self).dispatch(request, *args, **kwargs)
    # 需求:查看某一本书  eg:id为2
    def get(self,request,pk):
        # 1.数据格式
        back_dic = {'code':200,'msg':'获取成功'}
        book_obj = BookViews.objects.filter(pk=pk).first()
        if not book_obj:
            back_dic['code'] = 1002
            back_dic['msg'] = '这本书不存在'
            return JsonResponse(back_dic)

        # 2.读取数据
        back_dic['data'] = {'name':book_obj.name,'price':book_obj.price}

        return JsonResponse(back_dic)

    # 需求:修改数据    eg:id 将id为2的数据修改
    def put(self,request,pk):

        # 1.数据格式
        back_dic = {'code':200,'msg':'修改成功'}

        book_obj = BookViews.objects.filter(pk=pk).first()
        if not book_obj:
            back_dic['code'] = 1002
            back_dic['msg'] = '这本书不存在'
            return JsonResponse(back_dic)

        # 2.获取数据
        print(request.data)  # b'{"name":"hongloumeng","price":"99"}'
        dic =  json.loads(request.data)
        name = dic.get('name')
        print(name)
        price = dic.get('price')
        print(price)

        # 3.参数验证
        if not (name and price):
            back_dic['code'] = 1001
            back_dic['msg'] = '名字或价格不能为空'
            return JsonResponse(back_dic)
        # 4.修改
        BookViews.objects.filter(pk=pk).update(**dic)
        back_dic['data'] = {'name':name,'price':price}
        return JsonResponse(back_dic)

    # 需求:删除谋一条数据
    def delete(self,request,pk):
        # 1.数据格式
        back_dic = {'code':200,'msg':'删除成功'}

        # 删除
        book_obj = BookViews.objects.filter(pk=pk).first()
        if not book_obj:
            back_dic['code'] = 1002
            back_dic['msg'] = '这本书不存在'
            return JsonResponse(back_dic)
        book_obj.delete()
        return JsonResponse(back_dic)