pythondjangodjango-rest-frameworkdjango-webtest

Django Views: When is request.data a dict vs a QueryDict?


I have run into some trouble with the issue, that request.data sometimes is a dict (especially when testing) and sometimes a QueryDict instance (when using curl).

This is especially a problem because apparently there is a big difference when calling a view using curl like so:

curl -X POST --data "some_float=1.23456789012123123" "http://localhost:8000/myview"

Or using the django_webtest client like so:

class APIViewTest(WebTest):
    def test_testsomething(self):
        self.app.post(url=url, params=json.dumps({some_float=1.26356756467}))

And then casting that QueryDict to a dict like so

new_dict = dict(**request.data)
my_float = float(new_dict['some_float'])

Everything works fine in the tests, as there request.data is a dict, but in production the view crashes because new_dict['some_float'] is actually a list with one element, and not as expected a float.

I have considered fixing the issue like so:

    if type(request.data) is dict:
        new_dict = dict(**request.data)
    else:
        new_dict = dict(**request.data.dict())

which feels very wrong as the tests would only test line 2, and (some? all?) production code would run line 4.

So while I am wondering why QueryDict behaves in this way, I would rather know why and when response.data is a QueryDict in the first place. And how I can use django tests to simulate this behavior. Having different conditions for production and testing systems is always troublesome and sometimes unavoidable, but in this case I feel like it could be fixed. Or is this a specific issue related to django_webtest?


Solution

  • When your request content_type is "application/x-www-form-urlencoded", request.Data become QueryDict.

    see FormParser class.
    https://github.com/encode/django-rest-framework/blob/master/rest_framework/parsers.py

    And

    QueryDict has get lists method. but it can't fetch dict value.
    convert name str to array.

    <input name="items[name]" value="Example">
    <input name="items[count]" value="5">  
    

    https://pypi.org/project/html-json-forms/

    And define custom form paser.

    class CustomFormParser(FormParser):
    """
    Parser for form data.
    """
    media_type = 'application/x-www-form-urlencoded'
    
    def parse(self, stream, media_type=None, parser_context=None):
        """
        Parses the incoming bytestream as a URL encoded form,
        and returns the resulting QueryDict.
        """
        parser_context = parser_context or {}
        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
        data = QueryDict(stream.read(), encoding=encoding)
        return parse_json_form(data.dict()) # return dict
    

    And overwite DEFAULT_PARSER_CLASSES.
    https://www.django-rest-framework.org/api-guide/settings/#default_parser_classes