Django antipatterns 〉antipattern 〉A GET request with side-effects Fork me on GitHub

Often people construct views that have side, effects, for example:

def remove_comment(request, comment_pk):
Comment.objects.filter(
comment_id=comment_pk
).delete()
# …

# Why is it a problem?

Because this violates the HTTP standard. In the section safe methods of the HTTP specifications [w3.org] it specifies that:

In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe".

A GET request should thus not create, update, or delete entities.

This is important because other actors on the internet assume that a GET request is safe. For example if a browser will not give a warning if you refresh the browser with an additional GET request, whereas most browsers will do that for a POST request.

Search engines have web crawlers that look for URLs on pages, and will make GET requests to these pages to analyze the page that is returned and look for more URLs. This thus means that a GET request of such crawler might accidentally create, remove and update entities.

Django also does not offer security mechanisms like a CSRF-token for GET requests. This thus makes cross-site request forgery (CSRF) [wiki] easier.

# What can be done to resolve the problem?

One should use POST, PUT, PATCH, or DELETE requests to update entities. These are the HTTP methods designed for this. One can use for example the @require_http_methods decorator [Django-doc] to restrict a view to certain HTTP methods. This will return a HTTP 405 "Method Not Allowed" response [wiki] to warn the client that this request was not allowed:

from django.views.decorators.http import require_http_methods

@require_http_methods(['POST'])
def remove_comment(request, comment_pk):
Comment.objects.filter(
comment_id=comment_pk
).delete()
# …

The HTML page should thus use a mini <form> to produce the POST request, or an AJAX call.