Request and Response: DRF's upgrades over Django's own objects
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
@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 dataDjango'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
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
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
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.
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.