PUT and DELETE HTTP requests with Django and jQuery


Wed 25 June 2014

Sometimes it could be useful and elegant to have a Django view responding to more that GET and POST requests, implementing a simple REST interface. In this post I’ll show how you can support PUT and DELETE HTTP requests in Django.

Your goal

To put it in examples your goal is to have a View like this:

from django.views.generic import View
from django.shortcuts import redirect, render
from django.forms.models import modelform_factory
from django.http import HttpResponse

from .models import MyModel

class RestView(View):
  def get(self, request):
    # retrieve some object and render in template
    object = MyModel.objects.get(pk=request.GET['pk'])
    return render(request, 'object_detail.html',
                  {'object': object})

  def put(self, request):
    # create an object and redirect to detail page
    modelform = modelform_factory(MyModel)
    form = modelform(request.PUT)
    if form.is_valid():
        form.save()
    return redirect('restview')

  def delete(self, request):
    # delete an object and send a confirmation response
    MyModel.objects.get(pk=request.DELETE['pk']).delete()
    return HttpResponse()

And accessing the view by jQuery Ajax methods like this:

$.ajax({
    type: 'DELETE',
    url: '/restview',
    data: { pk: pk },
    success: function() {
        alert('Object deleted!')
    }
});

The problems

Unfortunately there are two problems that restrict you to implement the view and the javascript like described above:

  1. Not all browsers support HTTP requests different from GET and POST. In some browsers the type: ‘DELETE’ in your ajax method will not work;
  2. Django does not put data sent with a PUT or DELETE request in a request.PUT or request.DELETE dictionary, like it does with GET or POST data. This is for a good reason.

The solution

What you can do to solve these problems? You can use the so called POST TUNNELLING method, that is using a POST request, putting the HTTP header X_METHODOVERRIDE in the request.

In jQuery you can accomplish this by modifyng the ajax method a little bit:

$.ajax({
    type: 'POST',
    url: '/restview',
    data: { pk: pk },
    success: function() {
        alert('Object deleted!')
    },
    headers: { 'X_METHODOVERRIDE': 'DELETE' }
});

So now you have a “standard” type: ‘POST’ and you’ve added the X_METHODOVERRIDE=DELETE HTTP header to the request.

To support this in the Django side you can write a middleware, like this:

from django.http import QueryDict

class HttpPostTunnelingMiddleware(object):
    def process_request(self, request):
        if request.META.has_key('HTTP_X_METHODOVERRIDE'):
            http_method = request.META['HTTP_X_METHODOVERRIDE']
            if http_method.lower() == 'put':
                request.method = 'PUT'
                request.META['REQUEST_METHOD'] = 'PUT'
                request.PUT = QueryDict(request.body)
            if http_method.lower() == 'delete':
                request.method = 'DELETE'
                request.META['REQUEST_METHOD'] = 'DELETE'
                request.DELETE = QueryDict(request.body)
        return None

The middleware intercepts the HTTP_X_METHODOVERRIDE header, and act accordingly by forcing the HTTP method in the Django side and creating the request.PUT and request.DELETE QueryDict.

Assuming that you saved the above middleware in a file called middleware.py in your myapp Django app, you can add the middleware to your settings.py like this:

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'myapp.middleware.HttpPostTunnelingMiddleware',
)

Now your coding dream will come true, enjoy your simple REST View in Django, consuming it with jQuery!

What about the function based views?

You are not limited to a class-based view, but you can also use a function based view, by writing a view function similar to this:

def rest_view(request):
  if request.method == 'GET':
    # retrieve some object and render in template
    object = MyModel.objects.get(pk=request.GET['pk'])
    return render(request, 'object_detail.html',
                  {'object': object})

  elif request.method == 'PUT':
    # create an object and redirect to detail page
    modelform = modelform_factory(MyModel)
    form = modelform(request.PUT)
    if form.is_valid():
        form.save()
    return redirect('restview')

  elif request.method == 'DELETE':
    # delete an object and send a confirmation response
    MyModel.objects.get(pk=request.DELETE['pk']).delete()
    return HttpResponse('ok')

Conclusion

I hope that this post helped you to implement put and delete requests in Django. If you want you can read other interesting Django related posts here on my blog.


Share: