skip to Main Content

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

augusto

Freelance developer and sysadmin

This Post Has 13 Comments
  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.

      1. 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. 😉

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

      1. 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.

      2. 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

  2. when I import the above middleware, I can’t run django server
    Traceback (most recent call last):
    File “H:\Documents\env\webping\lib\site-packages\django\utils\module_loading.py”, line 20, in import_string
    return getattr(module, class_name)
    AttributeError: module ‘app.middleware’ has no attribute ‘HttpTunnelingMiddleware’

    The above exception was the direct cause of the following exception:

    Traceback (most recent call last):
    File “H:\Documents\env\webping\lib\site-packages\django\core\servers\basehttp.py”, line 45, in get_internal_wsgi_application
    return import_string(app_path)
    File “H:\Documents\env\webping\lib\site-packages\django\utils\module_loading.py”, line 17, in import_string
    module = import_module(module_path)
    File “C:\Users\vuthe\AppData\Local\Programs\Python\Python37\lib\importlib\__init__.py”, line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
    File “”, line 1006, in _gcd_import
    File “”, line 983, in _find_and_load
    File “”, line 967, in _find_and_load_unlocked
    File “”, line 677, in _load_unlocked
    File “”, line 728, in exec_module
    File “”, line 219, in _call_with_frames_removed
    File “H:\Documents\GitLab\webping\webping\wsgi.py”, line 16, in
    application = get_wsgi_application()
    File “H:\Documents\env\webping\lib\site-packages\django\core\wsgi.py”, line 13, in get_wsgi_application
    return WSGIHandler()
    File “H:\Documents\env\webping\lib\site-packages\django\core\handlers\wsgi.py”, line 135, in __init__
    self.load_middleware()
    File “H:\Documents\env\webping\lib\site-packages\django\core\handlers\base.py”, line 35, in load_middleware
    middleware = import_string(middleware_path)
    File “H:\Documents\env\webping\lib\site-packages\django\utils\module_loading.py”, line 24, in import_string
    ) from err
    ImportError: Module “app.middleware” does not define a “HttpTunnelingMiddleware” attribute/class

    The above exception was the direct cause of the following exception:

    Traceback (most recent call last):
    File “C:\Users\vuthe\AppData\Local\Programs\Python\Python37\lib\threading.py”, line 926, in _bootstrap_inner
    self.run()
    File “C:\Users\vuthe\AppData\Local\Programs\Python\Python37\lib\threading.py”, line 870, in run
    self._target(*self._args, **self._kwargs)
    File “H:\Documents\env\webping\lib\site-packages\django\utils\autoreload.py”, line 54, in wrapper
    fn(*args, **kwargs)
    File “H:\Documents\env\webping\lib\site-packages\django\core\management\commands\runserver.py”, line 137, in inner_run
    handler = self.get_handler(*args, **options)
    File “H:\Documents\env\webping\lib\site-packages\django\contrib\staticfiles\management\commands\runserver.py”, line 27, in get_handler
    handler = super().get_handler(*args, **options)
    File “H:\Documents\env\webping\lib\site-packages\django\core\management\commands\runserver.py”, line 64, in get_handler
    return get_internal_wsgi_application()
    File “H:\Documents\env\webping\lib\site-packages\django\core\servers\basehttp.py”, line 50, in get_internal_wsgi_application
    ) from err
    django.core.exceptions.ImproperlyConfigured: WSGI application ‘webping.wsgi.application’ could not be loaded; Error importing module.

Leave a Reply

Your email address will not be published. Required fields are marked *

Back To Top