Repository: encode/django-rest-framework

Issue:
returning QueryDict with content-type application/json and empty body
## Checklist

- [X] I have verified that that issue exists against the `master` branch of Django REST framework.
- [X] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
- [X] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
- [X] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](http://www.django-rest-framework.org/topics/third-party-resources/#about-third-party-packages) where possible.)
- [X] I have reduced the issue to the simplest possible case.
- [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

## Steps to reproduce
Hi, is this line correct? 5677d06#diff-6dc9b019ec1d96c56e0e31dac3bba51cR302

This line of code was added at this pull request.

if media_type and not is_form_media_type(media_type):
      empty_data = QueryDict('', encoding=self._request._encoding)
else:
      empty_data = {}

## Expected behavior
I suppose returning QueryDict should be only for form data type, not for application/json. 
## Actual behavior
In my case I was sending empty request with content-type application/json and received immutable QueryDict(). Is this correct behaviour?


Discussion:
Hi @Reskov. Yep - I think you're correct here. The condition should be
```python
# this
if media_type and is_form_media_type(media_type):
    ...

# not this
if media_type and not is_form_media_type(media_type):
    ...

```

I did a little debugging, and it looks like the test client isn't setting the request's `content_type`. Because of this, the condition fails on the first part of the expression and not on the `is_form_media_type` check.

Digging further, this is a behavior of Django's builtin `RequestFactory`. The `Content-Type` header is only set if data is [provided](https://github.com/django/django/blob/1.11/django/test/client.py#L402-L407). 

----

~~To be more explicit, this is an inconsistency between actual clients and the test client. It is possible for a client to specify a `Content-Type` without providing a message. Django's request factory will not set the header if no data is present.~~ 



Resolving patch:
diff --git a/rest_framework/request.py b/rest_framework/request.py
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -303,7 +303,7 @@ def _parse(self):
             stream = None
 
         if stream is None or media_type is None:
-            if media_type and not is_form_media_type(media_type):
+            if media_type and is_form_media_type(media_type):
                 empty_data = QueryDict('', encoding=self._request._encoding)
             else:
                 empty_data = {}


Test patch:
diff --git a/rest_framework/test.py b/rest_framework/test.py
--- a/rest_framework/test.py
+++ b/rest_framework/test.py
@@ -227,6 +227,15 @@ def options(self, path, data=None, format=None, content_type=None, **extra):
         data, content_type = self._encode_data(data, format, content_type)
         return self.generic('OPTIONS', path, data, content_type, **extra)
 
+    def generic(self, method, path, data='',
+                content_type='application/octet-stream', secure=False, **extra):
+        # Include the CONTENT_TYPE, regardless of whether or not data is empty.
+        if content_type is not None:
+            extra['CONTENT_TYPE'] = str(content_type)
+
+        return super(APIRequestFactory, self).generic(
+            method, path, data, content_type, secure, **extra)
+
     def request(self, **kwargs):
         request = super(APIRequestFactory, self).request(**kwargs)
         request._dont_enforce_csrf_checks = not self.enforce_csrf_checks
diff --git a/tests/test_testing.py b/tests/test_testing.py
--- a/tests/test_testing.py
+++ b/tests/test_testing.py
@@ -274,3 +274,12 @@ def test_request_factory_url_arguments_with_unicode(self):
         assert dict(request.GET) == {'demo': ['testé']}
         request = factory.get('/view/', {'demo': 'testé'})
         assert dict(request.GET) == {'demo': ['testé']}
+
+    def test_empty_request_content_type(self):
+        factory = APIRequestFactory()
+        request = factory.post(
+            '/post-view/',
+            data=None,
+            content_type='application/json',
+        )
+        assert request.META['CONTENT_TYPE'] == 'application/json'