在Django REST Framework序列化器的create方法中如何正确抛出PermissionDenied错误?
看起来你遇到的问题根源其实不是PermissionDenied的抛出方式不对,而是你的create_consultation函数可能存在执行路径没有返回实例,或者异常没有被正确触发的情况,导致DRF抛出了AssertionError来提示你create()方法没有返回对象实例。我来一步步帮你理清楚怎么正确处理:
首先,先分析为什么你会得到那个AssertionError:
- 当你的
create_consultation函数抛出PermissionDenied时,序列化器的create方法会直接中断执行,不会走到return consultation那一行。这时候DRF应该优先处理这个异常,返回403响应,而不是抛出AssertionError。所以你现在看到AssertionError,大概率是因为在某些情况下,create_consultation既没有抛出异常,也没有返回有效的Consultation实例——比如你贴的代码里create_consultation只写了if分支的raise,else分支是不是没写return逻辑?如果是这样,当ticket存在时,函数没有返回值,导致consultation变量是None,序列化器的create方法return None,DRF就会触发那个AssertionError。
那正确的解决步骤应该是这样:
1. 先完善create_consultation函数,确保所有路径都有返回或抛出异常
你的create_consultation函数必须保证:要么在ticket不存在时抛出PermissionDenied,要么在ticket存在时正确创建并返回Consultation实例。比如补全函数代码:
from rest_framework import exceptions def create_consultation(customer: CustomerFactory.Models.CUSTOMER, validated_data): # 先补充获取ticket的逻辑,比如从数据库查询用户的有效ticket ticket = Ticket.objects.filter(customer=customer, active=True).first() if ticket is None: raise exceptions.PermissionDenied("No active ticket found for this customer, request is forbidden") # 这里写创建Consultation实例的业务逻辑 consultation = Consultation.objects.create(customer=customer, **validated_data) # 确保返回有效的实例 return consultation
2. 保持序列化器的create方法不变,不需要额外处理
你的序列化器代码本身是没问题的,只要create_consultation要么抛异常要么返回实例,序列化器的create方法就能正常工作:
class ConsultationSerializer(serializers.ModelSerializer): def create(self, validated_data): consultation = ConsultationService.create_consultation(validated_data.pop('customer', None), validated_data) return consultation
3. 移除viewset里捕获AssertionError的代码
那个在perform_create里捕获AssertionError然后重新抛PermissionDenied的做法完全没必要,而且是在掩盖真正的问题。当create_consultation正确抛出PermissionDenied时,DRF的默认异常处理器会自动捕获它,返回一个403状态码的响应,并且把你设置的错误消息包含在响应的detail字段里,不需要你在viewset里额外处理。
为什么这样就可以?
当create_consultation抛出PermissionDenied时,这个异常会沿着调用栈向上传递:
- 序列化器的
create方法中断执行,抛出PermissionDenied - 视图集的
perform_create方法调用serializer.save()时会接收到这个异常 - DRF的默认异常处理机制会捕获
PermissionDenied,自动返回状态码403的响应,并且把你设置的错误消息放在响应体里
而AssertionError出现的唯一原因就是你的序列化器create方法返回了None,只要保证create_consultation在所有路径下要么抛异常要么返回实例,这个AssertionError就会消失,同时PermissionDenied也能正确返回你想要的错误消息。
备注:内容来源于stack exchange,提问作者nama asliku

