【问题标题】:Django request data returns str instead of listDjango 请求数据返回 str 而不是 list
【发布时间】:2015-07-13 20:21:08
【问题描述】:

我正在使用 Django 和 REST 框架开发一个 REST api。我有一个使用这种 json 接受 POST 请求的端点:

{
        "pipeline": ["Bayes"],
        "material": [
            "Rakastan iloisuutta!",
            "Autojen kanssa pitää olla varovainen.",
            "Paska kesä taas. Kylmää ja sataa"
        ]
    }

它是一个机器学习分析 api,json 告诉使用贝叶斯分类器提供字符串并返回结果。当我通过发布请求手动测试它时,这很好用。但是,当我尝试编写单元测试时,它会崩溃。我有以下测试:

class ClassifyTextAPITests(APITestCase):
    fixtures = ['fixtures/analyzerfixtures.json'] #suboptimal fixture since requires bayes.pkl in /assets/classifiers folder

    def test_classification(self):
        """ Make sure that the API will respond correctly when required url params are supplied.
        """
        response = self.client.post(reverse('analyzer_api:classifytext'), {
            "pipeline": ["Bayes"],
            "material": [
                "Rakastan iloisuutta!",
                "Autojen kanssa pitää olla varovainen.",
                "Paska kesä taas. Kylmää ja sataa",
            ]
        })
        self.assertTrue(status.is_success(response.status_code))
        self.assertEqual(response.data[0], 1)

测试每次都失败,因为后一个断言给出“AssertionError:'P'!= 1”

这是我的查看代码:

class ClassifyText(APIView):
    """
    Takes text snippet as a parameter and returns analyzed result.
    """
    authentication_classes = (authentication.TokenAuthentication,)
    permission_classes = (permissions.AllowAny,)
    parser_classes = (JSONParser,)

    def post(self, request, format=None):
        try:
            self._validate_post_data(request.data)
            print("juttu", request.data["material"])
            #create pipeline from request
            pipeline = Pipeline()
            for component_name in request.data["pipeline"]:
                pipeline.add_component(component_name)

            response = pipeline.execute_pipeline(request.data['material'])
            status_code = status.HTTP_200_OK

        except Exception as e:
            response = {"message": "Please provide a proper data.",
                        "error": str(e) }
            status_code = status.HTTP_400_BAD_REQUEST

        return Response(response, status=status_code)

    def _validate_post_data(self, data):
        if "pipeline" not in data:
            raise InvalidRequest("Pipeline field is missing. Should be array of components used in analysis. Available components at /api/classifiers")

        if len(data["pipeline"]) < 1:
            raise InvalidRequest("Pipeline array is empty.")

        if "material" not in data:
            raise InvalidRequest("Material to be analyzed is missing. Please provide an array of strings.")

        if len(data["material"]) < 1:
            raise InvalidRequest("Material to be analyzed is missing, array is empty. Please provide an array of strings.")

真正有趣的部分是当我触发调试器来检查这里发生了什么。原来这条线

request.data['material']

在我的请求中给出列表的最后一个条目,在这种情况下

“Paska kesä taas. Kylmää ja sataa”

但是,当我检查 request.data 的内容时,它显示了一个查询字典,其中列出了请求中的管道和材料。为什么我在调用 request.data["material"] 时得到的是字符串而不是材料列表?有什么我忘记了,我必须指定某种序列化程序吗?为什么它在正常执行期间有效,但在测试时无效?

我将 Django 1.8 与 Python 3 结合使用。另外,我没有将视图绑定到任何特定模型。

最后,当我将断点放入视图时,调试器显示的内容如下: 请求数据:

QueryDict: {'material': ['Rakastan iloisuutta!', 'Autojen kanssa pitää olla varovainen.', 'Paska kesä taas. Kylmää ja sataa'], 'pipeline': ['Bayes']}

asd = request.data["material"]:

'Paska kesä taas. Kylmää ja sataa'

【问题讨论】:

  • Pipeline.execute_pipeline 有什么作用?
  • 它处理材料。但是,我很确定问题出在此之前,因为调试器向我展示了 execute_pipeline 在测试期间实际上获取的是字符串而不是列表(它应该获取)。另外,如果我做了一个像 asd = request.data["material"] 这样的表达式,根据调试器 asd 包含列表的最后一个条目(字符串)。
  • 如果手动测试它不会得到它?当这两个示例(打印)行位于彼此下方时,调试器输出会显示什么?

标签: python django django-rest-framework


【解决方案1】:

这是因为 QueryDict 返回 __getitem__ 中列表的最后一个值:

QueryDict.getitem(键)

返回给定键的值。如果键有多个值,getitem() 返回最后一个值。如果键不存在,则引发 django.utils.datastructures.MultiValueDictKeyError。 (这是 Python 标准 KeyError 的子类,因此您可以坚持捕获 KeyError。)

https://docs.djangoproject.com/en/1.8/ref/request-response/#django.http.QueryDict.getitem

如果您发布一个表单,其中一个键映射到一个列表:

d = {"a": 123, "b": [1,2,3]}
requests.post("http://127.0.0.1:6666", data=d)

这是您在请求正文中得到的内容:

a=123&b=1&b=2&b=3

由于测试方法将数据作为表单发布,因此您从 request.data 获得的是一个 QueryDict(与 request.POST 相同),因此您在获取 request.data 时会获得列表中的最后一个值。

要获得预期的行为,请将数据作为 JSON 发布在请求正文中(如@Vladir Parrado Cruz 的回答)。

【讨论】:

  • 感谢您的解释。看来我昨天晚上浏览文档时太累了。我最终使用了@Vladir Parrado Cruz 的答案。
【解决方案2】:

默认情况下,QueryDict 将在执行getitem 调用时从列表中返回单个项目(或通过方括号进行访问,例如您在request.data['material'] 中所做的)

您可以改为使用getlist 方法返回键的所有值:
https://docs.djangoproject.com/en/1.8/ref/request-response/#django.http.QueryDict.getlist

class ClassifyText(APIView):
    """
    Takes text snippet as a parameter and returns analyzed result.
    """
    authentication_classes = (authentication.TokenAuthentication,)
    permission_classes = (permissions.AllowAny,)
    parser_classes = (JSONParser,)

    def post(self, request, format=None):
        try:
            self._validate_post_data(request.data)
            print("juttu", request.data["material"])
            print("juttu", request.data.getlist("material"]))
            #create pipeline from request
            pipeline = Pipeline()
            for component_name in request.data["pipeline"]:
                pipeline.add_component(component_name)

            response = pipeline.execute_pipeline(request.data.getlist('material'))
            status_code = status.HTTP_200_OK

        except Exception as e:
            response = {"message": "Please provide a proper data.",
                        "error": str(e) }
            status_code = status.HTTP_400_BAD_REQUEST

        return Response(response, status=status_code)

    def _validate_post_data(self, data):
        if "pipeline" not in data:
            raise InvalidRequest("Pipeline field is missing. Should be array of components used in analysis. Available components at /api/classifiers")

        if len(data["pipeline"]) < 1:
            raise InvalidRequest("Pipeline array is empty.")

        if "material" not in data:
            raise InvalidRequest("Material to be analyzed is missing. Please provide an array of strings.")

        if len(data["material"]) < 1:
            raise InvalidRequest("Material to be analyzed is missing, array is empty. Please provide an array of strings.")

【讨论】:

    【解决方案3】:

    尝试在测试中做类似的事情:

    import json
    
    def test_classification(self):
        """ Make sure that the API will respond correctly when required url params are supplied.
        """
        response = self.client.post(
            reverse('analyzer_api:classifytext'),
            json.dumps({
                "pipeline": ["Bayes"],
                "material": [
                    "Rakastan iloisuutta!",
                    "Autojen kanssa pitää olla varovainen.",
                    "Paska kesä taas. Kylmää ja sataa",
                ]
            }),
            content_type='application/json'
        )
        self.assertTrue(status.is_success(response.status_code))
        self.assertEqual(response.data[0], 1)
    

    也许如果你将数据作为 json 发送它会起作用。

    【讨论】:

      猜你喜欢
      • 2021-08-10
      • 2014-02-10
      • 1970-01-01
      • 2022-11-02
      • 1970-01-01
      • 2020-11-12
      • 2020-07-30
      • 2014-01-21
      • 2013-10-28
      相关资源
      最近更新 更多