PUT and DELETE HTTP requests with Django and jQuery

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.

To put it in examples your coding dream 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!')
    }
});

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.

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!

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

11 thoughts on “PUT and DELETE HTTP requests with Django and jQuery

  1. Hey! I am only learning Django and am trying to implement REST in a study-purpose project that I’m working on. I do not understand how to put the HTTP header X_METHODOVERRIDE in the request. I have two forms in my corresponding template, one to edit and one to delete. So how am I to tell my view that what coming is a delete? I don’t get where to/ how to put the header. For now I want jQuery kept aside.

      • Pay attention in not confusing request with response. You need to add the HTTP_X_METHODOVERRIDE header in the REQUEST made by the client, not on the RESPONSE given by the server. 😉

    • AFAIK there is no way to add request headers using plain HTML. You NEED to use jquery as I suggested to add that header.

      • Thank you for replying :). So as I have more than one form, I’ll give them ids and tell jQuery which form I’m talking about through it’s id? If yes would inside the dict like structure suffice? Also my URL takes an argument, objects pk. And I would like to use its name here. Would that be possible? Would inside the dict like structure make sense to Django? (I do have the object passed to the template)
        I have begun with jQuery only today and my experience with JavaScript is the little time I’ve spent with the tutorials at codecadamy so bare with me.

      • Thank you for replying :). So as I have more than one form, I’ll give them ids and tell jQuery which form I’m talking about through it’s id? If yes would ” id : ‘specific_id’ ” inside the dict like structure suffice? Also my URL takes an argument, objects pk. And I would like to use its name here. Would that be possible? Would ” url : ‘{% url ‘my_url’ my_object.id %}’ ” inside the dict like structure make sense to Django? (I do have the object passed to the template)
        I have begun with jQuery only today and my experience with JavaScript is the little time I’ve spent with the tutorials at codecadamy so bare with me

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s