• Tutorial: Converting the Django Poll Tutorial to use AJAX via JQuery

    Django tutorial project

    Django is a web platform written in Python which lets you build high-performing, elegant Web applications quickly. AJAX is a framework which allows asynchronous requests to be sent to servers, requesting or submitting data, allowing updates either to databases or HTML pages without the need for the pages to be reloaded.

    For my tutorial, I have been using Django 1.4 (and the corresponding tutorial), Python 2.7 and JQuery 1.9.0. This is probably not too important, except to note that the folder structure of Django projects has changed somewhat so it is possible that you may find things a little different if you have already completed the tutorial for different versions of Django.

    As someone who has dabbled both in Django development as well as a little AJAX, I thought as a refresher I would look to convert the standard Django Poll tutorial to work with AJAX (using JQuery). The Poll app lists a selection of questions, which when clicked on allows a user to vote for one of the answers. When voted on, the tally of votes is stored alongside the question in the database, and the current values shown to the user. Hitting the “Vote” button causes the form to be submitted (with POST data) and a different HTML template is rendered.

    This tutorial assumes that you have completed the Django tutorial. I am also going to focus on the user-facing side and will not make changes to the admin side. If you are interested in making changes to the admin site (including AJAX), have a look at this tutorial which somewhat cannibalises things in order to achieve their goal.

    Initial Preparations

    As we will be using the already-completed Django tutorial, there’s not a great deal to do before kicking off with this tutorial. You will, however, need to download the JQuery javascript file. This will need to be placed in a project media folder, which is discussed below.

    Creating a media directory

    In order to be able to reference our JavaScript files, we need somewhere in our project to host our various static files (such as stylesheets and JavaScript files). We will create a folder in the project called media, and provide references to the folder in settings.py.

    Create a folder called ‘media’:

    If you have followed the Django 1.4 tutorial, you will have three folders in your top-level project folder; mysite, polls and templates. In the polls folder within the templates folder, I have created a folder called media (so that the path to the media folder looks like: <project root>/templates/polls/media/).

    Reference the media folder in settings.py:

    Open settings.py. There are a couple of small changes which need to be made to the code. Firstly, for the sake of ease, we are going to create a variable pointing to the project root. Add the following lines to the top of your file:

    import os
    ROOT_PATH = os.path.join(os.path.dirname(__file__), "..")

    You will also need to change the following values in the file:

    MEDIA_ROOT = os.path.join(ROOT_PATH, 'templates', 'polls', 'media')
    MEDIA_URL = 'http://127.0.0.1:8000/media/'

    (Make sure that the MEDIA_URL is actually pointing to a vaid IP address and port.)

    Update mysite/urls.py to link to the new media folder:

    When a request is sent for one of the static files, we need to process the request and serve the data up. This requires an edit to the urlpatterns in mysite/urls.py. Add the following line:

    urlpatterns += patterns('',
        (r'^media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT }),
    )

    If you place a file in the media folder, you should now be able to access it by visiting the URL to the file. As an example, I placed the jquery.js file in my media folder. Visiting http://127.0.0.1:8000/media/jquery.js allowed me to view the file.

    Linking templates to jquery.js

    If you have not done so already, copy the JQuery JavaScript file to the media directory. Rather than keeping the version number in the filename, I have just called my file jquery.js. You will then need to reference the js file from your HTML templates.

    Create a base HTML file:

    In the templates folder, create a file called base.html. Paste the following text into it:

    <html>
        <head>
            <title>{% block title %}Poll AJAX Tutorial - DJBP.CO.UK{% endblock %}</title>
            <script type="text/javascript" src="/media/jquery.js"></script>
        </head>
        <body>
            <div class="container">
                <div class="message"></div>
    {% block content %}{% endblock %}
            </div>
        </body>
    </html>

    Update the existing HTML files:

    When you created the HTML files for the initial tutorial, there was no base template to link into. This has now changed and there are two changes which need to be made to each of the files in templates/polls/. First of all, you need to make each template aware that they need to import the base HTML file you created above. Then you have to define the block that the code in each template file applies to in the base HTML file. This is done by adding two lines at the top of each HTML file (the first two lines):

    {% extends "base.html" %}
    {% block content %}

    …and one at the very bottom:

    {% endblock %}

    If you view the source of the pages, you should see the contents of the base.html file above and below the content of the pages. I edited three files originally created as part of the Django tutorial – detail.html, index.html and results.html. All of these files now reference jquery.js in their <head> section.

    AJAX-ifying the Project

    Until now, we have really just been tweaking with the existing project. It is now time to start including some JavaScript.

    Submitting a Vote:

    Initially, we are going to add a new function in views.py to handle the AJAX submission. This is not entirely necessary but to keep the original Django tutorial code separate I think it worthwhile in this instance. Firstly, in polls/urls.py, add an additional line:

    url(r'^(?P<poll_id>\d+)/ajaxvote/$', 'polls.views.ajax_vote'),

    Then open polls/views.py and add the corresponding view:

    from django.http import HttpResponseRedirect, HttpResponse, HttpResponseServerError
    ...
    
    def ajax_vote(request, poll_id):
        p = get_object_or_404(Poll, pk=poll_id)
    
        try:
            selected_choice = p.choice_set.get(pk=request.POST['choice'])
        except (KeyError, Choice.DoesNotExist):
            return HttpResponseServerError("You didn't select a choice.")
        else:
            selected_choice.votes += 1
            selected_choice.save()
            return HttpResponseRedirect(p.get_absolute_url())

    This function is very similar to the original one, but it does not redirect to an HTML page, and will instead allow AJAX to do its thing.

    We have also referenced a new function in the new view (Poll.get_absolute_url()). Add the function to the Poll model in models.py:

    def get_absolute_url(self):
        return u"/polls/%s/" % self.pk

    We are going to have to add a couple of IDs to some of the elements in the HTML document to make identification a little easier. With this in mind, open detail.html and find the <input> used to submit the form, and make it look like this (adding id=”vote”):

    <input type="submit" value="Vote" id="vote" />

    We can now start adding some JQuery scripting. Just above the {% endblock %} template tag add a script block:

    <script type="text/javascript">
        $(document).ready(function() {
    
        });
    </script>

    The inclusion of the $(document).ready(…) function just ensures that everything contained within is loaded after the DOM has finished loading. We are going to add a couple of JavaScript functions to the script block; one to handle the submission of the data, and the other to handle the response from the server. Although these will be separate below, they should be included in the same <script> code block. The first function, called “create_vote” will collate the data and send it to the server using a POST:

    var create_vote = function() {
        var value = $("form input[name='choice']:checked").val();
    
        if (value != "")
        {
            var data = { choice:value };
            var args = { type:"POST", url:"/polls/{{ poll.id }}/ajaxvote/", data:data, complete:create_vote_complete };
            $.ajax(args);
        }
        else
        {
            // We should display a helpful error message
        }
        return false;
    };

    This first function gets whichever radio button in the simple form has been checked. Assuming one has been, it makes use of the JQuery ajax function to submit the data as POST parameters to the URL specified. The return function is also specified (create_vote_complete). Finally, the function returns false. This is very important as it causes the default click behaviour of the submit button to be overridden. If false was not returned, the user would be taken to the original HTML page after the vote had been registered. We want to prevent that from happening and provide feedback to the user on the same page.

    Finally for this sub-section, we need to tell JQuery to associate a click on the “submit” button with the method we have defined above. This is where the id=”vote” that we added earlier to the submit button comes into play. Add the following line at the bottom of the JavaScript block (still inside the $(document).ready( … ) function):

    $("#vote").click(create_vote);

    This tells JQuery that any click on something with the id “vote” should call the “create_vote” function.

    Handling the response:

    With all the code above (as well as the section on Cross site request forgery protection below), we have a system which will allow votes to be cast. The way it has been implemented thus far, once the vote has been cast, no update is made to the HTML page to notify the user that the vote has been cast, nor are they able to see what the results are. This next section will check the response from the server, and update the page accordingly.

    We have another function to add to the JavaScript block – “create_vote_complete()” which is referenced in the “create_vote()” function. This new function is called when the AJAX method returns after a post. We can access a status to see whether the vote was a success or not, and have a result object (res) which contains further information about the update. Add this next method to your JavaScript block:

    var create_vote_complete = function(res, status) {
        if (status == "success") {
            window.location.href = "/polls/{{ poll.id }}/results/";
        }
        else
        {
            display_message(res.responseText, $(".message"));
        }
    }

    If the vote was a success, then we are a little lazy about what we do – if we were doing this properly, we might dynamically remove the <form> in our details html and replace it with a text update confirming the status of the vote. Instead, as the results view already exists, we just use a JavaScript redirect.

    If the vote was not a success, we display a message which includes the error message text set on the server side. Add the following code to your JavaScript block to handle displaying an error message:

    var display_message = function(msg, elem) {
        var msg_div = $('<div><p>'+msg+'</p></div>');
        elem.append(msg_div).fadeIn('slow').animate({opacity: 1.0}, 7000).fadeOut('slow',function() { msg_div.remove(); });
    };

    This will display an error message for 7 seconds before fading it back out again (a nice JQuery touch).

    Cross Site Request Forgery (CSRF) Protection:

    If you have done the Django 1.4 tutorial, you will find references to {% csrf_token %} objects in your templates. I am not going to go into detail about this subject as there is a great resource for this on the Django website. It is, however, necessary to make some additions to our code in order to allow AJAX posts to work (otherwise you will get permission denied HTML errors). Paste the following in the same code block as all the JavaScript above:

    function getCookie(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
    var csrftoken = getCookie('csrftoken');
    
    function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }
    $.ajaxSetup({
        crossDomain: false, // obviates need for sameOrigin test
        beforeSend: function(xhr, settings) {
            if (!csrfSafeMethod(settings.type)) {
                xhr.setRequestHeader("X-CSRFToken", csrftoken);
            }
        }
    });

    Stylesheets

    All of what I have done simply converts the Django tutorial to making use of AJAX. I have not spent any time creating any CSS to make the whole thing look nice. Nor am I going to. If you want to, feel free to do so.

37 Responsesso far.

  1. Ergusto says:

    This tutorial could not have come at a better time. I thank you profusely.

  2. Ergusto says:

    I know this is quite a hefty request, but if you have time could you possibly do a tutorial on a complete form submission? I’d actually probably be willing to pay if you could do a tailored one to some of my Django code.

    • Duncan says:

      Hi,

      I am more than happy to provide some pointers, but you will need to be a little more specific about what you’re asking for. What specifically about form submission is it you want to know? The tutorial I have written deals with form submission – do you want to see a greater number and variety of fields? Or is it about multiple forms?

      The reason I did this specific tutorial is that there is a lot of information about generic form submission in AJAX on the ‘net, but there aren’t many which get you started in a project in Django.

  3. Taufiqm says:

    Hi! Nice tutorial you have there. However, I think you didn’t use Jquery 1.9 for this tutorial. You use some syntax that are deprecated: e.g:
    var value = $(“form input[@name=’choice’]:checked”).val();
    should’ve been just
    var value = $(“form input[name=’choice’]:checked”).val();

    took me several hours to figure out that since this is my first time using jQuery

    • Duncan says:

      Hi. Sorry for the time you spent investigating this issue. This is actually something which is incorrect in the example code (which I will now change). It can sometimes be quite difficult to remember to make all the changes which are made in code to the tutorial when minor fixes come into play. Thanks for taking the time to figure the problem out and point it out as it will no doubt help others.

  4. Soo Ling Lim says:

    Hi Duncan,

    Thanks for your tutorial. I think it is by far the clearest step by step tutorial on how to post data with ajax in django.

    In “def ajax_vote”, I don’t understand how the view can know which template to render, as none was provided to the view. I followed the code provided and ended up with a blank page in ajaxvote with the string “You didn’t select a choice.”

    I am not sure what I did wrong….please help.

    Thanks again!

    Soo Ling

    • Duncan says:

      ajax_vote is a function which is called by the JavaScript and will respond to the JavaScript in a way in which the JavaScript will understand. In the majority of cases, this will result in the client-side doing something clever. In this example, we use HttpResponseRedirect to take the user to a different page. ajax_vote is not a view which should be gone to in the address bar of the browser though – it is only called by the JavaScript. Does this help?

  5. Felix says:

    Thank you for writing this! Great intro to using AJAX with Django! Very detailed and easy to follow. This will come in very handy. Thanks!

  6. Matt says:

    Thanks for your clear tutorial.

    I am trying to implement this but without luck. I’m quite sure I have written everything correctly, but when I hit vote, the page just reloads (i.e. no ajax). The vote count is going up correctly as verified in /admin, but the javascript must not be working.

    I’ve tried using Chrome devtools console to trigger the jquery function alone to see if that’s somehow reloading the page (e.g. not returning false), but I don’t think that’s possible.

    In the console, after clicking vote, I get:

    [03/Jan/2014 13:42:20] “POST /vote/2/ HTTP/1.1” 302 0
    [03/Jan/2014 13:42:20] “GET / HTTP/1.1” 200 5118
    [03/Jan/2014 13:42:20] “GET / HTTP/1.1” 200 13405

    How can I troubleshoot this?

    • Duncan says:

      Can you confirm whether your ajax view is being hit (e.g. placing a print in the view)?

      You need to return false to prevent the default behaviour of buttons etc.

      Have you put break-points in the javascript to confirm that it is getting hit?

      • Matt Guy says:

        Thanks for your time. I should be absolutely honest, I’m repurposing your tutorial,
        so I think I’ve properly implemented it but there are significant digressions which may be leading to the problem.

        Here is a past of my views, models, index and urls:

        http://pastebin.com/kQiMrjbv

        Yes it is hitting the javascript and the function.

        I’m not sure what’s up. If you have time to take a peak, it would be much appreciated.

        Thanks!

        • Duncan says:

          The first thing I have noticed is that you’re returning a redirect response in your AJAX view. Rather than that, return JSON or something and interpret that in your JavaScript function.

          • Matt says:

            I got the redirect response from the view in your tutorial. Have I incorporated it wrongly?

            Anyhow, I tried your advice, replacing with both of these responses, and neither worked, just threw up 500:

            json = serializers.serialize(
            ‘json’, Poll.objects.all())
            # return HttpResponse(json, content_type=”application/json”)
            return HttpResponse(simplejson.dumps({‘helpful’:True}), ‘application/json’)

            what do you think?

            Thanks again for your time.

          • Duncan says:

            The tutorial does do a redirect – but that’s just because I didn’t write the code to deal with the result of the POST. It was outside the scope of the tutorial.

            Try returning the {‘helpful’:True} dictionary without calling dumps() on it – in other words, return a dictionary. See if that results in what you’re expecting. If not, wrap the response in a try/except and print the stacktrace.

  7. Neo says:

    This is really help. Could you send me the project zip file ? or share a link for downloading it, many thanks!

    • Duncan says:

      The vast majority of the code relies on you following the Django tutorial. This just builds on it. So if you follow the tutorial, you will have the code to begin with – no need for a zip file.

      • Neo says:

        Not really, because I have followed the tutorial, but as a totally newbie for jquery, I really don’t know how to paste the snippets to a correct position in the html doc.

  8. alex adwards says:

    Can you update it to django 1.6 version?

    Or the same code will work for 1.6 ?

    i have tried it for 1.6 but its still refreshing the page and then coming results page.

    • Duncan says:

      I’ve no idea whether this will work with Django 1.6. Assuming the Poll app works though, I can’t imagine much will need to change in order to get the AJAX side working as not much of that will depend upon Python or Django. When I get a chance though, I will give it a try. If I need to update the tutorial, I will do.

      • alex adwards says:

        Fine, can you help me in one thing,

        I have modified the poll app like i just created only one web page,
        In that page only i am showing the questions and below of the questions their corresponding choices and beside the choice i am printing the corresponding votes.
        below i have put one button to increase the votes.
        Now i want to get the number of votes while clicking on the button dynamically through ajax.

        i have tried so much to get but i am failing.

        Can you help me in this

        • Duncan says:

          It’s difficult to help with this without any further information I’m afraid. Essentially, all you want to do is to hit an endpoint which will return information in a manner which is of use to you. For example (using jQuery):
          $.get('/ajax/votecounts/', success=function(data) {
          // Do some processing - update the counts
          });

          Every time the vote button is pressed, call a function which does the above and based on an appropriate response from the server updates the counts.

      • alex adwards says:

        Ok, Can you tell me what we will get as response as you specified function(res, status) in this in status we will get success like the in res what we will get exactly?and can we extract that?

        • Duncan says:

          The res variable will contain the data that was returned from the server. For example, if there was a problem casting the vote.

          It is up to you what you return from Django – you may return a JSON dictionary, for example, with all the information you require present.

          • alex adwards says:

            Ok but when i am sending response as some dict or html page then in ajax response i am getting object not any data. how to get the the data?

          • Duncan says:

            A dictionary in JavaScript is an object. If in your python you were to return something like:
            return HttpResponse(simplejson.dumps(return_data), mimetype="application/json")
            (where return_data is a dictionary), then in JavaScript, that data would be available as the data in the success function.

  9. alex adwards says:

    Of course Duncan, i have done like that only
    def ajax_vote(request,poll_id):

    p = get_object_or_404(Poll, pk=poll_id)

    try:
    selected_choice = p.choice_set.get(pk=request.POST[‘choice’])
    except (KeyError, Choice.DoesNotExist):
    return HttpResponseServerError(“You didn’t select a choice to vote”)
    else:
    selected_choice.votes += 1
    selected_choice.save()
    num_votes = selected_choice.votes
    print num_votes
    if request.is_ajax():
    return HttpResponse(json.dumps(num_votes),mimetype=’application/json’)
    this is my code so now handling like this in jquery
    var create_vote_complete{{poll.id}} = function(res,status,jqXHR){
    if(status==”success”){
    alert(“vote submitted successfully”);
    alert(res);
    var value = $(“form input[name=’choice{{poll.id}}’]:checked”).val()
    $(“#”+value).html(res+” “);

    so here when i am replacing with res in that place i am getting [object Object]
    alert(res) showing also like that only [object Object] only
    can u solve this..?

  10. alex adwards says:

    Solved problem, i am sending response as html file in that html file i am printing only votes then replacing it..

    Thanks for giving reply.

Leave a Reply

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

Are you human: *