drf视图层

介绍

本章主要讲解了视图层常见的一些视图类

  1. 两个视图基类
    • APIView和GenericAPIView
  2. 5个视图扩展类
    • ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestroyModelMixin
  3. 9个视图子类
    • ListAPIView、CreateAPIView、ListCreateAPIView、RetrieveAPIView、DestroyAPIView、UpdateAPIView、RetrieveUpdateAPIView、RetrieveDestroyAPIView、RetrieveUpdateDestroyAPIView
  4. 视图集(viewsets)
    • ModelViewSet、ReadOnlyModelViewSet、ViewSetMixin、ViewSet、GenericViewSet

环境准备

urls.py

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

models.py

from django.db import models


# Create your models here.
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.CharField(max_length=32)
    # 图书与出版社外键字段,一对多
    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
    # 图书与作者外键字段,多对多
    authors = models.ManyToManyField(to='Author')

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

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


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


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

serializer.py

class BookModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        fields = ['name', 'price', 'publish', 'authors', 'publish_detail', 'author_detail']
        read_only_fields = ['publish_detail', 'author_detail']
        extra_kwargs = {
            'publish': {'write_only': True},
            'authors': {'write_only': True}
        }

    name = serializers.CharField(max_length=8, error_messages={'max_length': '不能超过8个字符'})
    price = serializers.CharField(max_length=8)

基于APIView的增删改查五个接口

from app01 import models
from app01.serializer.serializer import BookModelSerializer
from rest_framework.views import APIView
from rest_framework.response import Response


class BookView(APIView):
    # 查询所有
    def get(self, request):
        book_obj = models.Book.objects.all()
        book_ser = BookModelSerializer(instance=book_obj, many=True)
        return Response(book_ser.data)

    # 新增一条数据
    def post(self, request):
        book_ser = BookModelSerializer(data=request.data)
        if book_ser.is_valid():
            book_ser.save()
            return Response({'code': 200, 'msg': '新增成功', 'result': book_ser.data})
        else:
            return Response({'code': 201, 'msg': book_ser.errors})


class BookDetailView(APIView):
    # 查询一条数据
    def get(self, request, pk):
        book_obj = models.Book.objects.filter(pk=pk).first()
        book_ser = BookModelSerializer(instance=book_obj)
        return Response(book_ser.data)

    # 修改一条数据
    def put(self, request, pk):
        book_obj = models.Book.objects.filter(pk=pk).first()
        book_ser = BookModelSerializer(data=request.data, instance=book_obj)
        if book_ser.is_valid():
            book_ser.save()
            return Response({'code': '200', 'msg': '修改成功', 'result': book_ser.data})
        else:
            return Response({'code': '201', 'msg': book_ser.errors})

    # 删除一条数据
    def delete(self, request, pk):
        models.Book.objects.filter(pk=pk).delete()
        return Response({''})

如果需要写多个表的接口,可以看到需要写非常多重复的代码,那么为了节省代码量,drf封装了一些方法

第一步封装-GenericAPIView

GenericAPIView中使用的一些方法

属性 说明
queryset 指定要序列化或反序列化的表模型数据
serializer_class 指定使用的序列化类
lookup_field 查询单条时指定传入的主键值的Key是什么,因为默认是PK,但路由中如果传入的键为ID时,则需要配置此变量为ID
filter_backends 过虑类的配置
pagination_class 分页类的配置
方法 说明
get_queryset 获取要序列化的对象
get_object 获取单个对象使用
get_serializer 获取序列化类,而get_serializer_class方法,是在有需要的时候进行重新def定义使用的
filter_queryset 过滤相关

使用GenericAPIView写的代码,将序列化类与表的关系写在的类的最前面,供下面的增删改查来使用,这时候,如果写多个表的接口,则只需要修改queryset和serializer_class就可以了,但代码并未节省太多。

from app01 import models
from app01.serializer.serializer import BookModelSerializer
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView


class BookView(GenericAPIView):
    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer

    def get(self, request):
        obj = self.get_queryset()
        ser_obj = self.get_serializer(instance=obj, many=True)
        return Response({'code': 200, 'msg': '查询成功', 'result': ser_obj.data})

    def post(self, request):
        obj = self.get_serializer(data=request.data)
        if obj.is_valid():
            obj.save()
            return Response({'code': 200, 'msg': '新增成功', 'result': obj.data})


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

    def get(self, request, pk):
        # get_object()实际上是通过get_queryset()方法获得一个QuerySet对象,然后通过该QuerySet对象调用filter()方法来获取符合条件的数据,最终获得一条数据并返回。
        obj = self.get_object()
        ser_obj = self.get_serializer(instance=obj)
        return Response({'code': 200, 'msg': '查询成功', 'result': ser_obj.data})

    def put(self, request, pk):
        obj = self.get_object()
        ser_obj = self.get_serializer(data=request.data, instance=obj)
        if ser_obj.is_valid():
            ser_obj.save()
            return Response({'code': 200, 'msg': '修改', 'result': ser_obj.data})
        else:
            return Response({'code': '201', 'msg': ser_obj.errors})

    def delete(self, request, pk):
        self.get_object().delete()
        return Response({''})

现在的问题是,每个都包含增删改查四个方法,那这四个方法是不是可以封装一下?

第二步封装-五个视图扩展类

使用了GenericAPIView后,发现每个视图类都有重复的get(所有数据)/get(单条数据)/post/put/delete五个方法,那么这五个方法是否可以使用class封装起来,这样在每个类使用的时候继承这五个封装后的类就可以了,这样就节省了写这五个方法的代码量了!

所以drf提供了五个视图扩展类,搭配GenericAPIView视图类使用,因为是搭配使用,所以在继承时需要继承两个类,不能单独使用扩展类。(相像一下,存储和存储扩展柜的关系)

名称 说明 方法
ListModelMixin 查询所有数据 list
RetrieveModelMixin 查询单条数据 retrieve
CreateModelMixin 新增一条数据 create
UpdateModelMixin 修改一条数据 update
DestroyModelMixin 删除一条数据 destroy
  • 下面基于五个视图扩展类写五个接口
from app01 import models
from app01.serializer.serializer import BookModelSerializer
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin, \
    ListModelMixin


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

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

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


class BookDetailView(GenericAPIView, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin):
    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer
    # 这里因为前端要传入pk,所以这里直接使用*args接收,这前前端不管传来的键是PK还是ID还是别的都可以接收
    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)

可以看到,代码的简洁程度更高了,但是,还是有很多重复代码,如:

获取所有与新增一条中的return内容是相同的。

获取单条、修改、删除中的return内容是相同的

是不是可以继续封装呢?

第三步封装-九个视图子类

九个视图子类封装了get/put/delete/create这些方法,所以在使用的时候代码会进一步减少

视图子类 说明
ListAPIView 查询所有数据
CreateAPIView 新增一条数据
ListCreateAPIView 查询所有数据+新增一条数据
RetrieveAPIView 查询一条数据
DestroyAPIView 删除数据
UpdateAPIView 修改数据
RetrieveUpdateAPIView 查询一条数据+修改数据
RetrieveDestroyAPIView 查询一条数据+删除数据
RetrieveUpdateDestroyAPIView 查询一条数据+修改数据+删除数据
  • 使用九个视图子类写5个接口
from app01 import models
from app01.serializer.serializer import BookModelSerializer
from rest_framework.generics import ListAPIView, CreateAPIView, ListCreateAPIView, RetrieveAPIView, DestroyAPIView, UpdateAPIView, RetrieveUpdateAPIView, RetrieveDestroyAPIView, RetrieveUpdateDestroyAPIView


class BookView(ListCreateAPIView):
    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer


class BookDetailView(RetrieveUpdateDestroyAPIView):
    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer

现在已经简化成了两个视图类,但是现在还有一个方法可以做成一个视图类,那就是使用ModelViewSet

第四步封装-viewsets

viewsets下包含的几个方法

名称 关系 说明
ModelViewSet 继承5个视图扩展类加ViewSetMixin加GenericAPIView 与GenericViewSet不同的是,它提供了一些通用的视图方法,不可以自定义get/put等映射关系,而GenericViewSet则可以。可以参照下面的ModelViewSet中的对应put/get等关系对应表
ReadOnlyModelViewSet 继承2个视图扩展类加ViewSetMixin加GenericAPIView 与ModelViewSet相同,但只提供查询接口
ViewSetMixin 重写了as_view方法,只要继承它,就需要改变路由的写法,对应映射关系
ViewSet 继承了ViewSetMixin和APIView 它继承的两个类都未定义与数据库交互的内容,如果未来的接口不需要与数据库交互,就继承这个类
GenericViewSet 继承了ViewSetMixin和GenericAPIView 由于GenericAPIView定义了与数据库交互的方法,所以未来如果要跟数据库作交互就继承这个类

ModelViewSet

使用ModelViewSet必须在urls.py中定义方法的对应关系,否则会报错。这是因为ModelViewSet改写了五个方法。

  • 语法如下:
path('book/', views.BookView.as_view({'get': 'list', 'post': 'create'})),
path('book/<int:pk>/', views.BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
  • 对应关系如下
名称 对应 说明
get list 查询所有数据
post create 新增一条数据
get retrieve 查询一条数据
put update 修改一条数据
delete destroy 删除一条数据
  • 使用ModelViewSet编写五个接口
urlpatterns = [
    path('admin/', admin.site.urls),
    path('book/', views.BookView.as_view({'get': 'list', 'post': 'create'})),
    path('book/<int:pk>/', views.BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]

from app01 import models
from app01.serializer.serializer import BookModelSerializer
from rest_framework.viewsets import ModelViewSet


class BookView(ModelViewSet):
    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer

可以看到,一个类就解决了

ReadOnlyModelViewSet

ReadOnlyModelViewSet只包含了读取一条和读取所有数据两个功能,所以在路由中只能指定这两个功能,如下

urlpatterns = [
    path('admin/', admin.site.urls),
    # 因为ReadOnly只对应两个读方法,所以在路由里面只有两个只读的路由对应关系
    path('book/', views.BookView.as_view({'get': 'list'})),
    path('book/<int:pk>/', views.BookView.as_view({'get': 'retrieve'})),
]

as_view解析

ModelViewSet下的GenericViewSet下的ViewSetMixin中,找到as_view的源码,在as_view中找到了view方法,在此方法中改写了as_view。
所以,只要继承了ViewSetMixin类,路由写法就与传统写法不一样了,需要传入对应函数的字典
image

自定义增删改查名称

因为as_view被改写了,所以我们也可以自定义as_view中的字典的映射关系,只要继承了ViewSetMixin后,在urls.py中定义好字典即可。

urlpatterns = [
    # 定义映射关系,比如将get定义为login
    path('test/', views.Test.as_view({'get': 'login'}))
]


from rest_framework.viewsets import ModelViewSet,ViewSetMixin
from rest_framework.views import APIView
from rest_framework.response import Response
class Test(ViewSetMixin, APIView):
    def login(self, request):
        # 我是登录接口的逻辑
        return Response('我是登录接口')

部分关系图

image
其它关系见下表

名称 关系 说明
ModelViewSet 继承5个视图扩展类加ViewSetMixin加GenericAPIView 与GenericViewSet不同的是,它提供了一些通用的视图方法,不可以自定义get/put等映射关系,而GenericViewSet则可以。可以参照下面的ModelViewSet中的对应put/get等关系对应表
ReadOnlyModelViewSet 继承2个视图扩展类加ViewSetMixin加GenericAPIView 与ModelViewSet相同,但只提供查询接口
ViewSetMixin 重写了as_view方法,只要继承它,就需要改变路由的写法,对应映射关系
ViewSet 继承了ViewSetMixin和APIView 它继承的两个类都未定义与数据库交互的内容,如果未来的接口不需要与数据库交互,就继承这个类
GenericViewSet 继承了ViewSetMixin和GenericAPIView 由于GenericAPIView定义了与数据库交互的方法,所以未来如果要跟数据库作交互就继承这个类
posted @   树苗叶子  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类
点击右上角即可分享
微信分享提示