Menu
CoddyTech

One Class per Resource

Part of the JSON APIs section of Coddy's Django journey. Lesson 11 of 19.

task_detail is now a chain of if request.method == ... blocks plus a list of methods to keep in step with them. Django's base class View, under every generic view you used, does that bookkeeping.

Give the class one method per HTTP method, named in lowercase. View calls the one named after the request and answers 405 for any other. Values from the URL arrive after request, as in a function view, and the route uses .as_view(). A visitor's colour theme as a resource:

@method_decorator(csrf_exempt, name="dispatch")
class Theme(View):
    def get(self, request):
        return JsonResponse({"theme": request.session.get("theme", "light")})

    def put(self, request):
        data = json.loads(request.body)
        request.session["theme"] = data["theme"]
        return JsonResponse({"theme": data["theme"]})

@csrf_exempt works on functions. On a class, method_decorator puts it on dispatch, the method every request passes through.

challenge icon

Challenge

Medium

Turn task_detail into a class TaskDetail(View):

  • Methods get, patch and delete, each doing what the function did, 404 included.
  • Exempt from CSRF with method_decorator.
  • The route uses views.TaskDetail.as_view(), still named task_detail.

Press Run, then open /api/tasks/1/: the task shows as before.

Try it yourself

import json

from django.http import HttpResponse, JsonResponse
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_GET, require_http_methods

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()})


@require_GET
def stats(request):
    data = {
        "total": Task.objects.count(),
        "done": Task.objects.filter(done=True).count(),
    }
    return JsonResponse(data)


@csrf_exempt
@require_http_methods(["GET", "POST"])
def task_list(request):
    if request.method == "POST":
        try:
            data = json.loads(request.body)
        except json.JSONDecodeError:
            return JsonResponse({"error": "The body is not valid JSON."}, status=400)
        errors = {}
        title = data.get("title", "").strip()
        if not title:
            errors["title"] = "This field is required."
        if errors:
            return JsonResponse({"errors": errors}, status=400)
        task = Task.objects.create(
            title=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)


@csrf_exempt
@require_http_methods(["GET", "PATCH", "DELETE"])
def task_detail(request, pk):
    task = Task.objects.filter(pk=pk).first()
    if task is None:
        return JsonResponse({"error": "Task not found"}, status=404)
    if request.method == "DELETE":
        task.delete()
        return HttpResponse(status=204)
    if request.method == "PATCH":
        data = json.loads(request.body)
        if "title" in data:
            task.title = data["title"]
        if "done" in data:
            task.done = data["done"]
        task.save()
    return JsonResponse(task_to_dict(task))
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in JSON APIs

Practice on your own: Django playground