2014, Django and AJAX Form Submissions, Say ‘Goodbye’ to the Page Refresh

Introduction

Let’s get down to business:

  • Download the compressed pre-ajax Django Project from the repo

  • Activate a virtualenv

  • Install the requirements

  • Sync the database

  • Fire up the server

Once logged in, test out the form. What we have here is a simple communication app with just create rights.

It looks nice, but there’s one annoying issue: The page refresh .

How do we get rid of it? Or, how do we update just a portion of a webpage without having to refresh the entire page ?

Enter AJAX. AJAX is a client-side technology used for making asynchronous requests to the server-side - i.e., requesting or submitting data - where the subsequent responses do not cause an entire page refresh.

../../../../_images/django-forms-overview.png

This tutorial assumes you have working knowledge of Django as well as some experience with JavaScript/jQuery .

You should also be familiar with the basic HTTP methods, particularly GET and POST. Need to get up to speed? Get Real Python.

Use Protection

Regardless of whether you’re using AJAX or not, forms are at risk for Cross Site Request Forgeries (CSRF) attacks.

Read more about CSRF attacks on the Coding Horror blog . They’ve got a great article.

To prevent such attacks, you must add the {% csrf_token %} template tag to the form, which adds a hidden input field containing a token that gets sent with each POST request.

If you look at the talk/index.html template, you can see that we have already included this token.

However, when it comes to AJAX requests, we need to add a bit more code, because we cannot pass that token using a JavaScript object since the scripts are static.

To get around this, we need to create a custom header that includes the token to watch our back. Simply grab the code here and add it to the end of the main.js file. Yes, it’s a lot of code. We could go through it line-by-line, but that’s not the point of this post. Just trust us that it works.

Moving on…

Handling Events

Before we touch the AJAX code, we need to add an event handler to our JavaScript file using jQuery.

Note

Keep in mind that jQuery is JavaScript. It’s simply a JavaScript library used to reduce the amount of code you need to write. This is a common area of confusion so just be mindful of this as you go through the remainder of this tutorial.

Which event(s) do we need to “handle” ? Since we’re just working with creating a post at this point, we just need to add one handler to main.js:

1 // Submit post on submit
2 $('#post-form').on('submit', function(event){
3     event.preventDefault();
4     console.log("form submitted!")  // sanity check
5     create_post();
6 });

Here, when a user submits the form this function fires, which:

  • Prevents the default browser behavior for a form submission,

  • Logs “form submitted!” to the console, and

  • Calls a function called create_post() where the AJAX code will live.

Make sure to add an id of post-form to the form on the index.html file:

<form action="/create_post/" method="POST" id="post-form">

And add a link to the JavaScript file to the bottom of the template

<script src="static/scripts/main.js"></script>

Adding AJAX

Let’s develop one last iteration before we add the actual AJAX code.

Update main.js

Add the create_post() javascript function:

// AJAX for posting
function create_post() {
    console.log("create post is working!") // sanity check
    console.log($('#post-text').val())
};

Again, we ran a sanity check to ensure the function is called correctly, then we grab the input value of the form.

For this to work correctly we need to add an id to the form field:

Update forms.py

 1 class PostForm(forms.ModelForm):
 2     class Meta:
 3         model = Post
 4         # exclude = ['author', 'updated', 'created', ]
 5         fields = ['text']
 6         widgets = {
 7             'text': forms.TextInput(attrs={
 8                 'id': 'post-text',
 9                 'required': True,
10                 'placeholder': 'Say something...'
11             }),
12         }

Notice how we also added a placeholder to the field and made it required along with the id. We could add some error handlers to the form template or simply let HTML5 handle it. Let’s use the latter.

Test again. Submit the form with the word “test”. You should see the following in your console:

form submitted!
create post is working!
test

Sweet. So, we’ve confirmed that we’re calling the create_post() function correctly as well as grabbing the value of the form input.

Now let’s wire in some AJAX to submit the POST request.

Update main.js

 1 // AJAX for posting
 2 function create_post() {
 3     console.log("create post is working!") // sanity check
 4     $.ajax({
 5         url : "create_post/", // the endpoint
 6         type : "POST", // http method
 7         data : { the_post : $('#post-text').val() }, // data sent with the post request
 8
 9         // handle a successful response
10         success : function(json) {
11             $('#post-text').val(''); // remove the value from the input
12             console.log(json); // log the returned json to the console
13             console.log("success"); // another sanity check
14         },
15
16         // handle a non-successful response
17         error : function(xhr,errmsg,err) {
18             $('#results').html("<div class='alert-box alert radius' data-alert>Oops! We have encountered an error: "+errmsg+
19                 " <a href='#' class='close'>&times;</a></div>"); // add the error to the dom
20             console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console
21         }
22     });
23 };

Update the views

Now let’s update our views to handle the POST request correctly:

 1 def create_post(request):
 2     if request.method == 'POST':
 3         post_text = request.POST.get('the_post')
 4         response_data = {}
 5
 6         post = Post(text=post_text, author=request.user)
 7         post.save()
 8
 9         response_data['result'] = 'Create post successful!'
10         response_data['postpk'] = post.pk
11         response_data['text'] = post.text
12         response_data['created'] = post.created.strftime('%B %d, %Y %I:%M %p')
13         response_data['author'] = post.author.username
14
15         return HttpResponse(
16             json.dumps(response_data),
17             content_type="application/json"
18         )
19     else:
20         return HttpResponse(
21             json.dumps({"nothing to see": "this isn't happening"}),
22             content_type="application/json"
23         )

Here we grab the post text along with the author and update the database. Then we create a response dict, serialize it into JSON, and then send it as the response - which gets logged to the console in the success handler: console.log(json), as you saw in the create_post() function in the JavaScript file above.

Test this again.

You should see the object in the console:

form submitted!
create post is working!
Object {text: "hey!", author: "michael", postpk: 15, result: "Create post successful!", created: "August 22, 2014 10:55 PM"}
success

How about we add the JSON to the DOM !

Updating the DOM

Update the template

Simply add an id of “talk” to the <ul>:

<ul id="talk">

Then update the form so that errors will be added

<form method="POST" id="post-form">
    {% csrf_token %}
    <div class="fieldWrapper" id="the_post">
        {{ form.text }}
    </div>
    <div id="results"></div> <!-- errors go here -->
    <input type="submit" value="Post" class="tiny button">
</form>

Update main.js

Now we can add the JSON to the DOM where that new “talk” id is

1 success : function(json) {
2     $('#post-text').val(''); // remove the value from the input
3     console.log(json); // log the returned json to the console
4     $("#talk").prepend("<li><strong>"+json.text+"</strong> - <em> "+json.author+"</em> - <span> "+json.created+"</span></li>");
5     console.log("success"); // another sanity check
6 },