如何在Django REST Framework视图集中通过Query处理DELETE、PUT请求?
如何通过Query参数处理Django REST Framework的DELETE、PUT请求?
问题背景
作为Django新手,我已为Web应用实现Token认证,项目(hotels_app)中的核心代码如下:
models.py
class Hotel(UUIDMixin): company = models.CharField(_('company'), max_length=config.CHARS_DEFAULT, blank=True, null=True) name = models.CharField(_('name'), max_length=config.CHARS_DEFAULT) star_rating = models.PositiveSmallIntegerField( verbose_name=_('star rating'), blank=True, null=True, validators=[MaxValueValidator(5)], ) rooms_quantity = models.PositiveIntegerField(_('rooms quantity')) phone = PhoneNumberField( verbose_name=_('phone'), max_length=config.PHONE_LENGTH, blank=True, null=True, ) check_in = models.CharField(_('check in'), max_length=config.CHECK_IN_OUT_LEN) check_out = models.CharField(_('check out'), max_length=config.CHECK_IN_OUT_LEN) description = models.TextField(_('description'), blank=True, null=True) country = models.CharField(_('country'), max_length=config.CHARS_DEFAULT) state = models.CharField(_('state'), max_length=config.CHARS_DEFAULT, blank=True, null=True) city = models.CharField(_('city'), max_length=config.CHARS_DEFAULT) street = models.CharField(_('street'), max_length=config.CHARS_DEFAULT) building = models.CharField(_('building'), max_length=config.CHAR_BUILD_LENGTH) latitude = models.FloatField( verbose_name=_('latitude'), validators=[MinValueValidator(-config.LATITUDE), MaxValueValidator(config.LATITUDE)], ) longitude = models.FloatField( verbose_name=_('longitude'), validators=[MinValueValidator(-config.LONGITUDE), MaxValueValidator(config.LONGITUDE)], ) amenities = models.ManyToManyField(Amenity, verbose_name=_('amenities'), through='HotelAmenity') def __str__(self): return f'{self.name}, {self.country}' class Meta: indexes = [models.Index(fields=['name'], name='hotel_name_idx')] constraints = [ models.CheckConstraint( check=models.Q(star_rating__gte=0) & models.Q(star_rating__lte=5), name='star_rate_check', ), models.CheckConstraint(check=models.Q(rooms_quantity__gt=0), name='rooms_quan_check'), models.CheckConstraint( check=models.Q(latitude__gte=-config.LATITUDE) & models.Q(latitude__lte=config.LATITUDE), name='latitude_check', ), models.CheckConstraint( check=models.Q(longitude__gte=-config.LONGITUDE) & models.Q(longitude__lte=config.LONGITUDE), name='longitude_check', ), ] ordering = ['name'] db_table = 'hotel' verbose_name = _('hotel') verbose_name_plural = _('hotels')
serializers.py
class HotelSerializer(HyperlinkedModelSerializer): class Meta: model = Hotel fields = ( 'id', 'company', 'name', 'star_rating', 'rooms_quantity', 'phone', 'check_in', 'check_out', 'description', 'country', 'state', 'city', 'street', 'building', 'latitude', 'longitude', )
views.py
class Permission(permissions.BasePermission): def has_permission(self, request, _): if request.method in config.SAFE_METHODS: return bool(request.user and request.user.is_authenticated) elif request.method in config.UNSAFE_METHODS: return bool(request.user and request.user.is_superuser) return False def query_from_request(request, serializer=None) -> dict: if serializer: query = {} for attr in serializer.Meta.fields: attr_value = request.GET.get(attr, '') if attr_value: query[attr] = attr_value return query return request.GET def create_viewset(model, serializer): class CustomViewSet(viewsets.ModelViewSet): serializer_class = serializer permission_classes = [Permission] def get_queryset(self): instances = model.objects.all() query = query_from_request(self.request, serializer) if query: instances = instances.filter(**query) return instances return CustomViewSet HotelViewSet = create_viewset(Hotel, serializers.HotelSerializer)
urls.py
router = DefaultRouter() router.register(r'amenity', views.AmenityViewSet, basename='amenity') router.register(r'hotel', views.HotelViewSet, basename='hotel') router.register(r'room', views.RoomViewSet, basename='room') urlpatterns = [ # REST path('rest/', include(router.urls)), ]
问题描述
GET请求带Query参数可正常过滤数据(如http://127.0.0.1:8000/rest/hotel/?company=some_company),但向同一URL发送DELETE或PUT请求时,服务器返回:
{ "detail": "Method \"DELETE\" not allowed." }
已携带Token认证头,尝试过添加MultipleFieldLookupMixin、重写destroy和update方法,均无效。
原因分析
Django REST Framework的ModelViewSet通过DefaultRouter注册后,列表路由(如/rest/hotel/)默认仅支持GET(list)、POST(create)方法,DELETE和PUT方法被映射到详情路由(如/rest/hotel/<pk>/),用于操作单个实例。直接向列表URL发送DELETE/PUT会触发方法不允许的错误。
解决方案
要实现通过Query参数批量删除/更新实例,推荐使用DRF的自定义动作(Action),在ViewSet中添加批量操作的接口,符合REST规范且无需修改路由配置。
修改views.py中的ViewSet定义
在create_viewset函数的CustomViewSet类中添加以下自定义动作:
from rest_framework.decorators import action from rest_framework import status from rest_framework.response import Response def create_viewset(model, serializer): class CustomViewSet(viewsets.ModelViewSet): serializer_class = serializer permission_classes = [Permission] def get_queryset(self): instances = model.objects.all() query = query_from_request(self.request, serializer) if query: instances = instances.filter(**query) return instances # 批量删除动作 @action(detail=False, methods=['delete'], url_path='batch-delete') def batch_delete(self, request): query = query_from_request(request, self.serializer_class) if not query: return Response('缺少查询参数', status=status.HTTP_400_BAD_REQUEST) instances = self.get_queryset().filter(**query) count = instances.count() if count == 0: return Response(f'未找到匹配{model.__name__}的实例', status=status.HTTP_404_NOT_FOUND) try: instances.delete() except Exception as e: return Response(str(e), status=status.HTTP_500_INTERNAL_SERVER_ERROR) return ( Response(f'成功删除{count}个{model.__name__}实例', status=status.HTTP_200_OK) if count > 1 else Response(status=status.HTTP_204_NO_CONTENT) ) # 批量更新动作 @action(detail=False, methods=['put'], url_path='batch-update') def batch_update(self, request): query = query_from_request(request, self.serializer_class) if not query: return Response('缺少查询参数', status=status.HTTP_400_BAD_REQUEST) update_data = request.data if not update_data: return Response('缺少更新数据', status=status.HTTP_400_BAD_REQUEST) instances = self.get_queryset().filter(**query) count = instances.count() if count == 0: return Response(f'未找到匹配{model.__name__}的实例', status=status.HTTP_404_NOT_FOUND) try: # 批量更新字段(仅支持可直接更新的字段,复杂关联字段需单独处理) instances.update(**update_data) # 返回更新后的实例数据 updated_instances = self.get_queryset().filter(**query) serializer = self.get_serializer(updated_instances, many=True) return Response(serializer.data, status=status.HTTP_200_OK) except Exception as e: return Response(str(e), status=status.HTTP_500_INTERNAL_SERVER_ERROR) return CustomViewSet
使用方法
- 批量删除:向
http://127.0.0.1:8000/rest/hotel/batch-delete/?company=some_company发送DELETE请求 - 批量更新:向
http://127.0.0.1:8000/rest/hotel/batch-update/?company=some_company发送PUT请求,请求体携带要更新的字段(如{"star_rating": 4})
注意事项
- 批量更新仅支持Django ORM的
update()方法能处理的字段,涉及多对多、外键等关联字段的更新需额外逻辑处理 - 确保请求用户具备超级用户权限(已通过自定义
Permission类控制) - 若需要更复杂的过滤逻辑,可在
query_from_request函数中补充字段的查询类型(如模糊匹配、范围查询等)
内容的提问来源于stack exchange,提问作者Anna
相关产品推荐
相关产品推荐

