【问题标题】:Django REST Framework: return 404 (not 400) on POST if related field does not exist?Django REST Framework:如果相关字段不存在,则在 POST 上返回 404(不是 400)?
【发布时间】:2014-06-25 23:37:25
【问题描述】:

我正在开发一个 REST API,它从一些无法 PATCH 或其他任何东西的脑死软件接收 POST 请求。 POST 用于更新数据库中已经存在的模型对象。

具体来说,我正在为具有相关字段(一个 SlugRelatedField,因为 POSTer 知道“名称”属性但不知道“pk”)的对象发布数据。但是,如果 POSTer 发送的数据在 SlugRelatedField 上没有返回任何“名称”(例如,相关对象不存在),我需要返回 404。我已经用调试器完成了这个,但似乎 DRF 使用了一些 Django 信号魔法来做到这一点 The Way DRF Does It™,即返回 400 BAD REQUEST。我不知道如何修改这个 - 只有当它是上述条件而不是真正的 400-worthy POST - 到 404。

顺便说一句,在我看来,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。

【问题讨论】:

    标签: python django django-rest-framework django-signals


    【解决方案1】:

    您可以为此使用 Django 快捷方式,获取 obj.sample:

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

    【讨论】:

    • 谢谢 - 一位朋友向我建议,但它不起作用,因为在这种情况下 pre_save() 没有运行。我可能最终会在其他代码之外添加它来解决问题。另外,我对这个效果的原始评论神秘地消失了。
    • 那你可能想解决pre_save没有先运行的问题,最可能的原因是解析post数据时出现异常,可能是因为权限。尝试使用 AllowAny
    • 我会检查它 - 视图已设置为世界上的任何人都可以发布,但是 response.content 抱怨具有给定名称的样本不存在 - 所以如果有权限问题,还没有透露。我现在才重新开始使用代码 - 感谢您的提示。
    【解决方案2】:

    为什么不在您的 API 视图中覆盖 post,而不是使用 pre_save

    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,如果有多个对象匹配,则返回MultipleObjectsReturnedThe relevant documentation:

    请注意,使用 get() 和使用 filter() 带有 [0] 的切片。如果没有匹配的结果 查询,get() 将引发 DoesNotExist 异常。这个例外是一个 正在执行查询的模型类的属性 - 所以 在上面的代码中,如果没有主键为 1、Django会引发Entry.DoesNotExist。

    同样,如果有多个项目与 get() 查询。在这种情况下,它将引发 MultipleObjectsReturned, 这又是模型类本身的一个属性。

    【讨论】:

    • 谢谢!快速提问 - 如果以这种方式覆盖 POST,get_object_or_404 是否不适合使用?我还是 DRF 的新手,我对它何时需要 Response 对象感到有些困惑。
    • 回答了我自己的评论。我最终省略了 try/except 并在覆盖的 post() 中使用了一个虚拟的 get_object_or_404 调用,就像您指定的那样,只是为了立即抛出 404。然后,我也在下面使用了@fixmycode 的建议。我希望我能接受这两个答案。现在我所有的测试都通过了,非常感谢。
    • @GordonMorehouse 使用get_object_or_404 没有任何问题我只是喜欢使用 DRF 的状态选项,因为它使我所有的响应状态代码保持一致并且它 provides nearly all the HTTP codes。我会为你 +1 @fixmycode 的答案!
    • 我很难找到出现Response 的模块。有什么线索吗?
    • @dabad 试试from rest_framework.response import Response
    猜你喜欢
    • 2019-01-05
    • 1970-01-01
    • 2018-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-01
    • 2018-12-22
    • 2017-06-21
    相关资源
    最近更新 更多