How to use Python’s HTTPStatus with Django

2021-06-30 Snaily McSnailface says “use HTTPStatus”!

A “magic number” is the anti-pattern of using a number directly rather than storing it in a descriptive variable name. In web code HTTP status codes are often used as magic numbers, perhaps because web developers memorize common codes such as 200 and 404. In Python, we can avoid such magic with descriptive references from the standard library’s http.HTTPStatus enum.

Let’s look at two ways to use HTTPStatus in our Django code.

1. Creating Responses

Django includes a bunch of HttpResponse subclasses for common status codes, but the list is deliberately non-exhaustive. If we need to return a status code for which a classes does not exist, we can use Python’s HTTPStatus with Django’s HttpResponse.

For example, if one of our pages is unavailable due to legal reasons, so we want to return status code 451. We can do this with HttpResponse like so:

from http import HTTPStatus

from django.http import HttpResponse


def taken_down(request):
    ...
    return HttpResponse(
        content,
        status_code=HTTPStatus.UNAVAILABLE_FOR_LEGAL_REASONS,
    )

If we find ourselves using a status code a lot in our project, we can create our own HttpResponse subclasses, as the documentation mentions. For example:

from http import HTTPStatus

from django.http import HttpResponse


class HttpResponseLegallyUnavailable(HttpResponse):
    status_code = HTTPStatus.UNAVAILABLE_FOR_LEGAL_REASONS

Additionally, when using Django’s render() shortcut, we can change the status code with the status argument:

from http import HTTPStatus

from django.shortcuts import render


def taken_down(request):
    ...
    return render(
        request,
        "illegal.html",
        status=HTTPStatus.UNAVAILABLE_FOR_LEGAL_REASONS,
    )

2. In Test Assertions

When using Django’s test client, we normally make assertions on the response status codes. Such assertions can also be clarified using HTTPStatus:

from http import HTTPStatus

from django.test import TestCase


class IndexTests(TestCase):
    def test_success(self):
        response = self.client.get("/")

        self.assertEqual(response.status_code, HTTPStatus.OK)
        ...


class TakenDownTests(TestCase):
    def test_success(self):
        response = self.client.get("/some-taken-down-page/")

        self.assertEqual(
            response.status_code,
            HTTPStatus.UNAVAILABLE_FOR_LEGAL_REASONS,
        )
        ...

rest_framework.status

Django REST Framework has a similar construct in its rest_framework.status module, which predates http.HTTPStatus. This isn’t an enum but a module containing numerical constants such as HTTP_200_OK.

I recommend you use http.HTTPStatus over rest_framework.status for a few reasons:

  1. It’s part of the standard library you can use it in any project, not just those using Django REST Framework.
  2. It has more data: each status code also has its reason phrase and description.
  3. As an enum it is a separate type to int, which is useful for comparison, debugging, and type checking.

Fin

May your code’s only magic be joyous,

—Adam


🤩 Django-Related Sales for Black Friday Week 🤩


Subscribe via RSS, Twitter, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: django, python