如何重构Django REST框架多模型视图代码以遵循DRY原则?
Django REST框架代码重构(遵循DRY原则)
问题描述
开发中针对Products、ProductsStorage、ProductSign三个模型分别实现了ListCreateAPIView、RetrieveUpdateAPIView、RetrieveDestroyAPIView类型的API视图,存在大量重复配置代码;同时urls.py中路由配置重复,且存在部分错误(比如Storage的详情路由错误绑定了StorageAPIList,ProductSign视图的queryset配置错误)。
原代码
views.py
from rest_framework import generics from rest_framework.pagination import LimitOffsetPagination from rest_framework.filters import SearchFilter, OrderingFilter from django_filters.rest_framework import DjangoFilterBackend from rest_framework.permissions import IsAdminUser from .serializers import * from .permissions import IsAdminOrReadOnly from .filters import * class ProductsAPIList(generics.ListCreateAPIView): queryset = Products.objects.all() serializer_class = ProductsSerializer pagination_class = LimitOffsetPagination permission_classes = (IsAdminOrReadOnly,) filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter) search_fields = ('title', 'category__name') filter_fields = ('category',) ordering_fields = ('price',) filterset_class = ProductsFilter class ProductsAPIUpdate(generics.RetrieveUpdateAPIView): queryset = Products.objects.all() serializer_class = ProductsSerializer permission_classes = (IsAdminOrReadOnly, ) class ProductsAPIRemove(generics.RetrieveDestroyAPIView): queryset = Products.objects.all() serializer_class = ProductsSerializer permission_classes = (IsAdminOrReadOnly, ) class StorageAPIList(generics.ListCreateAPIView): queryset = ProductsStorage.objects.all() serializer_class = ProductsStorageSerializer permission_classes = (IsAdminUser,) class StorageAPIUpdate(generics.RetrieveUpdateAPIView): queryset = ProductsStorage.objects.all() serializer_class = ProductsStorageSerializer permission_classes = (IsAdminUser,) class StorageAPIRemove(generics.RetrieveDestroyAPIView): queryset = ProductsStorage.objects.all() serializer_class = ProductsStorageSerializer permission_classes = (IsAdminUser,) class ProductSignAPIList(generics.ListCreateAPIView): queryset = ProductSign.objects.all() serializer_class = ProductSignSerializer permission_classes = (IsAdminUser,) class ProductSignAPIUpdate(generics.RetrieveUpdateAPIView): queryset = ProductSign serializer_class = ProductSignSerializer permission_classes = (IsAdminUser,) class ProductSignAPIRemove(generics.RetrieveDestroyAPIView): queryset = ProductsStorage.objects.all() serializer_class = ProductSignSerializer permission_classes = (IsAdminUser,)
urls.py
from django.conf.urls.static import static from django.contrib import admin from django.urls import path, include from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView from InternetShop import settings from InternetShopApp.views import * urlpatterns = [ path('admin/', admin.site.urls), path('api/v1/products/', ProductsAPIList.as_view()), path('api/v1/products/<int:pk>/', ProductsAPIUpdate.as_view()), path('api/v1/productsremove/<int:pk>/', ProductsAPIRemove.as_view()), path('api/v1/storage/', StorageAPIList.as_view()), path('api/v1/storage/<int:pk>/', StorageAPIList.as_view()), path('api/v1/storageremove/<int:pk>/', StorageAPIList.as_view()), path('api/v1/productsign/', ProductSignAPIList.as_view()), path('api/v1/productsign/<int:pk>/', ProductSignAPIUpdate.as_view()), path('api/v1/productsignremove/<int:pk>/', ProductSignAPIRemove.as_view()), path('api/v1/login/', include('djoser.urls')), path('api/v1/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/v1/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), path('api/v1/token/verify/', TokenVerifyView.as_view(), name='token_verify'), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
重构方案
一、视图层重构:抽取通用基类
将重复的权限、分页、过滤等配置抽离为通用基类,具体模型视图仅需指定queryset和serializer_class,同时修正原代码中的错误:
from rest_framework import generics from rest_framework.pagination import LimitOffsetPagination from rest_framework.filters import SearchFilter, OrderingFilter from django_filters.rest_framework import DjangoFilterBackend from rest_framework.permissions import IsAdminUser from .serializers import * from .permissions import IsAdminOrReadOnly from .filters import * # -------------------------- 通用基类 -------------------------- # 带分页、过滤、搜索的管理员/只读权限ListCreate基类(Products专用) class AdminOrReadOnlyListCreateAPIView(generics.ListCreateAPIView): permission_classes = (IsAdminOrReadOnly,) pagination_class = LimitOffsetPagination filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter) search_fields = ('title', 'category__name') filter_fields = ('category',) ordering_fields = ('price',) filterset_class = ProductsFilter # 仅管理员权限的ListCreate基类 class AdminOnlyListCreateAPIView(generics.ListCreateAPIView): permission_classes = (IsAdminUser,) # 管理员/只读权限的RetrieveUpdate基类 class AdminOrReadOnlyRetrieveUpdateAPIView(generics.RetrieveUpdateAPIView): permission_classes = (IsAdminOrReadOnly,) # 管理员/只读权限的RetrieveDestroy基类 class AdminOrReadOnlyRetrieveDestroyAPIView(generics.RetrieveDestroyAPIView): permission_classes = (IsAdminOrReadOnly,) # 仅管理员权限的RetrieveUpdate基类 class AdminOnlyRetrieveUpdateAPIView(generics.RetrieveUpdateAPIView): permission_classes = (IsAdminUser,) # 仅管理员权限的RetrieveDestroy基类 class AdminOnlyRetrieveDestroyAPIView(generics.RetrieveDestroyAPIView): permission_classes = (IsAdminUser,) # -------------------------- 具体模型视图 -------------------------- class ProductsAPIList(AdminOrReadOnlyListCreateAPIView): queryset = Products.objects.all() serializer_class = ProductsSerializer class ProductsAPIUpdate(AdminOrReadOnlyRetrieveUpdateAPIView): queryset = Products.objects.all() serializer_class = ProductsSerializer class ProductsAPIRemove(AdminOrReadOnlyRetrieveDestroyAPIView): queryset = Products.objects.all() serializer_class = ProductsSerializer class StorageAPIList(AdminOnlyListCreateAPIView): queryset = ProductsStorage.objects.all() serializer_class = ProductsStorageSerializer class StorageAPIUpdate(AdminOnlyRetrieveUpdateAPIView): queryset = ProductsStorage.objects.all() serializer_class = ProductsStorageSerializer class StorageAPIRemove(AdminOnlyRetrieveDestroyAPIView): queryset = ProductsStorage.objects.all() serializer_class = ProductsStorageSerializer class ProductSignAPIList(AdminOnlyListCreateAPIView): queryset = ProductSign.objects.all() serializer_class = ProductSignSerializer class ProductSignAPIUpdate(AdminOnlyRetrieveUpdateAPIView): queryset = ProductSign.objects.all() # 修正原代码错误:补充.objects.all() serializer_class = ProductSignSerializer class ProductSignAPIRemove(AdminOnlyRetrieveDestroyAPIView): queryset = ProductSign.objects.all() # 修正原代码错误:替换为ProductSign的queryset serializer_class = ProductSignSerializer
二、路由层重构:两种可选方案
方案1:使用DRF DefaultRouter(推荐,符合RESTful规范)
DRF的路由器可自动为视图集生成标准REST路由,替代手动编写重复路由:
第一步:将视图改为ModelViewSet(替代三个独立视图)
在views.py中添加:
from rest_framework.viewsets import ModelViewSet class ProductsViewSet(ModelViewSet): queryset = Products.objects.all() serializer_class = ProductsSerializer permission_classes = (IsAdminOrReadOnly,) pagination_class = LimitOffsetPagination filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter) search_fields = ('title', 'category__name') filter_fields = ('category',) ordering_fields = ('price',) filterset_class = ProductsFilter class ProductsStorageViewSet(ModelViewSet): queryset = ProductsStorage.objects.all() serializer_class = ProductsStorageSerializer permission_classes = (IsAdminUser,) class ProductSignViewSet(ModelViewSet): queryset = ProductSign.objects.all() serializer_class = ProductSignSerializer permission_classes = (IsAdminUser,)
第二步:配置路由器
修改urls.py:
from django.conf.urls.static import static from django.contrib import admin from django.urls import path, include from rest_framework.routers import DefaultRouter from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView from InternetShop import settings from InternetShopApp.views import ProductsViewSet, ProductsStorageViewSet, ProductSignViewSet # 初始化路由器并注册视图集 router = DefaultRouter() router.register(r'api/v1/products', ProductsViewSet, basename='products') router.register(r'api/v1/storage', ProductsStorageViewSet, basename='storage') router.register(r'api/v1/productsign', ProductSignViewSet, basename='productsign') urlpatterns = [ path('admin/', admin.site.urls), # 包含路由器生成的所有路由 path('', include(router.urls)), path('api/v1/login/', include('djoser.urls')), path('api/v1/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/v1/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), path('api/v1/token/verify/', TokenVerifyView.as_view(), name='token_verify'), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
路由器会自动生成以下标准路由:
GET/POST /api/v1/products/→ 列表/创建GET/PUT/PATCH /api/v1/products/<int:pk>/→ 详情/更新DELETE /api/v1/products/<int:pk>/→ 删除
方案2:手动批量生成路由(保留原视图结构)
如果不想修改视图结构,可通过批量配置减少重复代码:
from django.conf.urls.static import static from django.contrib import admin from django.urls import path, include from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView from InternetShop import settings from InternetShopApp.views import * # 定义路由配置元组:(前缀, List视图, Update视图, Remove视图) route_configs = [ ('products', ProductsAPIList, ProductsAPIUpdate, ProductsAPIRemove), ('storage', StorageAPIList, StorageAPIUpdate, StorageAPIRemove), ('productsign', ProductSignAPIList, ProductSignAPIUpdate, ProductSignAPIRemove), ] # 批量生成模型路由 model_urls = [] for prefix, list_view, update_view, remove_view in route_configs: model_urls.append(path(f'api/v1/{prefix}/', list_view.as_view())) model_urls.append(path(f'api/v1/{prefix}/<int:pk>/', update_view.as_view())) model_urls.append(path(f'api/v1/{prefix}remove/<int:pk>/', remove_view.as_view())) urlpatterns = [ path('admin/', admin.site.urls), # 展开批量生成的路由 *model_urls, path('api/v1/login/', include('djoser.urls')), path('api/v1/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/v1/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), path('api/v1/token/verify/', TokenVerifyView.as_view(), name='token_verify'), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
内容的提问来源于stack exchange,提问作者alemax1
相关产品推荐
相关产品推荐

