文章

16. REST Framework GenericView 通用视图

在之前的代码中, 基本实现了对于ProdcutCollection的增删改查接口.

但是在API的实现代码中存在着大量几乎相同的代码, 如:

1
2
3
4
5
6
7
8
9
10
11
12
# ProductList
def get(self, request):
        queryset = Product.objects.all()
        serializer = ProductSerializer(queryset, many=True,
                                       context={'request': request})
        return Response(serializer.data)

# CollectionList
def get(self, request: Request):
        queryset = Collection.objects.all().annotate(products_count=Count('products'))
        serializer = CollectionSerializer(queryset, many=True)
        return Response(serializer.data)

他们本质上是相同的功能, 只是处理的目标对象不同, 而编码模式也是几乎一样.

为了减少这种类似的重复编码, 就可以使用rest Framework所提供的generic view来实现

通用视图

rest framework提供了多种通用视图, 统一封装在 rest_framework.generic 包下.

详细官方文档

ProductList视图为例, 由于接口能够处理GETPOST请求, 同时返回的是一个结果集, 就可以直接使用ListCreateAPIView来快速实现:

1
2
3
4
class ProductList(generics.ListCreateAPIView):

    serializer_class = ProductSerializer
    queryset = Product.objects.select_related('collection').all()

注意: serializer_class的值是类而不是对象, 不能加()

由于products/路由在查询时需要查询关联的collection, 所以需要重写get_queryset方法来完善代码逻辑.

如果在查询时存在其他的业务需求, 比如根据用户类型和权限进行不同的查询等等, 则需要重写get_queryset方法:

1
2
3
4
def get_queryset(self):
        # logic code here
        queryset = Model.objects.all()
        return queryset

serializer_class同理, 可以通过重写get_serializer_class方法添加额外业务处理.

get_serializer_cotext方法指定了序列化的上下文, 如在进行超链接关系字段的序列化时就需要用到上下文中的request, 但是GenericView已经对其进行了更为完善的封装, 在仅需要设定request上下文的情况下无需进行更改. 查看父类的get_serializer_context可知:

1
2
3
4
5
6
7
8
9
10
11
12
# GenericAPIView

def get_serializer_context(self):
        """
        Extra context provided to the serializer class.
        """
        return {
                'request': self.request,
                'format': self.format_kwarg,
                'view': self
}

通过GenericView, 之前的视图方法被压缩到仅仅两行, 除此之外, GenericView还会为POST请求自动生成一个表单, 如图所示: form

对于这类接口, 甚至仅需要做一下配置便可以快速完成, 不需要额外代码:

1
2
3
4
5
6
7
8
# `store/urls.py`
urlpatterns = [
    path('products/', generics.ListCreateAPIView.as_view(
        queryset=Product.objects.select_related('collection').all(),
        serializer_class=ProductSerializer
    )),
    # other routes...
]

虽然可以通过路由配置文件直接实现通用视图接口, 但不建议这么使用.

  1. 后续可能会增加业务逻辑, 一旦增加那么还是要提取到单独的视图类中
  2. 配置文件中会引入逻辑代码, 以及额外的导入似的后期不便于维护

仅当临时测试新建接口时用一用就可以了.

本文由作者按照 CC BY 4.0 进行授权