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 responds with 500 Server Error, which tells the program nothing about what to fix.
When the data is the problem, respond with 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 "". A body that is not JSON makes json.loads raise json.JSONDecodeError. Catch it with try and except, and respond with 400 instead of crashing.
Challenge
MediumMake POST /api/tasks/ refuse bad data with status 400.
- In
task_list, catchjson.JSONDecodeErroraroundjson.loadsand respond with{"error": "The body is not valid JSON."}. - If the title is missing or blank once stripped, save nothing and respond with
{"errors": {"title": "This field is required."}}. - Otherwise create it with the stripped title.
In the Shell tab, the line below prints This field is required.
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