简体   繁体   English

使用传递参数并从rest_framework请求JSON的Django测试客户端发布方法创建单元测试

[英]Create a unit test using Django's test client post method passing parameters and requesting JSON from rest_framework

I want to instantiate a django.test.client.Client() or rest_framework.test.APIClient() , POST a simple set of parameters, and request a JSON format response from a djangorestframework class-based view. 我想实例化django.test.client.Client()rest_framework.test.APIClient() ,发布一组简单的参数,并从基于djangorestframework类的视图中请求JSON格式响应。

The documentation suggests I just instantiate APIClient() and post with the parameter format='json' : 该文档建议我只实例化APIClient()并使用参数format='json'

rest_framework.test import APIClient
apiclient = APIClient()
response = apiclient.post('/api/v1/model/1/run',
                          data=request_params, format='json')

However then my view (a DRF viewset custom method) does not receive the request parameters. 但是,然后我的视图(DRF视图集自定义方法)没有收到请求参数。 Tracing this to the view, the POST parameters do make it to request.data as a dict, but request.POST.items() returns an empty list. 将其跟踪到视图,POST参数会将其作为字典传递给request.data ,但是request.POST.items()返回一个空列表。 When I use the code below to make a POST request over AJAX from a browser, request.POST.items() returns all the parameters correctly. 当我使用以下代码从浏览器通过AJAX发出POST请求时, request.POST.items()正确返回所有参数。 It is only when using the unit test APIClient() post() method that the parameter values aren't in request.POST.items() . 仅在使用单元测试APIClient() post()方法时,参数值才不在request.POST.items()

If I use the .get() method of APIClient() , the request parameters are not in request.data when it reaches the view, but they are in request.GET.items() , passed down in QUERY_STRING . 如果我使用APIClient().get()方法,则请求参数到达视图时不在request.data ,但在QUERY_STRING传递的request.GET.items()中。 The values are moved from query string to the WSGIRequest GET QueryDict by ClientHandler. 值由ClientHandler从查询字符串移动到WSGIRequest GET QueryDict。 call in django.test.client line 115 request = WSGIRequest(environ) (Django 1.9.7). 在django.test.client行中调用 115 request = WSGIRequest(environ) (Django 1.9.7)。 This doesn't seem to be happening for APIClient() post() . 对于APIClient() post()似乎没有发生这种情况。

I tried the following: 我尝试了以下方法:

  • Passing json.dumps(request_params) to the data parameter, but same response - my view doesn't see any parameters in the request ( ref ). json.dumps(request_params)传递给data参数,但响应相同-我的视图在请求( ref )中未看到任何参数。

  • Using the Django Client, passing content_type='application/json' , with and without json.dumps, but same response. 使用Django客户端,传递content_type='application/json' ,带有和不带有json.dumps,但响应相同。

  • Using Django Client, setting post **extra parameter to HTTP_ACCEPT='application/json' (with and without json.dumps) - same response. 使用Django客户端,将post ** extra参数设置为HTTP_ACCEPT='application/json' (带有和不带有json.dumps)-相同的响应。

  • Initializing the Django Client with HTTP_ACCEPT='application/json' (with and without json.dumps) - same response. 使用HTTP_ACCEPT='application/json' (带有和不带有json.dumps)初始化Django客户端-相同的响应。

  • Leaving the Accept HTTP header, post's content_type parameter, and APIClient's format parameter undefined, and adding {'format':'json'} to the request_params - which works for Client.get requests , my code sees request parameters, but rest_framework returns HTML. 保留Accept HTTP标头,post的content_type参数和APIClient的format参数未定义,并向request_params添加{'format':'json'} - 适用于Client.get请求 ,我的代码看到请求参数,但是rest_framework返回HTML。 The JSON rendered in this HTML shows the code is working correctly (returns status 202 and a polling URL, as it should). 在此HTML中呈现的JSON显示代码正常工作(应返回状态202和轮询URL)。

  • Appending .json to the URL in the unit test and leaving content type, etc, at their defaults, but I get Not Found: /api/v1/model/1/run/.json from get_response. .json附加到单元测试中的URL上,并将内容类型等保留为默认值,但我从get_response处Not Found: /api/v1/model/1/run/.json

My code works fine accepting AJAX POST requests through the browser, and my unit tests were working fine when I was using client.get(). 我的代码可以很好地通过浏览器接受AJAX POST请求,而当我使用client.get()时,我的单元测试也可以正常工作。 It is only the combination of using client.post() and needing JSON back that I cannot get working. 仅仅是使用client.post()和需要JSON返回的组合,我无法使用。

I extract the request values with: 我使用以下命令提取请求值:

if request.method == 'POST':
    form_values = ((key, value) for key, value in request.POST.items())
else:
    form_values = ((key, value) for key, value in request.GET.items())

The Javascript that sends the AJAX request, that succeeds and returns JSON , is as follows: 发送成功并返回JSON的AJAX请求的Javascript如下:

// Setup at the bottom of the HTML body
$(document).ready(function(){
    $.ajaxSetup({
      data: {csrfmiddlewaretoken: "{{ csrf_token }}", format: "json" }
    });
    $.ajaxSetup({
        beforeSend: function (xhr, settings) {
            xhr.setRequestHeader("Accept", "application/json");
            if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
            }
        }
    });
});

// Code that makes the request url=/api/v1/model/1/run, method=post 
// Only POST is permitted on the view method by decorator @detail_route(methods=['post']))
function run_model(event)
{
    var form = $(event.target);

    $.ajax({
        type: form.attr('method'),
        url: form.attr('action'),
        data: $("#" + form.attr('id')).serialize() + "&format=json&csrfmiddlewaretoken={{ csrf_token }}"
    })
    .done(function (data, status, jqXHR) {
        poll_instance(data.instance_id, data.model_id);
    })
    .fail(function (jqXHR, status, err) {
        var status_div = $("." + construct_div_class("model", "div", jqXHR.responseJSON.model_id)).children("div.status");
        if (catch_ajax_error(status_div, failed_tries, jqXHR, status, err)) {
            setTimeout(run_model, 3000, event);
        };
    });

    event.preventDefault();
};

The Accept header was what got this working, format=json didn't work. Accept标头是使此工作正常的原因,format = json不起作用。

This is the receiving view: 这是接收视图:

class ModelViewSet(viewsets.ModelViewSet):
    @detail_route(methods=['post'])
    def run(self, request, *args, **kwargs):
        """
        Runs a model and redirects to the URL that will return the output results when ready.
        """
        try:
            instance_id = run_model(request, self.get_object().id)

        except ParameterValidationError as e:

        # ...    

        return Response(data={'instance_id': instance_id, 'model_id': self.get_object().id},
                        status=status.HTTP_202_ACCEPTED)

The form, whose submit is tied to run_model() above: 该表单的提交与上面的run_model()关联:

<form method="POST" action="/api/v1/model/3/run/" id="model-form-3">
    <table class="table table-striped table-bordered table-hover">
        <tbody><tr>
            <th>
                Model
            </th>
            <th>
                Parameter
            </th>
            <th>
                Value
            </th>
        </tr>
         <tr>
             <td>
                 Source source model of Composite (model #2)
             </td>
             <td>
                 GUI dim value in for POC model #89
             </td>
             <td>
                 <select name="5_77" id="5_77">
                        <option value="13">
                            Dimension description #17
                        </option>
                        <option value="14">
                            Dimension description #18
                        </option>
                </select>
             </td>
         </tr>
         <tr>
             <td>
                 Source model of Composite (model #1)
             </td>
             <td>
                 Decimal GUI value in for POC model #64
             </td>
             <td>
                    <input name="4_52" id="4_52" value="123456789" type="text">
             </td>
         </tr>
         <tr>
             <td>
                 Second source model of Composite (model #3)
             </td>
             <td>
                 GUI dim value in for POC model #112
             </td>
             <td>
                 <select name="6_100" id="6_100">
                        <option value="16">
                            Dimension description #20
                        </option>

                        <option value="17">
                            Dimension description #21
                        </option>
                    </select>
             </td>
         </tr>
         <tr>
             <td>
                 Dependent of Composite (model #0)
             </td>
             <td>
                 GUI dim value in for POC model #45
             </td>
             <td>
                 <select name="3_33" id="3_33">
                        <option value="7">
                            Dimension description #11
                        </option>
                        <option value="8">
                            Dimension description #12
                        </option>
                    </select>
             </td>
         </tr>
         <tr>
             <td>
                 Dependent of Composite (model #0)
             </td>
             <td>
                 Decimal GUI value in for POC model #43
             </td>
             <td>
                    <input name="3_31" id="3_31" value="123456789" type="text">
             </td>
         </tr>
    </tbody></table>
    <input value="Run model" type="submit"><br><br>
</form>

I'm on Python 3.5, Django 1.9.7, djangorestframework 3.4.0 (also happened in 3.2.1), djangorestframework-xml 1.3.0, debugging in PyCharm 2016.1 我正在使用Python 3.5,Django 1.9.7,djangorestframework 3.4.0(也在3.2.1中发生),djangorestframework-xml 1.3.0,在PyCharm 2016.1中进行调试

Turns out the AJAX data is supposed to appear in request.data , and I was using the wrong approach to submit the data via AJAX from the browser. 原来AJAX数据应该出现在request.data ,而我使用了错误的方法来从浏览器通过AJAX提交数据。 Django rest_framework (DRF) assumes that data from the request will be passed in the same format as data returned to the client - in this case JSON both ways. Django rest_framework(DRF)假定来自请求的数据将以与返回给客户端的数据相同的格式传递-在这种情况下,双向都是JSON。 As it assumes that for an Accept=application/json request, incoming data will be in JSON format, it automatically parses it and populates request.data for you, and request.GET and request.POST are empty by the time the request reaches your DRF view. 假设对于Accept=application/json请求,传入数据将为JSON格式,它将自动对其进行解析并为您填充request.data ,并且在请求到达您的请求时request.GETrequest.POST为空DRF视图。

To pass a form of data in the AJAX request I use the jquery form plugin's .formSerialize() method. 为了在AJAX请求中传递数据形式,我使用了jQuery表单插件的 .formSerialize()方法。

I did just have a .map() compile a dictionary from a form, but this won't work for radios and other instances where you might have several values for a single key/form id. 我只是有一个.map()从表单编译字典,但这不适用于收音机和其他可能为单个键/表单ID包含多个值的实例。

Credit for this answer should really go to @dhke, who pointed out my fundamental error. 这个答案应该归功于@dhke,他指出了我的基本错误。 Although perhaps this question should be deleted. 尽管也许这个问题应该删除。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM