【3.0】DRF之初识

发布时间 2023-07-31 12:24:29作者: Chimengmeng

【一】序列化与反序列化

  • api接口开发,最核心最常见的一个过程就是序列化

【1】序列化

  • 把我们识别的数据转换成指定的格式提供给别人。

  • 例如:

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

【2】反序列化

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

【3】小结

  • 序列化:

    • drf称为 read(读取数据)
    • 序列化
    • queryset --- > json
    • 返给前端
  • 反序列化:

    • drf称为 write(写入数据)
    • 反序列化
    • 字符串 --- > json
    • 接收前端的数据

【二】接口引入

BOOK表为例

【1】准备数据

  • models.py
from django.db import models


# Create your models here.
class Books(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField(null=True, blank=True)
  • 数据库迁移
python mange.py makemigrations

python manage.py migrate
  • 手动录入两条数据
西游记 888
水浒传 999

【2】创建接口

(1)查询所有图书

  • 接口设计

    path("books/all/", BookView.as_view()),
    
  • 功能实现

    # 查询所有数据
    class BookView(View):
    
        def get(self, request):
            back_dict = {"code": "", "msg": "", "result": []}
    
            book_list = models.Books.objects.all()
            for book in book_list:
                books = {"id": book.pk, "name": book.name, "price": book.price}
                back_dict["result"].append(books)
            back_dict["code"] = 1000
            back_dict["msg"] = "请求成功!"
    
            return JsonResponse(back_dict)
    
  • 响应数据

    • postman

      get 请求
      携带主键值 
      
    http://127.0.0.1:8000/books/all/
    
    {
        "code": 1000,
        "msg": "请求成功!",
        "result": [
            {
                "id": 1,
                "name": "西游记",
                "price": 888
            },
            {
                "id": 2,
                "name": "水浒传",
                "price": 999
            }
        ]
    }
    

(2)新增一本图书

  • 接口设计

    # 增加单本图书
    path("books/add/", BookAddView.as_view()),
    
  • 功能实现

    # 增加单条数据
    class BookAddView(View):
        def post(self, request):
            back_dict = {"code": "", "msg": "", "result": []}
            data = json.loads(request.body)
            name = data.get('name')
            price = data.get('price')
            # print(name,price)
    
            models.Books.objects.create(name=name, price=price)
    
            back_dict["code"] = 1000
            back_dict["msg"] = "添加成功!"
    
            return JsonResponse(back_dict)
    
  • 响应数据

    • postman

      post 请求 必须带参数
      {
          "name":"小红帽",
          "price":66
      }
      
    http://127.0.0.1:8000/books/add/
    
    {
        "code": 1000,
        "msg": "添加成功!",
        "result": []
    }
    

(3)修改一本图书

  • 接口设计

    # 修改单本图书
    path("books/change/", BookChangeView.as_view()),
    
  • 功能实现

    # 修改单本书籍
    class BookChangeView(View):
        def post(self, request, pk):
            data = json.loads(request.body)
            name = data.get('name')
            price = data.get('price')
            book_obj = models.Books.objects.filter(pk=pk).first()
            back_dict = {"code": "", "msg": "", "result": []}
            if book_obj:
                if name:
                    book_obj.name = name
                    back_dict["msg"] = "书籍名字成功!"
                    if price:
                        book_obj.price = price
                        back_dict["msg"] = "书籍名字和价格成功!"
                else:
                    book_obj.price = price
                    back_dict["msg"] = "书籍价格成功!"
                back_dict["code"] = 1000
                book_obj.save()
            else:
                back_dict["code"] = 1001
                back_dict["msg"] = "书籍不存在!"
            return JsonResponse(back_dict)
    
  • 响应数据

    • postman
    post 请求 三个参数都可选加
    {
        "pk":"2",
        "name":"小叮当",
        "price":"88888"
    }
    
    http://127.0.0.1:8000/books/change/
    
    {
        "code": 1000,
        "msg": "书籍名字成功!",
        "result": []
    }
    
    {
        "code": 1000,
        "msg": "书籍名字和价格成功!",
        "result": []
    }
    

(4)查询一本图书

  • 接口设计

    path("books/detail/<int:pk>/", BookDetailView.as_view()),
    
  • 功能实现

    # 查询单条 数据
    class BookDetailView(View):
        def get(self, request, pk):
            back_dict = {"code": "", "msg": "", "result": []}
            book_detail = models.Books.objects.filter(pk=pk).first()
    
            books = {"id": book_detail.pk, "name": book_detail.name, "price": book_detail.price}
    
            back_dict["code"] = 1000
            back_dict["msg"] = "请求成功!"
            back_dict["result"].append(books)
    
            return JsonResponse(back_dict)
    
  • 响应数据

    • postman

      • 携带参数:书籍主键值ID
      get 请求
      携带主键值 必须携带
      
    http://127.0.0.1:8000/books/detail/1/
    
    {
        "code": 1000,
        "msg": "请求成功!",
        "result": [
            {
                "id": 1,
                "name": "西游记",
                "price": 888
            }
        ]
    }
    

(5)删除一本图书

  • 接口设计

    # 查询单本图书
    path("books/detail/<int:pk>/", BookDetailView.as_view()),
    
  • 功能实现

    # 删除单挑数据
    class BookDeleteView(View):
        def get(self, request, pk):
            back_dict = {"code": "", "msg": "", "result": []}
            book_obj = models.Books.objects.filter(pk=pk).first()
            if book_obj:
                book_obj.delete()
                back_dict["code"] = 1000
                back_dict["msg"] = "删除成功!"
            else:
                back_dict["code"] = 1001
                back_dict["msg"] = "书籍不存在!"
            return JsonResponse(back_dict)
    
  • 响应数据

    • postman

      get 请求
      携带主键值
      
    http://127.0.0.1:8000/books/delete/1/
    
    {
        "code": 1000,
        "msg": "请求成功!",
        "result": [
            {
                "id": 1,
                "name": "西游记",
                "price": 888
            },
            {
                "id": 2,
                "name": "水浒传",
                "price": 999
            }
        ]
    }
    

【3】小结

  • 模型层
from django.db import models


# Create your models here.
class Books(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField(null=True, blank=True)
  • 路由层
"""day02 URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from app01.views import BookView, BookDetailView, BookChangeView, BookAddView, BookDeleteView

urlpatterns = [
    path('admin/', admin.site.urls),
    # 查询全部图书
    path("books/all/", BookView.as_view()),
    # 查询单本图书
    path("books/detail/<int:pk>/", BookDetailView.as_view()),
    # 删除单本图书
    path("books/delete/<int:pk>/", BookDeleteView.as_view()),
    # 增加单本图书
    path("books/add/", BookAddView.as_view()),
    # 修改单本图书
    path("books/change/", BookChangeView.as_view()),
]
  • 视图层
import json

from django.http import JsonResponse
from django.shortcuts import render, HttpResponse
from django.views import View

from . import models
from .models import Books


# Create your views here.
# 查询所有数据
class BookView(View):

    def get(self, request):
        back_dict = {"code": "", "msg": "", "result": []}

        book_list = models.Books.objects.all()
        for book in book_list:
            books = {"id": book.pk, "name": book.name, "price": book.price}
            back_dict["result"].append(books)
        back_dict["code"] = 1000
        back_dict["msg"] = "请求成功!"

        return JsonResponse(back_dict)


# 查询单条 数据
class BookDetailView(View):
    def get(self, request, pk):
        back_dict = {"code": "", "msg": "", "result": []}
        book_detail = models.Books.objects.filter(pk=pk).first()

        books = {"id": book_detail.pk, "name": book_detail.name, "price": book_detail.price}

        back_dict["code"] = 1000
        back_dict["msg"] = "请求成功!"
        back_dict["result"].append(books)

        return JsonResponse(back_dict)


# 删除单挑数据
class BookDeleteView(View):
    def get(self, request, pk):
        back_dict = {"code": "", "msg": "", "result": []}
        book_obj = models.Books.objects.filter(pk=pk).first()
        if book_obj:
            book_obj.delete()
            back_dict["code"] = 1000
            back_dict["msg"] = "删除成功!"
        else:
            back_dict["code"] = 1001
            back_dict["msg"] = "书籍不存在!"
        return JsonResponse(back_dict)


# 增加单条数据
class BookAddView(View):
    def post(self, request):
        back_dict = {"code": "", "msg": "", "result": []}
        data = json.loads(request.body)
        name = data.get('name')
        price = data.get('price')
        # print(name,price)

        models.Books.objects.create(name=name, price=price)

        back_dict["code"] = 1000
        back_dict["msg"] = "添加成功!"

        return JsonResponse(back_dict)


# 修改单本书籍
class BookChangeView(View):
    def post(self, request, pk):
        data = json.loads(request.body)
        pk = data.get('pk')
        name = data.get('name')
        price = data.get('price')
        book_obj = models.Books.objects.filter(pk=pk).first()
        back_dict = {"code": "", "msg": "", "result": []}
        if book_obj:
            if name:
                book_obj.name = name
                back_dict["msg"] = "书籍名字成功!"
                if price:
                    book_obj.price = price
                    back_dict["msg"] = "书籍名字和价格成功!"
            else:
                book_obj.price = price
                back_dict["msg"] = "书籍价格成功!"
            back_dict["code"] = 1000
            book_obj.save()
        else:
            back_dict["code"] = 1001
            back_dict["msg"] = "书籍不存在!"
        return JsonResponse(back_dict)

【三】DRF介绍与快速使用

  • DRF(Django REST Framework)是一个强大且灵活的开发工具包,用于构建基于Django的Web API。
  • 它提供了许多内置的功能和工具,使得编写高质量的API变得更加容易和高效。

【1】使用DRF进行快速开发的简要步骤:

(1)安装DRF:

  • 使用pip包管理器,在终端中运行以下命令来安装DRF:
pip install djangorestframework

(2)配置DRF:

  • 在你的Django项目的settings.py文件中,确保将DRF添加到INSTALLED_APPS列表中:
INSTALLED_APPS = [
    ...
    'rest_framework',
    ...
]

(3)创建序列化器(Serializer):

  • 序列化器是DRF中一个重要的概念,它将Python对象转换为JSON等可被传输的格式,并可以反序列化接收到的数据。
  • 在你的应用程序中创建一个名为serializers.py的文件,并定义你的序列化器类。
  • 一个示例:
from rest_framework import serializers

class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = '__all__'
  • 在这个示例中,我们使用ModelSerializer来自动创建序列化器类。

(4)创建视图(View):

  • 在你的应用程序中创建一个名为views.py的文件,并定义视图类。
  • 一个示例:
from rest_framework import generics
from .serializers import MyModelSerializer
from .models import MyModel

class MyModelListView(generics.ListCreateAPIView):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
  • 在这个示例中,我们使用ListCreateAPIView来创建一个支持列表和创建操作的通用视图。

(5)配置URL路由:

  • 在你的应用程序的urls.py文件中,定义DRF的URL路由。
  • 一个示例:
from django.urls import path
from .views import MyModelListView

urlpatterns = [
    path('mymodels/', MyModelListView.as_view(), name='mymodel-list'),
]
  • 通过上述配置,当访问/mymodels/时,将会调用MyModelListView视图。
  • 以上是使用DRF进行快速开发的简要步骤。
  • 当然,DRF还提供了许多其他功能,如认证、权限控制、过滤器、分页等,你可以根据自己的需求进一步学习和定制。
  • 你可以参考官方文档以获得更详细的信息:

【2】快速使用

(1)路由

from app01.views import BookView
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('books', BookView, 'books')
urlpatterns = [
]
urlpatterns += router.urls

(2)视图

from .serializer import BookSerializer
from rest_framework.viewsets import ModelViewSet
class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

(3)序列化类

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

【补充】小问题

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

【补充】下载兼容性问题

  • 安装命令
pip3.8 install djangorestframework==稍微将版本
  • 如果你是django2

    • 直接这样装,装最新drf,他们不匹配
    • ---》pip会自动把django卸载,安装最新django,安装最新drf
  • django3 ,这样没有任何问题

  • 强制更新

    pip3.8 install djangorestframework --upgrade
    

【补充】包扩展

  • 如果写了一个包,或app,想给别人用
  • ---》把你写的包,放到pypi上别人pip install
  • 安装---》使用

【四】DRF之APIView源码分析

【1】基于APIView的5个接口

(1)视图类

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


class BookView(APIView):
    # 查询所有
    def get(self, request):
        book_list = Book.objects.all()
        # drf提供了序列化类(先别关注)
        ser = BookSerializer(instance=book_list, many=True)  # 序列化
        return Response({'code': 100, 'msg': '成功', 'result': ser.data})

    def post(self, request):
        ser = BookSerializer(data=request.data)  # 反序列化
        if ser.is_valid():  # 数据校验---》有些不合法的禁止
            ser.save()  # 保存到数据库中
        return Response({'code': 100, 'msg': '成功'})


class BookDetailView(APIView):
    # 查询单条
    def get(self, request, pk):
        book = Book.objects.filter(pk=pk).first()
        ser = BookSerializer(instance=book, many=False)  # 序列化
        return Response({'code': 100, 'msg': '成功', 'result': ser.data})

    # 修改一条
    def put(self, request, pk):
        book = Book.objects.filter(pk=pk).first()
        ser = BookSerializer(instance=book, data=request.data)  # 反序列化
        if ser.is_valid():  # 数据校验---》有些不合法的禁止
            ser.save()  # 保存到数据库中
        return Response({'code': 100, 'msg': '成功'})

    def delete(self, request, pk):
        Book.objects.filter(pk=pk).delete()
        return Response({'code': 100, 'msg': '删除成功'})

(2)序列化类

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

(3)路由

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

【2】CBV源码分析

(1)cbv写法:

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

(2)前置条件

  • 前端请求,一旦路径匹配成功,就会执行

    • 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的执行流程一样

(3)最终结论

  • 什么请求方式,就会执行视图类中的什么方法

【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 是新的request,      request是老的request
    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

【4】总结

  • 以后只要继承APIView的所有视图类的方法,都没有csrf的校验了

  • 以后只要继承APIView的所有视图类的方法 中的request是新的request了

  • 在执行视图类的方法之前,执行了三大认证(认证,权限,频率)

  • 期间除了各种错误,都会被异常捕获,统一处理

【补充】装饰器语法糖

fbv,局部禁用csrf,如何写?

@csrf_exempt
def index(request):
    pass
  • 本质原理是(装饰器本质):
    • index=csrf_exempt(index)

【补充】FBV装饰器

  • 写一个装饰器,装饰在fbv上
  • 这个fbv可以接受前端的编码格式可以是urlencoded,form-data,json
  • 取数据,都是从request.data中取
  • 您可以使用Python编写一个装饰器函数,将其应用于使用函数基础视图(FBV)的端点。
  • 这个装饰器可以处理不同的编码格式(urlencoded、form-data、json),并从request.data中提取数据。

下面是一个示例实现:

from functools import wraps
from django.http import QueryDict
import json

def parse_request_data(view_func):
    @wraps(view_func)
    def wrapper(request, *args, **kwargs):
        if request.content_type == 'application/x-www-form-urlencoded':
            # 处理urlencoded编码格式
            data = request.POST
        elif request.content_type.startswith('multipart/form-data'):
            # 处理form-data编码格式
            data = request.FILES
        elif request.content_type == 'application/json':
            # 处理json编码格式
            try:
                data = json.loads(request.body)
            except json.JSONDecodeError:
                data = {}
        else:
            # 其他编码格式处理(可根据实际情况进行扩展)
            data = {}

        request.data = data
        return view_func(request, *args, **kwargs)

    return wrapper
  • 使用这个装饰器,您可以将其应用于需要处理不同编码格式数据的FBV上,例如:
@parse_request_data
def your_view(request):
    # 在这里使用request.data来访问解析后的数据
    # 例如,如果是urlencoded编码格式,可以通过request.data['key']获取值
    
    return HttpResponse("response")

  • 在上述示例中
    • parse_request_data装饰器会根据请求的content_type来解析不同的编码格式
    • 并将解析后的数据存储在request.data中,以供视图函数使用。