Django-视图类代码优化


一、 优化get、post等方法

查看我们的代码,不难发现,我们实现的get、post、put等方法,不管对那个接口进行操作,代码都是一样的。那么我们就可以把这些方法抽取出来。

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

        serializer = self.get_serializer(instance=queryset, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

# 此处需注意继承顺序,自定义的类在前面,GenericAPIView在后面
class ProjectViews(ListModelMixin, GenericAPIView):
    queryset = Projects.objects.all()
    serializer_class = serializers.ProjectModelSerializer
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ['^name', '=leader']
    ordering_fields = ['id', 'name', 'leader']
    pagination_class = PageNumberPagination

    def get(self, request: Request):
    	'''# 把这部分全部抽取出来写一个类,当前类继承抽取出来的类,调用父类的该方法就可以实现了
    	queryset = self.filter_queryset(self.get_queryset())
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(instance=page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(instance=queryset, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
        '''
        return self.list(request)

1.1 mixins

其实上面提取的那些方法,在DRF框架中已经给我们提供了,只需要导入就可以使用了。上面的代码可以优化为:

from rest_framework import mixins

class ProjectViews(mixins.ListModelMixin, GenericAPIView):
    queryset = Projects.objects.all()
    serializer_class = serializers.ProjectModelSerializer
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ['^name', '=leader']
    ordering_fields = ['id', 'name', 'leader']
    pagination_class = PageNumberPagination

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

二、优化视图类继承关系

经过上面的优化后类视图变为:

class ProjectViews(mixins.ListModelMixin,
                   mixins.CreateModelMixin,
                   GenericAPIView):
    queryset = Projects.objects.all()
    serializer_class = serializers.ProjectModelSerializer
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ['^name', '=leader']
    ordering_fields = ['id', 'name', 'leader']
    pagination_class = PageNumberPagination

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

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

这样看虽然优化了很多,但是继承的类还是有些多,而且get方法和post方法的逻辑在每一个类中都是一样的,我们可以创建一个基类,就免了这许多麻烦。

class ListCreateAPIView(mixins.ListModelMixin,
                        mixins.CreateModelMixin,
                        GenericAPIView):
    def get(self, request: Request):
        return self.list(request)

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

class ProjectViews(ListCreateAPIView):
    queryset = Projects.objects.all()
    serializer_class = serializers.ProjectModelSerializer
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ['^name', '=leader']
    ordering_fields = ['id', 'name', 'leader']
    pagination_class = PageNumberPagination

drf框架提供了类似上述的APIview视图。路径为rest_framework.generics。我们可以根据项目的情况来选择需要的视图。
在这里插入图片描述这样,上面的代码可以优化成这样:

from rest_framework import generics

class ProjectViews(generics.ListCreateAPIView):
    queryset = Projects.objects.all()
    serializer_class = serializers.ProjectModelSerializer
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ['^name', '=leader']
    ordering_fields = ['id', 'name', 'leader']
    pagination_class = PageNumberPagination

此时,我们有两个类视图,一个是没有传id的,一个是传了id的,但是两个类视图中的queryset和serializer_class都是一样的,那么我们有没有什么办法只写一个类视图呢?

三、优化为一个类视图

首先把所有的方法提取出来,虽然两个get方法,但是他们调用的不是同一个方法。
该方法继承视图集(ViewSet),只有继承了视图集才支持请求方法名称与action名称一一对应的功能。

class ProjectViewSet(viewsets.ViewSet):
    queryset = Projects.objects.all()
    serializer_class = serializers.ProjectModelSerializer

    def list(self, request, *args, **kwargs):
        pass

    def retrieve(self, request, *args, **kwargs):
        pass

    def update(self, request, *args, **kwargs):
        pass

    def destroy(self, request, *args, **kwargs):
        pass

    def partial_update(self, request, *args, **kwargs):
        pass

但是Django只认那些固定的方法名称,例如get,post等。所以我们需要路由文件(url.py)使它支持我们上面的那些方法名。

  1. 只要继承了ViewSet的类视图,那么就支持请求方法名称与action名称进行一一对应的功能,这是ViewSetMixin提供的功能。
  2. 在as_view方法中添加字典,key为请求方法名称(get、post、put、delete、patch、option、head),value为需要调用的action方法名称
urlpatterns = [
    path('projects/', views.ProjectViewSet.as_view({
    
    
        'get': 'list',
        'post': 'create'
    })),
    path('projects/<int:pk>/', views.ProjectViewSet.as_view({
    
    
        'get': 'retrieve',
        'put': 'update',
        'patch': 'partial_update',
        'delete': 'destroy'
    })),
]

但是上面的视图类只继承了ViewSet,不支持get_queryset,get_serializer等方法,这些方法是GenericAPIView提供的。
视图集中提供了一个GenericViewSet,即继承了ViewSetMixin,又继承了generics.GenericAPIView。我们继承GenericViewSet就解决了问题。

class ProjectViewSet(viewsets.GenericViewSet):
    ...

我们视图类中的list、retrieve、update、destroy、partial_update方法,在mixins.RetrieveModelMixin,mixins.ListModelMixin,mixins.UpdateModelMixin,mixins.DestroyModelMixin中都有提供,我们只要继承这些类就不用写这些方法了,简化后的代码如下:

class ProjectViewSet(mixins.RetrieveModelMixin,
                     mixins.ListModelMixin,
                     mixins.UpdateModelMixin,
                     mixins.DestroyModelMixin,
                     viewsets.GenericViewSet):
	queryset = Projects.objects.all()
    serializer_class = serializers.ProjectModelSerializer

查看视图集的源代码,我们发现有如下的方法,非常符合我们的需求:

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    pass

于是再次修改我们的视图类:

class ProjectViewSet(viewsets.ModelViewSet):
	queryset = Projects.objects.all()
    serializer_class = serializers.ProjectModelSerializer

至此,我们视图类的代码就精简为3行了,非常的方便。
当然,并不是所有的视图类都是继承ModelViewSet,我们还需要根据自己项目的实际情况,选择更适合的视图集去继承。

四、优化路由

如果我们有很多action,就会使得路由文件写起来很麻烦,我们可以使用router来自动生成路由。
修改url.py文件代码。

# 创建一个路由对象
# 方式一:
router = routers.SimpleRouter()
# 方式二:(会自动添加跟路由,作为获取当前数据的入口)
# router = routers.DefaultRouter()
# 注册路由
# 第一个参数为路由前缀(等同于path中的'projects/'),第二个参数为视图集
# 使用视图集中的路由机制,只会为特定的action(retrieve、update、list、create等)自动生成路由条件
# 默认自定义的action,不会自动生成路由条目,需要手动添加路由映射
router.register(r'projects', views.ProjectViewSet)

urlpatterns = [
    # path('projects/', views.ProjectViewSet.as_view({
    
    
    #     'get': 'list',
    #     'post': 'create'
    # })),
    # path('projects/<int:pk>/', views.ProjectViewSet.as_view({
    
    
    #     'get': 'retrieve',
    #     'put': 'update',
    #     'patch': 'partial_update',
    #     'delete': 'destroy'
    # })),

    # 合并路径条目,方式二(一般不使用这种方法):
    # path('', include(router.urls))
]

# 合并路径条目,方式一:
urlpatterns += router.urls

如果我们的视图中有自定义的action如下:

class ProjectViewSet(viewsets.ModelViewSet):
	...
    def names(self, request):
        queryset = self.get_queryset()
        names_list = [{
    
    'id': project.id, 'name': project.name} for project in queryset]
        return Response(names_list, status=status.HTTP_200_OK)

想要访问这个接口,我们就只能手动添加路由。

...
urlpatterns = [
    path('projects/names/', views.ProjectViewSet.as_view({
    
    
	    'get': 'names',
    })),
]
...

如果自定义的action有很多,这种方法就非常麻烦,那么我们需要有自动添加自定义路由的方法,修改视图文件,给自定义的action前加action装饰器即可:

from rest_framework.decorators import action

class ProjectViewSet(viewsets.ModelViewSet):
	...
    # @action(methods=['get'], detail=False, url_path='pp', url_name='nn')
	@action(methods=['get'], detail=False)
    def names(self, request):
        queryset = self.get_queryset()
        names_list = [{
    
    'id': project.id, 'name': project.name} for project in queryset]
        return Response(names_list, status=status.HTTP_200_OK)

action装饰器参数说明:

  1. methods参数默认为get方法,可以在列表中指定多个请求方法
  2. detail指定是否需要接收模型主键值,如果无需接收主键值,那么需要设置detail=False,否则需要设置detail=True
  3. url_path指定生成的路由条目路径名,默认为自定义action方法名称
  4. url_name指定生成的路由名称后缀,默认为自定义action方法名称

五、其他方法的优化

我们上面的name方法(获取所有项目的名称)能不能再优化一下呢,不难发现我们name方法没有使用序列化器类,在数据非常多的时候这样是很不方便的。
之前的序列化器类很明显不符合我们这个需求,我们需要再写一个序列化器类来匹配这个。

class ProjectNamesModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Projects
        fields = ('id', 'name')

修改name方法:

	@action(methods=['get'], detail=False)
    def names(self, request, *args, **kwargs):
        queryset = self.get_queryset()
        serializer = serializers.ProjectNamesModelSerializer(instance=queryset, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

5.1 一个视图类使用多个序列化器类

但是这样把序列化器类写死不方便管理。查看源码我们发现,在调用get_serializer()时,会调用get_serializer_class()方法。我们可以通过重写get_serializer_class()方法来解决在一个视图类中出现多个序列化器类的问题。

	@action(methods=['get'], detail=False)
    def names(self, request, *args, **kwargs):
        queryset = self.get_queryset()
        serializer = self.get_serializer(instance=queryset, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
        
    def get_serializer_class(self):
        if self.action == 'names':
            return serializers.ProjectNamesModelSerializer
        else:
            return self.serializer_class

5.2 使用父类的方法返回

观察上面name方法中的代码,发现代码与mixins中的list()相似度极高,只是list多了分页操作而已。那么我们就可以用list来返回。

	@action(methods=['get'], detail=False)
    def names(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

但是此时我们多了分页功能,与需求不符。

5.3 跳过父类的过滤功能

父类有过滤功能,但是我们又不想使用过滤功能,我们可以通过重写filter_queryset()方法来跳过过滤功能。

    def filter_queryset(self, queryset):
        if self.action == 'names':
            return self.queryset
        else:
            return super().filter_queryset(queryset)

5.4 跳过父类的分页功能

父类有分页功能,但是我们又不想使用分页功能,我们可以通过重写paginate_queryset()方法来跳过分页功能。

    def paginate_queryset(self, queryset):
        if self.action == 'names':
            return None
        else:
            return super().paginate_queryset(queryset)

5.5 使用不同的查询集

我们可以通过重写get_queryset()方法来使用不同的查询集。

    def get_queryset(self):
        if self.action == 'names':
            return self.queryset.filter(name__contains='Aaron')
        else:
            return super().get_queryset()

5.6 重写mixins下的几个方法

如果list、retrieve、update、destroy这些方法都不满足我们的需求,但是代码又差不多 我们也可以重写这些方法来达到我们的要求。

    def retrieve(self, request, *args, **kwargs):
        response = super().retrieve(request, *args, **kwargs)
        project_id = response.data.pop('id')
        return response

六、类视图设计原则

类视图的设计原则:

  1. 类视图尽量简化
  2. 根据需求选择相应的父类视图
  3. 如果DRF中的类视图有提供相应的逻辑,那么直接使用父类提供的
  4. 如果DRF中的类视图,绝大多数需要都能满足,那么直接重写父类的实现
  5. 如果DRF中的类视图完全不满足要求,直接自定义

猜你喜欢

转载自blog.csdn.net/qq_33537936/article/details/113940376