Menu
CoddyTech

Allowed Methods

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

Send DELETE /api/tasks/ and it falls through to the code that lists tasks: 200, as if it worked. A program that asked to delete something now believes it did.

The honest answer is 405 Method Not Allowed. The decorators in django.views.decorators.http give it before your view runs, with an Allow header that lists what the view accepts:

@require_POST
def subscribe(request):
    ...


@require_http_methods(["GET", "PUT"])
def profile(request):
    ...

require_GET and require_POST are the short forms for one method. A view can carry two decorators, one per line, like @csrf_exempt above @require_POST. Allow exactly the methods a view's code does something for.

challenge icon

Challenge

Easy

Answer 405 for methods a view does not handle:

  • task_list accepts GET and POST.
  • stats and task_detail accept only GET.

Press Run, then in the Shell tab: from django.test import Client; print(Client().delete("/api/stats/").status_code) prints 405.

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":
        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)


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