Repository: django-json-api/django-rest-framework-json-api

Issue:
JSONRenderer modifies data when status_code is `204 NO CONTENT`
`JSONRenderer` modifies the data to be renedered, from `None` to `{'data': None}` when `response.status_code` is 204.

From [HTTP's RFC](https://tools.ietf.org/html/rfc2616#section-4.3) I see that:
> All responses to the HEAD request method
   MUST NOT include a message-body, even though the presence of entity-
   header fields might lead one to believe they do. All 1xx
   (informational), 204 (no content), and 304 (not modified) responses
   MUST NOT include a message-body

This behavior made some clients to crash when the response stauts is `204 NO CONTENT` and the header `Content-Length` is different from 0 (in this case 13 since the response body is `{"data":null}`).


Discussion:

Resolving patch:
diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py
--- a/rest_framework_json_api/renderers.py
+++ b/rest_framework_json_api/renderers.py
@@ -426,6 +426,14 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
         if resource_name == 'errors':
             return self.render_errors(data, accepted_media_type, renderer_context)
 
+        # if response.status_code is 204 then the data to be rendered must
+        # be None
+        response = renderer_context.get('response', None)
+        if response is not None and response.status_code == 204:
+            return super(JSONRenderer, self).render(
+                None, accepted_media_type, renderer_context
+            )
+
         from rest_framework_json_api.views import RelationshipView
         if isinstance(view, RelationshipView):
             return self.render_relationship_view(data, accepted_media_type, renderer_context)


Test patch:
diff --git a/example/tests/test_views.py b/example/tests/test_views.py
--- a/example/tests/test_views.py
+++ b/example/tests/test_views.py
@@ -164,6 +164,7 @@ def test_post_to_many_relationship_with_no_change(self):
         }
         response = self.client.post(url, data=json.dumps(request_data), content_type='application/vnd.api+json')
         assert response.status_code == 204, response.content.decode()
+        assert len(response.rendered_content) == 0, response.rendered_content.decode()
 
     def test_post_to_many_relationship_with_change(self):
         url = '/entries/{}/relationships/comment_set'.format(self.first_entry.id)
@@ -207,6 +208,7 @@ def test_delete_to_many_relationship_with_no_change(self):
         }
         response = self.client.delete(url, data=json.dumps(request_data), content_type='application/vnd.api+json')
         assert response.status_code == 204, response.content.decode()
+        assert len(response.rendered_content) == 0, response.rendered_content.decode()
 
     def test_delete_one_to_many_relationship_with_not_null_constraint(self):
         url = '/entries/{}/relationships/comment_set'.format(self.first_entry.id)
@@ -253,3 +255,15 @@ def _get_create_response(self, data, view):
         user = self.create_user('user', 'pass')
         force_authenticate(request, user)
         return view(request)
+
+
+class TestModelViewSet(TestBase):
+    def setUp(self):
+        self.author = Author.objects.create(name='Super powerful superhero', email='i.am@lost.com')
+        self.blog = Blog.objects.create(name='Some Blog', tagline="It's a blog")
+
+    def test_no_content_response(self):
+        url = '/blogs/{}'.format(self.blog.pk)
+        response = self.client.delete(url)
+        assert response.status_code == 204, response.rendered_content.decode()
+        assert len(response.rendered_content) == 0, response.rendered_content.decode()