~/TechPurAI
~/tutorials/django-rest-framework/request-and-response
beginner·part 5 of 22·2 min read

Request and Response: DRF's upgrades over Django's own objects

Updated Aug 16, 2026Python · Django

Part 4 used request.data and Response(...) without pausing on what makes them different from Django's own request.POST and HttpResponse. They're not aliases — DRF wraps both specifically for API work, and knowing what each upgrade actually does is what makes the rest of this series's code make sense on sight.

request.data vs. request.POST

python
@api_view(["POST"])
def example(request):
    request.data     # works for JSON, form data, multipart — whatever was sent
    request.POST     # only ever populated for traditional form-encoded data

Django's request.POST only parses application/x-www-form-urlencoded or multipart/form-data bodies — a JSON request body leaves it empty. request.data is DRF's replacement: it inspects the request's Content-Type and parses accordingly, so the exact same view code handles a JSON API client, an HTML form, and a file upload without branching on how the data arrived.

Response — no json.dumps required

python
from rest_framework.response import Response

return Response({"status": "ok"})
return Response(serializer.data, status=201)

Django's HttpResponse needs the body already serialized: HttpResponse(json.dumps(data), content_type="application/json"). Response takes plain Python data — a dict, a list, a serializer's .data — and renders it based on content negotiation (next section), deferring the actual json.dumps()-equivalent work until the response is actually sent.

Content negotiation, concretely

bash
curl http://127.0.0.1:8000/api/tasks/ -H "Accept: application/json"
curl http://127.0.0.1:8000/api/tasks/ -H "Accept: text/html"

The first request gets plain JSON back; the second gets the browsable API's HTML. Both hit the exact same view and the exact same Response(serializer.data) call — DRF's negotiation layer reads the Accept header and picks a renderer accordingly, which is also why part 4's browser test and curl test returned different formats without any code difference between them: a browser's default Accept header prefers HTML, curl's doesn't specify a preference and gets DRF's default (JSON).

Status codes as part of the same import

python
from rest_framework import status

return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

status.HTTP_400_BAD_REQUEST is just the integer 400 — using the named constant instead of the bare number is purely for readability, and it's the DRF convention every example in this series follows from here on. Part 6 goes through the specific codes worth knowing by name.

Common mistake

Reaching for Django's JsonResponse inside a DRF view instead of Response. It technically returns JSON, but it skips DRF's content negotiation and browsable-API rendering entirely — inside any view decorated with @api_view or built on DRF's generic classes, Response is always the right return type.

Next: the specific status codes an API actually needs, and what each one is supposed to communicate.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous4. Function-based API views with @api_viewnext →6. Status codes done properly with rest_framework.status