JSON Validation Errors
Part of the JSON APIs section of Coddy's Django journey. Lesson 7 of 19.
Right now a POST without a title crashes with KeyError, and Django answers 500 Server Error, which tells the program nothing about what to fix.
When the data is the problem, answer 400 Bad Request and say what is wrong in JSON, one message per field, the way a Django form keeps its errors. A bookshop checking a book's year:
errors = {}
year = data.get("year")
if not isinstance(year, int) or year < 0:
errors["year"] = "Enter the year as a whole number."
if errors:
return JsonResponse({"errors": errors}, status=400)Two traps remain. A title of only spaces is not empty, but " ".strip() is "". And a body that is not JSON makes json.loads raise json.JSONDecodeError, which try and except turn into a 400. A 4xx status means the caller sent something wrong; a 5xx means your code broke.
Challenge
MediumMake POST /api/tasks/ answer 400 for bad data:
- No title, or only spaces:
{"errors": {"title": "This field is required."}}, and nothing saved. - A body that is not JSON:
{"error": "The body is not valid JSON."}. - A good title is saved without the spaces around it.
Press Run, then in the Shell tab: from django.test import Client; print(Client().post("/api/tasks/", {"title": " "}, content_type="application/json").json())
Try it yourself
import json
from django.http import JsonResponse
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from .models import Task
def task_to_dict(task):
return {
"id": task.id,
"title": task.title,
"done": task.done,
"priority": task.priority,
}
def home(request):
return render(request, "tasks/home.html", {"tasks": Task.objects.all()})
def stats(request):
data = {
"total": Task.objects.count(),
"done": Task.objects.filter(done=True).count(),
}
return JsonResponse(data)
@csrf_exempt
def task_list(request):
if request.method == "POST":
data = json.loads(request.body)
task = Task.objects.create(
title=data["title"],
priority=data.get("priority", "normal"),
)
return JsonResponse(task_to_dict(task), status=201)
tasks = Task.objects.all()
done = request.GET.get("done")
if done == "true":
tasks = tasks.filter(done=True)
elif done == "false":
tasks = tasks.filter(done=False)
return JsonResponse([task_to_dict(task) for task in tasks], safe=False)
def task_detail(request, pk):
task = Task.objects.filter(pk=pk).first()
if task is None:
return JsonResponse({"error": "Task not found"}, status=404)
return JsonResponse(task_to_dict(task))
All lessons in JSON APIs
2Reading Requests
Filter with a QueryCreating with POSTPractice on your own: Django playground