使用API​​RequestFactory测试GET端点时的MultiValueDictKeyError

时间:2016-03-28 23:44:03

标签: python django django-views django-rest-framework django-serializer

我有一个GET端点,我尝试使用APIRequestFactory进行测试,遵循此SO帖子中描述的方法:https://stackoverflow.com/a/28429089

首先,我知道这个端点有效,因为当我通过cURL发送GET请求时,我得到预期的行为。

curl -X GET -H "Content-Type: application/json" -i -d "{\"cardinal_key\":12XXXX3, \"cardinal_type\":\"Npis\", \"terminal_type\":\"Deas\", \"candidate_count\":2}" http://127.0.0.1:8000/v1/ranked_results/
HTTP/1.0 200 OK
Date: Mon, 28 Mar 2016 22:29:29 GMT
Server: WSGIServer/0.2 CPython/3.4.3
X-Frame-Options: SAMEORIGIN
Allow: GET, HEAD, OPTIONS
Vary: Accept, Cookie
Content-Type: application/json

{"npi":"12XXXX3","first_name":"XXXXXX","middle_name":null,"last_name":"XXXXXX","full_name":"XXXXX, XXXX XXXXX","link_candidates":{"FXXXXX9":0.9900022451986468,"BXXXXXXX3":0.8483023431385707}}

然而,当我尝试使用工厂模拟此请求时,我得到ValueError例外(下面的完整描述):

from rest_framework.test import APIRequestFactory
from linker.views import RankedResults
factory = APIRequestFactory()
options = {"cardinal_key":12XXXX3, "cardinal_type":"Npis", "terminal_type":"Deas", "candidate_count":2}
request = factory.get('/v1/ranked_results/', json.dumps(options), content_type='application/json')
view = RankedResults.as_view()
response = view(request)

ValueError: need more than 1 value to unpack

我的下一个想法是,因为我试图将请求作为JSON发送,所以有些东西无效,所以我尝试在没有JSON的情况下发出请求,然后我得到了MultiValueDictKeyError

from rest_framework.test import APIRequestFactory
from linker.views import RankedResults
factory = APIRequestFactory()
options = {"cardinal_key":12XXXX3, "cardinal_type":"Npis", "terminal_type":"Deas", "candidate_count":2}
request = factory.get('/v1/ranked_results/', options)
view = RankedResults.as_view()
response = view(request)

MultiValueDictKeyError: "'cardinal_type'"

我对MultiValueDictKeyError异常的理解是当密钥不在字典中时会发生。此外,文档说MultiValueDict用于具有相同键的多个值的情况 - 我不知道为什么我看到这个,因为这不是请求的情况我是试图创造。

我在这里不知所措 - 我错过了什么?

视图:

class RankedResults(APIView):
    def get(self, request):
        cardinal_instance = apps.get_model('warehouse', request.data['cardinal_type']).objects.get(reference_key=request.data['cardinal_key'])
        serializer = LinkModelSerializer(cardinal_instance, context=request.data)
        return Response(serializer.data)

串行器:

class LinkModelSerializer(serializers.ModelSerializer):
    link_candidates = serializers.SerializerMethodField('get_candidate_links')

    def get_candidate_links(self, obj):
        terminal = apps.get_model('warehouse', self.context['terminal_type'])
        n = self.context['candidate_count']
        return dict(obj.top_n_candidates(terminal, n))

    class Meta:
        model = warehouse.Npis
        fields = ('npi', 'first_name', 'middle_name', 'last_name', 'full_name', 'link_candidates')
        read_only_fields = ('link_candidates')

跟踪我尝试发送JSON请求的情况:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-20-6b10ed2040c0> in <module>()
      2 factory = APIRequestFactory()
      3 options = {"cardinal_key":12XXXX3, "cardinal_type":"Npis", "terminal_type":"Deas", "candidate_count":2}
----> 4 request = factory.get('/v1/ranked_results/', json.dumps(options), content_type='application/json')
      5 view = RankedResults.as_view()
      6 response = view(request)

~/lib/python3.4/site-packages/rest_framework/test.py in get(self, path, data, **extra)
     79     def get(self, path, data=None, **extra):
     80         r = {
---> 81             'QUERY_STRING': urlencode(data or {}, doseq=True),
     82         }
     83         # Fix to support old behavior where you have the arguments in the url

~/lib/python3.4/site-packages/django/utils/http.py in urlencode(query, doseq)
     94         [(force_str(k),
     95          [force_str(i) for i in v] if isinstance(v, (list, tuple)) else force_str(v))
---> 96             for k, v in query],
     97         doseq)
     98 

~/lib/python3.4/site-packages/django/utils/http.py in <listcomp>(.0)
     94         [(force_str(k),
     95          [force_str(i) for i in v] if isinstance(v, (list, tuple)) else force_str(v))
---> 96             for k, v in query],
     97         doseq)
     98 

ValueError: need more than 1 value to unpack

...最后是我在没有JSON请求时尝试的跟踪:

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
~/lib/python3.4/site-packages/django/utils/datastructures.py in __getitem__(self, key)
     82         try:
---> 83             list_ = super(MultiValueDict, self).__getitem__(key)
     84         except KeyError:

KeyError: 'cardinal_type'

During handling of the above exception, another exception occurred:

MultiValueDictKeyError                    Traceback (most recent call last)
<ipython-input-17-559ee170e692> in <module>()
      4 request = factory.get('/v1/ranked_results/', options)
      5 view = RankedResults.as_view()
----> 6 response = view(request)

~/lib/python3.4/site-packages/django/views/decorators/csrf.py in wrapped_view(*args, **kwargs)
     56     # function.
     57     def wrapped_view(*args, **kwargs):
---> 58         return view_func(*args, **kwargs)
     59     wrapped_view.csrf_exempt = True
     60     return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view)

~/lib/python3.4/site-packages/django/views/generic/base.py in view(request, *args, **kwargs)
     66             self.args = args
     67             self.kwargs = kwargs
---> 68             return self.dispatch(request, *args, **kwargs)
     69         view.view_class = cls
     70         view.view_initkwargs = initkwargs

~/lib/python3.4/site-packages/rest_framework/views.py in dispatch(self, request, *args, **kwargs)
    464 
    465         except Exception as exc:
--> 466             response = self.handle_exception(exc)
    467 
    468         self.response = self.finalize_response(request, response, *args, **kwargs)

~/lib/python3.4/site-packages/rest_framework/views.py in dispatch(self, request, *args, **kwargs)
    461                 handler = self.http_method_not_allowed
    462 
--> 463             response = handler(request, *args, **kwargs)
    464 
    465         except Exception as exc:

~/views.py in get(self, request)
     36 class RankedResults(APIView):
     37     def get(self, request):
---> 38         cardinal_instance = apps.get_model('warehouse', request.data['cardinal_type']).objects.get(reference_key=request.data['cardinal_key'])
     39         serializer = LinkModelSerializer(cardinal_instance, context=request.data)
     40         return Response(serializer.data)

~/lib/python3.4/site-packages/django/utils/datastructures.py in __getitem__(self, key)
     83             list_ = super(MultiValueDict, self).__getitem__(key)
     84         except KeyError:
---> 85             raise MultiValueDictKeyError(repr(key))
     86         try:
     87             return list_[-1]

MultiValueDictKeyError: "'cardinal_type'"

1 个答案:

答案 0 :(得分:0)

来自request.data的文档:

  

request.data返回请求正文的已解析内容。这类似于标准request.POSTrequest.FILES属性...

因此,request.data仅在您在POSTPUTPATCH请求中传递请求正文中的内容时才可用。

但您正在提出GET请求:

request = factory.get('/v1/ranked_results/', json.dumps(options), 
                      content_type='application/json')

对于GET请求,获取查询字符串的正确方法是使用request.query_params

  

request.query_paramsrequest.GET的更正确命名的同义词。

     

为了清晰显示代码,我们建议使用request.query_params代替Django的标准request.GET。这样做有助于保持代码库更加正确和明显 - 任何HTTP方法类型都可能包含查询参数,而不仅仅是GET个请求。

因此,您将能够访问您想要的内容:

cardinal_type = request.query_params['cardinal_type']
# and similarly others