drf视图类 视图基类、视图扩展类、视图子类、视图集以及路由类

drf视图类 视图基类、视图扩展类、视图子类、视图集

image

两个视图基类

APIView

这个视图类在前文已经介绍过了,web常见5个接口--APIView的最后有提到:

APIView也是继承了django的原生视图类View,而APIView做了更多的工作来方便我们编写视图函数。以下为查询接口的一段代码,利用率drf的APIView和Response

而这个视图类也是drf框架提供给我们的视图基类之一,它相对于原生的django视图类来说有以下特点:

  • 执行流程:新的reqeust,三大认证,全局异常

  • 重写了as_view,dispatch

  • 类属性:

    parser_classes 请求格式(有三个)

    renderer_classes 响应格式(有两个)

    authentication_classes 认证类 throttle_classes 频率类 permission_classes 权限类

GenericAPIView

GenericAPIView又继承了APIView,而它的特点就在于与数据库打交道比较方便。

我们对比两个APIView的子类(视图类),发现如果都是一样的接口时,内部程序的差异就只有模型表和序列化类不同。如下:

# 书籍视图类
class BookView(APIView):
    def get(self, request):
        queryset = Book.objects.all()
        ser = BookSerializer(instance=queryset, many=True)
        return Response(ser.data)
        
# 出版社视图类
class PublishView(APIView):
    def get(self, request):
        queryset = Publish.objects.all()
        ser = PublishSerializer(instance=queryset, many=True)
        return Response(ser.data)

所以这样冗余的代码显然我们可以对其进行优化,drf也为我们提供了GenericAPIView类。

继承了这个类产生的子类也是drf视图基类之一,它代表的是常用的接口视图类,我们需要提供两个重要属性给GenericAPIView类:

  • queryset:这个参数就是模型表查询语句,(注意orm是惰性的,诸如Book.objects.all()的这类语句还没有实际去数据库)
  • serializer_class:这个参数就是对应的序列化类

以上两个参数属性是必要的,还有一些其他可支配的参数,用以下形式配置到类中:

class BookView(GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    lookup_field = 'pk'   # 当路由分组是detail类的时候分出的字段名,默认pk
    filter_backends = '过滤类的配置(了解)'
    pagination_class = '分页类的配置(了解)'

而GenericAPIView内部也书写了一下几个方法:

  • get_queryset:获取序列化对象queryset
  • get_object:获取单个对象
  • get_serialzer:获取序列化类,还有一个差不多的get_serializer_class,一般覆写

为什么采取函数去拿到queryset和序列化类属性呢,因为在有些场景中,我们可能根据不同的请求方式和接口来分配不同的queryset,用函数便于覆写和分支

采取GenericAPIView基类,我们在配置好queryset和serializer_class后,就可以搭配视图扩展类,来快速的书写get、post等请求处理函数了。

五个视图扩展类

在rest_framework.mixins中有一些类,这些类必须搭配GenericAPIView使用,因为它们本身缺少一些方法,不具备视图类的特点。这种类一般以Mixin等后缀结尾,表示配合一种类通过多继承产生子类,这里就是产生了一个视图类。

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


# 基于GenericAPIView+5个视图扩展类写接口

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

    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 = BookSerializer

    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)

self中的list、create、retrive、update、destroy就封装好了根据queryset和serializer来操作数据库的对应操作,对应查所有,新增,查单个,修改,删除。而我们对于接口的开设这个写法也是固定的,只是根据不同的接口的排列组合,我们可以排列组合得到9种视图子类。

九个视图子类

  1. 五个单接口的视图子类

    ListAPIView、CreateAPIView、RetrieveAPIView、UpdateAPIView、DestroyAPIView

  2. 三个两两组合的子类

    ListCreateAPIView -- 查所有和新增属于没有额外路由的

    RetrieveUpdateAPIView、RetrieveDestroyAPIView -- 有额外路由的两两组合,认为有查的接口才能改删

  3. 一个三接口的detail子类

    RetrieveUpdateDestroyAPIView

这样我们在编写五个接口就会像下面这样:

# 视图类
class BookView(ListCreateAPIView):  # 查询所有,新增一个
    queryset = Book.objects.all()
    serializer_class = BookSerializer

# 查询一个,修改一个,删除一个
class BookDetailView(RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

而两个类干脆合并到一块,于是drf又提供了视图集。

视图集

ModelViewSet集合五个接口

class BookView(ModelViewSet):  # 五个接口都齐了
    queryset = Book.objects.all()
    serializer_class = BookSerializer

注意通过视图集得到的视图类,我们再用原本的路由就不行了,并会抛出这样一个异常:

The `actions` argument must be provided when calling `.as_view()` on a ViewSet. For example `.as_view({'get': 'list'})`

提示我们需要按照示例一样,在as_view()函数中添加参数,所以原本路由失效的原因就是因为,视图集重写了as_view函数。

urlpatterns = [
    path('books/', views.BookView.as_view({'get': 'list', 'post': 'create'})),
    path('books/<int:pk>/', views.BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]

重写as_view位置分析

那么是在哪里重写的呢,我们查看视图集发现其继承了6个父类,排除五个视图扩展类和视图基类GenericAPIView,最终定位到ViewSetMixin中,看形式,这个类也是配合基类使用的。在其中就重写了as_view()方法,而在我们的视图类执行时,它就会到第一个父类中先寻找as_view方法,所以ViewSetMixin在继承顺序上注意要排在前面。

ViewSetMixin的as_view方法简析

# 请求来了,路由匹配成功---》get请求,匹配成功books,会执行  views.BookView.as_view({'get': 'list', 'post': 'create'})()------>读as_view【这个as_view是ViewSetMixin的as_view】

    @classonlymethod
    def as_view(cls, actions=None, **initkwargs):
        # 如果没有传actions,直接抛异常,路由写法变了后,as_view中不传字典,直接报错
        if not actions:
            raise TypeError("The `actions` argument must be provided when "
                            "calling `.as_view()` on a ViewSet. For example "
                            "`.as_view({'get': 'list'})`")
		# 。。。。其他代码不用看
        def view(request, *args, **kwargs):
            ...
            return self.dispatch(request, *args, **kwargs)
        # 去除了csrf校验
        return csrf_exempt(view)
    
    
# 路由匹配成功执行views.BookView.as_view({'get': 'list', 'post': 'create'})()----》本质执
行ViewSetMixin----》as_view----》内的view()
    def view(request, *args, **kwargs):
            #actions 是传入的字典--->{'get': 'list', 'post': 'create'}
            self.action_map = actions
            # 第一次循环:method:get,action:list
            # 第一次循环:method:post,action:create
            for method, action in actions.items():
                # 反射:去视图类中反射,action对应的方法,action第一次是list,去视图类中反射list方法
                # handler就是视图类中的list方法
                handler = getattr(self, action)
                # 反射修改:把method:get请求方法,handler:list
                # 视图类的对象的get方法,设置为list
                setattr(self, method, handler)
            # dispatch是APIView的,经历上述循环后,我们就让请求类型函数映射到了视图类中的函数
            return self.dispatch(request, *args, **kwargs) 

总结:

  1. 要求传入action参数,否则报错
  2. 返回去除csrf认证的view函数
  3. view函数中最终还是执行了APIView中的dispatch,遵循按请求类型分发
  4. 但在执行dispatch前通过对action进行反射,将请求名如get映射到视图类中本身拥有的函数(或者父类中的函数)如list。

所以

ReadOnlyModelViewSet编写两个序列化接口

ReadOnlyModelViewSet集合了查询多个和查询单个这两个接口,实际上就是直接继承了两个只读视图扩展类和含viewsetmixin的genericviewset。

# 路由
urlpatterns = [
    path('books/', views.BookView.as_view({'get': 'list'})),
    path('books/<int:pk>/', views.BookView.as_view({'get': 'retrieve'})),
]

# 视图类
class BookView(ReadOnlyModelViewSet):  # 查询所有,新增一个
    queryset = Book.objects.all()
    serializer_class = BookSerializer

rest_framework.viewsets包下的类

视图集除了两个基础视图集类:ModelViewSet和ReadOnlyModelViewSet

还有:

  • ViewSetMixin:专门用于改写as_view修改请求类型和函数的映射关系的
  • ViewSet:ViewSetMixin+ APIView
  • GenericViewSet:ViewSetMixin+ GenericAPIView

这三个就是用来改写路由的写法的。

路由类

在上述视图对应的路由中,我们一般采取:

  • path('books/', views.BookView.as_view())
  • path('books/', views.BookView.as_view({'get': 'list', 'post': 'create'}))

这两种方式都采取了原生的或者drf改写as_view方法,而在drf改写的方法中,手动书写常用的映射关系比较麻烦,所以drf又提供了路由类帮助我们生成路由。

自动生成路由

from rest_framework.routers import SimpleRouter, DefaultRouter  # 两个路由类,一般用前者
router = SimpleRouter()
router.register('book', views.BookView, 'book')  
"""
注意这里的BookView必须是继承了改写as_view的视图类,即两个视图集或者
viewset.ViewSet和viewset.GenericViewSet
"""

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include(router.urls))   # include方式可以在这层路由前再添加一些路由
]

# 也可以采取urlpatterns += router.urls的方式

路由类会自动默认的将请求按以下方式映射:

  • get--->list
  • get---->retrieve
  • put---->update
  • post---->create
  • delete---->destory

这里的get是按照什么映射成查单个和查多个的呢?

可以根据路由是否拥有pk字段来判断

action装饰器

action是配合路由类使用的,将其加装在接口函数的上方,传入一些属性,路由类便能根据这些属性生成匹配的路由映射,路由层生成路由的代码与上述一致。

class SendView(ViewSet):
    # methods指定请求方法,可以传多个
    # detail:只能传True和False
    	-False,不带id的路径:send/send_sms/
        -True,带id的路径:send/2/send_sms/
    # url_path:生成send后路径的名字,默认以方法名命名 
    # url_name:别名,反向解析使用,了解即可
    @action(methods=['POST'], detail=False)
    def send_sms(self, request):
        ...

而这种方式生成的路由,会在视图类注册的总路由(book)后再加上action中的url_path,也就是说一个视图类中可以含有路由不同的接口函数。

通过action方法和属性分发不同的序列化类

class SendView(GenericViewSet):
queryset = None
serializer_class = '序列化类'

def get_serializer(self, *args, **kwargs):
    if self.action=='hear':
        return '某个序列化类'
    else:
        return '另一个序列化列'
@action(methods=['GET'], detail=True)
def send_sms(self, request,pk):
    print(pk)
    # 手机号,从哪去,假设get请求,携带了参数
    phone = request.query_params.get('phone')
    print('发送成功,%s' % phone)
    return Response({'code': 100, 'msg': '发送成功'})

@action(methods=['GET'], detail=True)
def hear(self,request):  # get
    # 序列化类
    pass

@action(methods=['GET'], detail=True)
def login(self,request):  # get
    # 序列化类
    pass
posted @ 2023-02-06 19:11  leethon  阅读(78)  评论(0编辑  收藏  举报