45

I'm using django-rest-framework (latest) for REST API, and implemented few test cases in django using built in test client.

following django test case was working fine with django version < 1.5

self.client.put('/core/accounts/%s/'% self.account.id,
        data = prepare_dict(self.account),
        HTTP_AUTHORIZATION=self.token)

upgraded to django 1.5, all tests are passing except tests related to HTTP PUT while looking into the issue found this @ https://docs.djangoproject.com/en/dev/releases/1.5/#options-put-and-delete-requests-in-the-test-client

If you were using the data parameter in a PUT request without a content_type, you must encode your data before passing it to the test client and set the content_type argument.

So, updated my test to reflect this change and tried following, but still getting http 415 instead of http 200

from django.test.client import MULTIPART_CONTENT, BOUNDARY, encode_multipart
self.client.put('/core/accounts/%s/'% self.account.id,
            data = encode_multipart(BOUNDARY, prepare_dict(self.account)),
                content_type=MULTIPART_CONTENT,
        HTTP_AUTHORIZATION=self.token)

Any idea what I'm missing? P.S: All functionality is working fine from django-rest-framework built-in web UI

Narendra Kamma
  • 1,431
  • 1
  • 11
  • 19

3 Answers3

51

You're absolutely on the right track - the breaking test in that case is certainly due to Django's change in PUT behavior for the test client.

Your fix looks right to me, too. 415 is the "Unsupported Media Type" response, which means that the request content type wasn't something that could be handled by any of the parsers configured for the view.

Normally in case like this, that'd be due to forgetting to set the content type of the request, but it looks like you've got that correctly set to multipart/form-data; boundary=...

Things to double check:

  • Exactly what does response.data display as the error details?
  • What do you have configured in you DEFAULT_PARSER_CLASSES setting, if you have one, or what do you have set on the view attribute parser_classes if it has one?
  • Make sure there's not a typo in content_type in the test (even though it's correct here).

Edit:

Thanks for your comments - that clears everything up. You've only got the JSON parser installed, but you're trying to send Form encoded data. You should either:

  • Add FormParser and MultiPartParser to your settings/view, so that it supports form encodings. (Note also that the default DEFAULT_PARSER_CLASSES setting does include them, so if you don't set anything at all it'll work as expected)

Or

  • Encode the request using json encoding, not form encoding... data=json.dumps(prepare_dict(self.account)), content_type='application/json' in your test case.
Tom Christie
  • 33,394
  • 7
  • 101
  • 86
  • 1
    Tom, first of all thanks for djnago-rest-framwork. As you have asked I double checked. response.data = {u'detail': u"Unsupported media type 'multipart/form-data; boundary=BoUnDaRyStRiNg' in request."} 2. Default parser set to 'rest_framework.parsers.JSONParser' 3. No parser_classes as view attribute. – Narendra Kamma Mar 01 '13 at 09:59
  • set parser_classes = (JSONParser,) for view, but no difference – Narendra Kamma Mar 01 '13 at 10:28
  • 1
    OK, removing default_parser (JSON) from settings did the magic. Digging into framework, this line in 306@request.py parser = self.negotiator.select_parser(self, self.parsers) is unable to negotiate proper parser, when default parser (JSON) is set and content-type is 'multipart/form-data' – Narendra Kamma Mar 01 '13 at 10:45
  • 2
    Yes, I should mark your answer as correct. But for the sake of record here is where I went wrong. Till now I thought that, settings.DEFAULT_PARSER_CLASSES is to set default parser when nothing found. Now I realized that, it should contain all possible parsers you want to use. Because it uses those settings as ultimate list, if available. Now I set JSON/Form/Multipart as default, and everything is fine. Thanks. Default got multiple meanings :) – Narendra Kamma Mar 01 '13 at 10:55
  • I had forgotten to put MultiPartParser in @parser_classes([...]), stupid me. tnx. – Iman Akbari Mar 09 '16 at 13:34
  • 3
    I had this issue on DRF 3.3 and it was because I was using `django.test.TestCase` and not `rest_framework.test.APITestCase`. Once I made the change, the issue went away. – Aaron Lelevier Jun 09 '16 at 18:47
  • Settings example is provided in [DRF docs](http://www.django-rest-framework.org/api-guide/settings/#default_parser_classes) – juliocesar Feb 06 '18 at 15:47
10

Got a 415 error because I used an instance of django.test import Client instead of rest_framework.test import APIClient. APIClient will encode the data automatically in it's right way.

Pure json request:

client = APIClient()
client.post(url, format='json', data=json, headers=headers)
client.put(url, format='json', data=json, headers=headers)

Create/Update including file(s):

client = APIClient()
client.post(url, format='multipart', data=data, headers=headers)
client.put(url, format='multipart', data=data, headers=headers)

I spent a lot of time with this 415 error, so I hope this helps anyone else.

Tobias Ernst
  • 4,214
  • 1
  • 32
  • 30
0

I got the error 415 while testing POST on Django REST Framework using JSON format. This is what I did to fix it:

I added these Django REST Framework settings to settings.py

REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
    ],
    'TEST_REQUEST_DEFAULT_FORMAT': 'json',
}

https://www.django-rest-framework.org/api-guide/settings/

In the test file:

from rest_framework.test import RequestsClient
from django.test import TestCase


class MyTestCase(TestCase):

    def test_post(self):
        url = 'http://localhost:8000/api/'
        my_data = {'key_1': 1, 'key_2': 'a_string'}

        client = RequestsClient()

        response = client.post(url, json=my_data, 
                        headers={'content-type': 'application/json'})
        
        status_code = response.status_code
        data = response.json()

Python and packages version:

  • python 3.8.0
  • Django==3.0.6
  • djangorestframework==3.11.0

This is not the solution for the question, but since I ended up here after some search, maybe this can help someone else.

angelacpd
  • 564
  • 6
  • 9