Django REST Framework:如果相关字段不存在,POST时返回404而不是400?

6 投票
2 回答
13304 浏览
提问于 2025-04-18 11:06

我正在开发一个REST API,这个API需要处理一些非常简单的软件发来的POST请求,这些软件无法使用PATCH或者其他方法。这个POST请求是用来更新数据库中已经存在的模型对象。

具体来说,我在发送一些数据,这些数据是与某个字段相关的(一个叫做SlugRelatedField的字段,因为发送数据的人知道'名称'属性,但不知道'主键')。不过,如果发送数据的人提供的'名称'在SlugRelatedField中找不到对应的对象,我需要返回一个404错误(也就是找不到的意思)。我用调试工具检查过,发现DRF(Django REST Framework)使用了一些Django的信号机制来处理这个问题,结果是返回了一个400错误(错误的请求)。我不知道怎么修改这个逻辑,让它在上述情况下返回404,而不是错误的400。

顺便提一下,我在我的视图中的pre_save()方法在测试失败时并没有被执行。

这是序列化器的代码:

class CharacterizationSerializer(serializers.ModelSerializer):
    """
    Work-in-progress for django-rest-framework use.  This handles (de)serialization
    of data into a Characterization object and vice versa.

    See: http://www.django-rest-framework.org/tutorial/1-serialization
    """
    creator = serializers.Field(source='owner.user.username')
    sample = serializers.SlugRelatedField(slug_field='name',
                                          required=True,
                                          many=False,
                                          read_only=False)

    class Meta:
        model = Characterization
        # leaving 'request' out because it's been decided to deprecate it. (...maybe?)
        fields = ('sample', 'date', 'creator', 'comments', 'star_volume', 'solvent_volume',
                  'solution_center', 'solution_var', 'solution_minimum', 'solution_min_stddev',
                  'solution_test_len',)

这是视图的代码,在这个测试中pre_save没有被执行(但在其他一些测试中是会执行的):

class CharacterizationList(generics.ListCreateAPIView):
    queryset = Characterization.objects.all()
    serializer_class = CharacterizationSerializer
    permission_classes = (AnonPostAllowed,)   # @todo XXX hack for braindead POSTer

    def pre_save(self, obj):
        # user isn't sent as part of the serialized representation,
        # but is instead a property of the incoming request.
        if not self.request.user.is_authenticated():
            obj.owner = get_dummy_proxyuser()   # this is done for CharacterizationList so unauthed users can POST. @todo XXX hack
        else:
            obj.owner = ProxyUser.objects.get(pk=self.request.user.pk)

        # here, we're fed a string sample name, but we need to look up
        # the actual sample model.
        # @TODO: Are we failing properly if it doesn't exist?  Should
        # throw 404, not 400 or 5xx.
        # except, this code doesn't seem to be run directly when debugging.
        # a 400 is thrown; DRF must be bombing out before pre_save?
        obj.sample = Sample.objects.get(name=self.request.DATA['sample'])

为了确保,我还附上了失败的测试代码:

def test_bad_post_single_missing_sample(self):
    url = reverse(self._POST_ONE_VIEW_NAME)

    my_sample_postdict = self.dummy_plqy_postdict.copy()
    my_sample_postdict["sample"] = "I_DONT_EXIST_LUL"
    response = self.rest_client.post(url, my_sample_postdict)
    self.assertTrue(response.status_code == 404,
                    "Expected 404 status code, got %d (%s). Content: %s" % (response.status_code, response.reason_phrase, response.content))

如果我在self.rest_client.post()调用处设置一个断点,到那时响应已经显示400错误了。

2 个回答

8

你可以用Django的一个快捷方式来做到这一点,获取obj.sample:

from django.shortcuts import get_object_or_404
obj.sample = get_object_or_404(Sample, name=self.request.DATA['sample'])
6

与其使用 pre_save,不如在你的 API 视图中重写 post 方法:

def post(self, request, *args, **kwargs):
    ...other stuff
    try:
        obj.sample = Sample.objects.get(name=self.request.DATA['sample'])
        ...or whatever other tests you want to do
    except:
        return Response(status=status.HTTP_404_NOT_FOUND)

    response = super(CharacterizationList, self).post(request, *args, **kwargs)
    return response

确保你导入了 DRF 的状态:

from rest_framework import status

另外,注意你可能需要更具体地处理你捕获的异常。Django 的 get 方法会返回 DoesNotExist,如果没有匹配的结果,或者返回 MultipleObjectsReturned,如果有多个对象匹配。相关文档

注意,使用 get() 和使用 filter() 加上切片 [0] 是有区别的。如果没有结果匹配查询,get() 会抛出一个 DoesNotExist 异常。这个异常是查询所针对的模型类的一个属性——所以在上面的代码中,如果没有主键为 1 的 Entry 对象,Django 会抛出 Entry.DoesNotExist

同样,如果有多个项目匹配 get() 查询,Django 也会报错。在这种情况下,它会抛出 MultipleObjectsReturned,这也是模型类本身的一个属性。

撰写回答