drf 视图组件

内容概要

  • request 对象和 response 对象
  • GenericAPIView 介绍
  • 基于 GenericAPIView 的 5个视图扩展类
  • GenericAPIView 的9个视图子类
  • 视图集
  • ModelViewSet 的使用 ViewSetMixin 源码分析

内容详细

request 对象和 response 对象

请求之 request 对象

我们已经知道,drf 对原生的 request 请求数据对象做过封装处理,原生 request 对象被保存在_request 中,通过 __getattr__【点拦截】魔法方法可以获取 原生 request 对象所有方法和属性,并且 drf 的 request 可以通过 request.data 取出包括 json 格式数据在内的所有编码格式,使得数据处理更加方便。

导入模块 Request

from rest_framework.request import Request

request 对象需要记忆:

  • __getattr__
  • request.data
  • request.query_parmas

request.query_parmas 源码:接受原生 request 对象的GET 请求的数据,也就是过滤参数

@property
def query_params(self):
    """
    More semantically correct name for request.GET.
    """
    return self._request.GET

限制 request 能接收的数据格式

局部配置:

在视图层的视图类中配置,限制 request 对象只能处理json格式数据,只针对当前视图类有效

from rest_framework.parsers import JSONParser,FormParser,MultiPartParser

class PublishView(APIView):
    # 局部使用,只针对当前视图类有效,只想处理json格式
    parser_classes = [JSONParser]
    
    def get(self, request):
		pass

全局配置:

在项目文件夹下的 settings.py文件中配置

REST_FRAMEWORK = {
    "DEFAULT_PARSER_CLASSES": [
        "rest_framework.parsers.JSONParser",
    ],
}

解析顺序:
视图层类中的配置 > 项目文件夹下 settings.py 文件配置 > drf 原有的默认配置

drf的默认配置:from rest_framework import settings

image

响应之 response 对象

from rest_framework.response import Response

形参:

data=None,    # 字符串,字典,列表--》给http响应body体中内容-->response对象中取出处理
status=None,  # 响应状态码:1xx,2xx,3xx,默认是200
from rest_framework.status import HTTP_201_CREATED
Response(ser.data,status=HTTP_201_CREATED)

headers=None,      # 响应头 字典

---了解---
template_name=None,  # 模板名字(不用),用浏览器访问时,可以改
exception=False,    # 异常处理
content_type=None   # 响应编码格式

设置响应的数据格式:

局部设置:

from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
class BookDetailView(APIView):
    renderer_classes = [JSONRenderer,]

全局设置:

REST_FRAMEWORK = {
    "DEFAULT_RENDERER_CLASSES": (  # 默认响应渲染类
        "rest_framework.renderers.JSONRenderer",  # json渲染器
        "rest_framework.renderers.BrowsableAPIRenderer",  # 浏览API渲染器
    )
}

GenericAPIView 介绍

GenericAPIView 继承的是 APIView

前面我们已经知道 APIView 对原生 request 对象进行了处理,新的request对象多了 .data 属性,并且有三大认证和全局异常处理。

APIView还拥有限制请求和响应数据的属性 renderer_classes,parser_classes

那么,GenericAPIView 就是在 APIView 的基础上,多了更多属性和方法

from rest_framework.generics import GenericAPIView

更多属性和方法可以查看 GenericAPIView 源码

属性:

class GenericAPIView(views.APIView):
    queryset = None
    serializer_class = None
    lookup_field = "pk"
    lookup_url_kwarg = None
    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS

方法:

image

优先记忆:

属性

  • queryset = None : 获取模型类对象
  • serializer_class = None : 获取序列化类的属性
  • lookup_field = "pk" : 获取单个对象时需要用到改属性

方法:

  • get_queryset :获取模型类对象 queryset
  • get_object : 获取单个模型类对象
  • get_serializer :获取序列化类执行并返回一个序列化后的对象
  • get_serializer_class : 获取序列化类
get_queryset
    def get_queryset(self):
        # 断言 self.queryset 不为空,否则报错后面信息,说明 queryset 属性必须定义
        assert self.queryset is not None, (
            ""%s" should either include a `queryset` attribute, "
            "or override the `get_queryset()` method."
            % self.__class__.__name__
        )
        # 获取 queryset 属性并放回
        queryset = self.queryset
        if isinstance(queryset, QuerySet):
            queryset = queryset.all()
        return queryset

get_serializer
    def get_serializer(self, *args, **kwargs):
        serializer_class = self.get_serializer_class()
        kwargs.setdefault("context", self.get_serializer_context())
        return serializer_class(*args, **kwargs)

get_serializer_class
    def get_serializer_class(self):
        assert self.serializer_class is not None, (
            ""%s" should either include a `serializer_class` attribute, "
            "or override the `get_serializer_class()` method."
            % self.__class__.__name__
        )
        return self.serializer_class
    
get_object
    def get_object(self):
        queryset = self.filter_queryset(self.get_queryset())
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
        assert lookup_url_kwarg in self.kwargs, (
            "Expected view %s to be called with a URL keyword argument "
            "named "%s". Fix your URL conf, or set the `.lookup_field` "
            "attribute on the view correctly." %
            (self.__class__.__name__, lookup_url_kwarg)
        )
        filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
        obj = get_object_or_404(queryset, **filter_kwargs)
        self.check_object_permissions(self.request, obj)
        return obj

基于 GenericAPIView 的 5个视图扩展类

不是视图类,没有继承APIView,需要配合GenericAPIView 使用,这五个类中提供了查找、新增、修改删除模型类数据的方法,在GenericAPIView类的五个接口中可以直接调用

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

使用 APIView 的视图层类:

class BookAPIView(APIView):
    def get(self, request):
        book_list = models.Book.objects.all()
        ser = BookModelSerializer(instance=book_list, many=True)  # 获取多个资源用 many=True
        print(ser.data)
        return Response(ser.data)

    def post(self, request):
        ser = BookModelSerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response({"code": 100, "msg": "新增成功", "data": ser.data})
        return Response({"code": 101, "msg": "新增失败", "data": ser.errors})


class BookSetAPIView(APIView):
    def get(self, request, pk):
        book_obj = models.Book.objects.filter(pk=pk).first()
        ser = BookModelSerializer(instance=book_obj)
        return Response(ser.data)

    def put(self, request, pk):
        book_obj = models.Book.objects.filter(pk=pk).first()
        ser = BookModelSerializer(instance=book_obj, data=request.data)  # 修改是既有instance 也有data才行
        if ser.is_valid():
            ser.save()
            return Response({"status": 100, "msg": "修改成功", "data": ser.data})
        return Response({"status": 100, "msg": "修改失败", "data": ser.errors})

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

使用 GenericAPIView 的视图层类:

和继承 APIView 的区别就是,多了querysetserializer_class属性,还有get_queryset等方法,而且无论换了什么模型表(Auth、Publish),只需要改变querysetserializer_class属性即可,五个接口方法均不需要改变

class BookGeneric(GenericAPIView):
    queryset = Book.objects
    serializer_class = BookModelSerializer

    def get(self, request):
        quertset = self.get_queryset()
        serializer_class = self.get_serializer(instance=quertset, many=True)
        print(serializer_class.data)
        return Response(serializer_class.data)

    def post(self, request):
        serializer_class = self.get_serializer(data=request.data)
        if serializer_class.is_valid():
            serializer_class.save()
            return Response({"code": 100, "msg": "新增成功", "data": serializer_class.data})
        return Response({"code": 101, "msg": "新增失败", "data": serializer_class.errors})


class BookSetGeneric(GenericAPIView):
    queryset = Book.objects
    serializer_class = BookModelSerializer

    def get(self, request, pk):
        queryset = self.get_object()
        serializer_class = self.get_serializer(instance=queryset)
        return Response(serializer_class.data)

    def put(self, request, pk):
        queryset = self.get_object()
        serializer_class = self.get_serializer(instance=queryset, data=request.data)  # 修改是既有instance 也有data才行
        if serializer_class.is_valid():
            serializer_class.save()
            return Response({"status": 100, "msg": "修改成功", "data": serializer_class.data})
        return Response({"status": 100, "msg": "修改失败", "data": serializer_class.errors})

    def delete(self, request, pk):
        self.get_object().delete()
        return Response({"status": 100, "msg": "删除成功"})

继承 GenericAPIView 和5个视图扩展类:

五个视图扩展类封装了原来 GeneriacAPIView 类所需要书写的五个接口方法

获取模型类所有数据对象(get方法)和修改数据(post方法),书写的方法和原来 GeneriacAPIView 类中的五个接口几乎一致:

class ListModelMixin:
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

class CreateModelMixin:
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

获取单个模型类数据对象、修改数据、删除数据三个需要传入pk值的视图扩展类:

class RetrieveModelMixin:
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

class UpdateModelMixin:
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop("partial", False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)
        if getattr(instance, "_prefetched_objects_cache", None):
            instance._prefetched_objects_cache = {}
        return Response(serializer.data)

class DestroyModelMixin:
    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

视图层类:

继承了这些视图扩展类,我们就可以使用类中封装的接口函数,更进一步精简视图层类的接口代码了

class BookGenericMinxin(GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = Book.objects
    serializer_class = BookModelSerializer

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

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


class BookGenericSetMinxin(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    queryset = Book.objects
    serializer_class = BookModelSerializer

    def get(self, request, pk):
        return self.retrieve(request)

    def put(self, request, pk):
        return self.update(request)

    def delete(self, request, pk):
        return self.destroy(request)

序列化类:

class BookModelSerializer(ModelSerializer):  # 继承的是 ModelSerializer
    class Meta:
        model = Book
        fields = ["id", "name", "price", "publish", "authors", "author_list", "publish_info"]  # 注意别漏写 fields

        extra_kwargs = {
            "publish": {"write_only": True},
            "authors": {"write_only": True}
        }

路由层:

urlpatterns = [
    path("admin/", admin.site.urls),
    path("book/", views.BookAPIView.as_view()),
    path("book/<int:pk>/", views.BookSetAPIView.as_view()),
    path("book1/", views.BookGeneric.as_view()),
    path("book1/<int:pk>/", views.BookSetGeneric.as_view()),
    path("book2/", views.BookGenericMinxin.as_view()),
    path("book2/<int:pk>/", views.BookGenericSetMinxin.as_view()),
]

封装程度越来越高,代码越来越精简

GenericAPIView 的9个视图子类

这九个视图子类,连 getpostputgetdelete 五个接口都帮我们写完了,我们在书写GenericAPIView视图层类时,只需要导入这九个试图子类来继承,就可以不用书写五个方法了,需要什么接口导入什么模块。

单独的类:

from rest_framework.generics import CreateAPIView, ListAPIView, RetrieveAPIView, UpdateAPIView, DestroyAPIView

组合:

from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView, RetrieveDestroyAPIView, RetrieveUpdateAPIView
  • CreateAPIView

  • ListAPIView

  • RetrieveAPIView

  • UpdateAPIView

  • DestroyAPIView

  • ListCreateAPIView

  • RetrieveUpdateDestroyAPIView

  • RetrieveDestroyAPIView

  • RetrieveUpdateAPIView

其实,它们就是继承了GenericAPIView类,并且与五个视图扩展类进行了组合继承,并把接口函数也添加上罢了。pk使用 *args 和 **kwargs 来取缔了

class CreateAPIView(mixins.CreateModelMixin,
                    GenericAPIView):
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class ListAPIView(mixins.ListModelMixin,
                  GenericAPIView):
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

class RetrieveAPIView(mixins.RetrieveModelMixin,
                      GenericAPIView):
    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

class UpdateAPIView(mixins.UpdateModelMixin,
                    GenericAPIView):
    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)
    def patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)

class DestroyAPIView(mixins.DestroyModelMixin,
                     GenericAPIView):
    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

# 组合的就不逐一列举了,原理一样
class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
                                   mixins.UpdateModelMixin,
                                   mixins.DestroyModelMixin,
                                   GenericAPIView):
    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 patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)
    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

这样,我们在书写视图层类更加精简了:

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


class BookSetView(RetrieveUpdateDestroyAPIView):
    queryset = Book.objects
    serializer_class = BookModelSerializer

路由书写:

urlpatterns = [
    path("book3/", views.BookView.as_view()),
    path("book3/<int:pk>/", views.BookSetView.as_view()),
]

ViewSetMixin 介绍

ViewSetMixin的源码可以看到,它把as_view方法进行了重写,在路由层调用视图类时,如果类第一个继承的时ViewSetMixin,会优先调用它的as_view方法,需要传入位置参数 (一个字典)actions,而且由actions.items(),actions是一个字典。

这样一来我们不使用到 九个视图子类给我们封装的五个接口方法(get、post、put、delete、patch),可以直接继承 ViewSetMixin, ListModelMixin, CreateModelMixin, GenericAPIView。因为在路由中会传进来 get 等五个字典key,赋值对象地址后可直接调用。

记住:继承ViewSetMixin 之后书写路由时需要传入 actions 参数

class ViewSetMixin:
    @classonlymethod
    def as_view(cls, actions=None, **initkwargs):

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            # 例子 method = "get",action="list"
            for method, action in actions.items():
                # 获取 list() 方法的对象地址
                handler = getattr(self, action)
                # 相当于 self.get = list,把lsit方法赋值给了self.get,发送get请求时触发,相当于触发了 ListModelMixin 的 list() 方法
                setattr(self, method, handler)

            return self.dispatch(request, *args, **kwargs)
        view.actions = actions
        return csrf_exempt(view)

导入

from rest_framework.viewsets import ViewSetMixin

路由层:

as_view()需要传入字典:{“请求方式”: “方法名”}

注意字典中分别是五个接口的请求方式和对应的五个视图扩展类封装好的方法

urlpatterns = [
    path("book4/", views.BookViewSet.as_view(actions={"get": "list", "post": "create"})),
    path("book4/<int:pk>/", views.BookViewSetPk.as_view(actions={"get": "retrieve", "put": "update", "delete": "delete"}))
]

视图层类:

class BookViewSet(ViewSetMixin, ListCreateAPIView):
    queryset = Book.objects
    serializer_class = BookModelSerializer


class BookViewSetPk(ViewSetMixin, RetrieveUpdateDestroyAPIView):
    queryset = Book.objects
    serializer_class = BookModelSerializer

Viewset系列

from rest_framework.viewsets import ViewSetMixin, ViewSet, GenericViewSet, ModelViewSet
  • ViewSetMixin
  • ViewSet
  • GenericViewSet
  • ModelViewSet

ViewSet

继承了 ViewSetMixinAPIView

class ViewSet(ViewSetMixin, views.APIView):
    """
    The base ViewSet class does not provide any actions by default.
    """
    pass

GenericViewSet

继承了 ViewSetMixinGenericAPIView

class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
    """
    The GenericViewSet class does not provide any actions by default,
    but does include the base set of generic view behavior, such as
    the `get_object` and `get_queryset` methods.
    """
    pass

ModelViewsetMinx

包含了五个视图扩展类和 GenericViewSet

这样继承了 ModelViewSet的试图层类可以不用把五个接口分开两个类来写了

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass
class BookViewSet(ModelViewSet):
    queryset = Book.objects
    serializer_class = BookModelSerializer

继承了 ViewSetMixin 之后,路由就要都要写成这样:

urlpatterns = [
    path("book4/", views.BookViewSet.as_view(actions={"get": "list", "post": "create"})),
    path("book4/<int:pk>/", views.BookViewSetPk.as_view(actions={"get": "retrieve", "put": "update", "delete": "delete"}))
]

drf 提供了自动生成这样两条路由与视图函数对应关系的功能模块 routers

用法:

# 导入
from rest_framework.routers import DefaultRouter, SimpleRouter

# 实例化对象并注册路由
router = DefaultRouter()
router.register("book4", views.BookViewSet, "book4")

添加到路由列表中

1、路由分发

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/v1/", include(router.urls)),
]

2、直接列表相加

urlpatterns += router.urls

image

hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » drf 视图组件