Django请求数据返回字符串而不是列表

5

我正在使用Django和REST框架开发REST API。我有一个端点,可以接收这种JSON格式的POST请求:

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

这是一个机器学习分析API,JSON指示使用贝叶斯分类器提供字符串并返回结果。当我手动进行POST请求测试时,它运行得很好。然而,当我尝试编写单元测试时,它就会崩溃。以下是我的测试:

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["material"]时会得到字符串而不是材料列表?我是否忘记了什么,并且我必须指定某种序列化器?为什么它在正常执行期间可以工作,但在测试期间无法正常工作?

我正在使用Python 3的Django 1.8版本。此外,我没有将视图绑定到任何特定的模型。

最后,这是我在视图中设置断点时调试器显示的内容: request.data:

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是什么意思? - Lucas Moeskops
它处理材料。然而,我相当确定问题出现在那之前,因为调试器向我展示了在测试期间execute_pipeline实际上获取了字符串而不是列表(它应该获取列表)。另外,如果我像asd = request.data ["material"]这样做一个表达式,根据调试器,asd包含列表的最后一个条目(字符串)。 - Tumetsu
它如果手动测试就无法得到结果吗?当这两个示例(print)行彼此相邻时,调试器输出的内容是什么? - Lucas Moeskops
3个回答

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

QueryDict.getitem(key)

返回给定键的值。如果该键具有多个值,则 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的答案。 - Tumetsu

6
默认情况下,QueryDict 在进行 getitem 调用时 (或通过方括号访问,例如在 request.data['material'] 中所做的方式) 会返回列表中的单个项目。您可以使用 getlist 方法来返回键的所有值: https://docs.djangoproject.com/en/1.8/ref/request-response/#django.http.QueryDict.getlist。请注意,保留 HTML 标记。
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格式,它就能正常工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接