You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何重构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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 12:15:42