Lesson 3 – Django REST Framework: AJAX

JAVASCRIPT!

Actually, more accurately, JQuery. JQuery makes it really easy to create AJAX-y HTTP Requests.

AJAX!? What is it?

For some of you, you probably already know what AJAX is and why you’d use it. In this case you can safely skip down to the next header.

AJAX stands for Asynchronous JAvaScript and XML. It is a cool thing that allows browsers make requests even AFTER a browser is done loading (Asynchronous).

You have a HTML page. It’s loaded with some data. Then, you click a button, or a link, etc. Then, the button or link or whatever create and sends a request to the server WITHOUT having to refresh your page and the data will just show up inside the current page.

This one tiny technology gave rise to some really cool online applications: Facebook, GMail, Twitter, etc., etc., etc.!! There are TONS of things you can do with AJAX requests. The cool thing about it is you don’t need an API to use it! So what does that mean?

AJAX allows you to make any kind of request on your own server. You can request a zip file, a CSV file, an HTML file, a JSON file, an XML file, any kind of file. As a matter of fact, an API (like the one we just created) all it is, is a generated JSON file that gets delivered through HTTP Requests straight to your browser.

So, we are going to use our API to create a Single Page Application. A Single Page Application is an application where we load our page ONCE (single page) that we can interact with by creating and retrieving data (application).

Let’s get started with some JQuery AJAX requests!

HTTP Requests in JQuery

Back in the day while I was learning web stuff before there were cool Javacript libraries like JQuery and AngularJS, we had to create AJAX requests that look like this:

var xhttp; 
if (window.XMLHttpRequest) { 
    xhttp = new XMLHttpRequest(); 
} else { 
    // code for IE6, IE5 
    xhttp = new ActiveXObject("Microsoft.XMLHTTP"); 
} 
xhttp.open("GET", "stuff.json", true); 
xhttp.send();

These days, with the advent of JQuery, we can do the same thing above that looks like this:

$.get("stuff.json");

ONE LINE OF JQuery!!!! You can see how important it is that we start using JQuery for this part of the Course. I’m trying to focus my efforts on getting you a working API as quickly as possible. I’m not concerned with an one Javascript Library but I’m starting with JQuery because it’s the easiest.

NOTE: If you stick with this course for a few more days, we’ll look at creating a quick app using AngularJS. AngularJS + Django is currently one the most asked / requested tutorials ever!! But, because we get to AngularJS it’s imperative you understand Django REST Framework and how to create a webapp with Javascript first!

Install JQuery in Django

First, we need to get JQuery inside our Django app. I’m not going to worry about a “base template” or anything. I’m just going to place our JQuery inside of our index.html template in the polls app.

polls/templates/polls/index.html

<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  </head>
  <body>
    <!-- Django Template code -->
    <script>
    $(document).ready(function() {
      // Our JQuery code will go here.
    });
    </script>
  </body>
</html>

In JQuery, $(document).ready(function() { }); will execute once the entire page is completely rendered. Now, all we have to do is call our API and create our HTML for each question in the database.

polls/templates/polls/index.html

<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  </head>
  <body>
    <div id="questions">
      <p>No polls are available.</p>
    </div>
    <script>
    $(document).ready(function() {
      $.get("/polls/api/questions").success(function (data) {
        var question_list = "<ul>";
        for (question in data) {
          question_list += "<li><a href='#' onclick='loadQuestion("+data["id"]+")'>"+data["question_text"]+"</a></li>";
        }
        if (question_list !== "<ul>") {
          $("#questions").html(question_list + "</ul>");
        }
      });
    });
    </script>
  </body>
</html>

Here, we create a <div> that will hold our list of questions. Then, for the Javascript code, we make our request to our API and loop through the questions while spitting out a link to load the question in our application.

“LoadQuestion” is a function that will load our first question along a form that allows our user to vote in our poll. “LoadQuestion” should retrieve the API endpoint /polls/api/questions/1 and create a form that allows the user vote. When the user votes, we need to send the data using a POST Request to that same endpoint.

Extremely Important Subsection!!

As an astute observer, you may have noticed our QuestionSerializer has a choices field. But, if you were playing around with the API in the last lesson you probably didn’t see any “choices” for the /polls/api/questions/ endpoint. Why is that? I left out a very important piece of the project. It’s SO important, in fact, to leave out this very small addition to Django application because this is what trips up so many people! Look at the code below.

class QuestionSerializer(serializers.ModelSerializer):
    choices = ChoiceSerializer(many=True, read_only=True, required=False)

    class Meta:
        model = Question

I coded the QuestionSerializer to have a field for our question’s “choices” but it isn’t showing up in the API. That’s because that choices field has to match the field on the Question model. But, currently, The field on the Question model is currently choice_set and NOT choices.

If I didn’t wait until now, you probably wouldn’t take too seriously this little nuance and it will get your everytime you design your API.

How do we get this to work correctly? Open up your models.py file.

polls/models.py

class Choice(models.Model):
    question = models.ForeignKey(Question, related_name="choices")
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

    def __unicode__(self):
        return self.choice_text

The related_name parameter allows us to change the field choice_set and turn it into choices.

Next, anywhere in your project you are using question.choice_set.all(), you must change to question.choices.all()

Now, we can go back to http://localhost:8000/polls/api/questions/ and now we have a nested serializer for our question’s choices!

Create our Form…the AJAX way!

Let’s create LoadQuestion() to create and display our form.

polls/templates/polls/index.html

<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  </head>
  <body>
    <div id="questions">
      <p>No polls are available.</p>
    </div>
    <div>
      <h1 id="question_text"></h1>
      <form id="question_form" action="#">
        {% csrf_token %}
      </form>
    </div>
    <script>
    $(document).ready(function() {
      $.get("/polls/api/questions").success(function (data) {
        var question_list = "<ul>";
        for (question in data) {
          question_list += "<li><a href='#' onclick='loadQuestion("+data["id"]+")'>"+data["question_text"]+"</a></li>";
        }
        if (question_list !== "<ul>") {
          $("#questions").html(question_list + "</ul>");
        }
      });
    });

    var sendVote = function(question_id) {
      var form_data = $("#question_form").serialize();
      $.post('/polls/'+question_id+'/vote/', form_data).success(function() {
        alert("success!");
      });
    }

    var loadQuestion = function (question_id) {
      $.get("/polls/api/questions/"+question_id).success(function (data) {
        $("#question_text").html(data["question_text"]);
        var form = "";
        for (choice in data["choices"]) {
          form += "<input type='radio' name='choice' id='choice"+choice+"' value='"+data["choices"][choice]["id"]+"' />";
          form += "<label for='choice"+choice+"'>"+data["choices"][choice]["choice_text"]+"</label><br />";
        }
        $("#question_form").append(form + "<input type='button' onclick='sendVote("+question_id+")' value='Vote' />");
      });
    }
    </script>
  </body>
</html>

Here, we have loadQuestion which takes in a question_id. We use the question_id to create an AJAX request to /polls/api/questions/{question_id} and retrieve the "choices" from the JSON to construct our form.

When the user votes and clicks the “Vote” button, we then serialize the form data and construct another AJAX request $.post('/polls/{question_id}/vote', form_data) which sends the data to that view in our application. Notice, I’m using the URL to the vote view of our application and NOT the REST API. Part of your homework will be to think about why I’m doing this. You can email me your answer and I’ll let you know if you’re correct.

Next we use our success() callback which will let us know if the form data was correct and that we actually changed the vote in our database.

Now, all we need to do to finish our JQuery is change the alert("success"); call to instead remove our form and show the new results of our question. This is performing pretty much exactly like our original Django Project, however, you can see I keep the list of questions on the page. Why am I doing that? This question is another homework question to think about or reply to this email.

Display the Results!

Let’s finish sendVote and display the results of our vote.

polls/templates/polls/index.html

<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  </head>
  <body>
    <div id="questions">
      <p>No polls are available.</p>
    </div>
    <div>
      <h1 id="question_text"></h1>
      <div id="results">
        <form id="question_form" action="#">
          {% csrf_token %}
        </form>
      </div>
    </div>
    <script>
    $(document).ready(function() {
      $.get("/polls/api/questions").success(function (data) {
        var question_list = "<ul>";
        for (question in data) {
          question_list += "<li><a href='#' onclick='loadQuestion("+data["id"]+")'>"+data["question_text"]+"</a></li>";
        }
        if (question_list !== "<ul>") {
          $("#questions").html(question_list + "</ul>");
        }
      });
    });

    var sendVote = function(question_id) {
      var form_data = $("#question_form").serialize();
      $.post('/polls/'+question_id+'/vote/', form_data).success(function() {
        $.get('/polls/api/questions/'+question_id).success(function (data) {
          var choice_list = "<ul>";
          for (choice in data["choices"]) {
            var votes = data["choices"][choice]["votes"];
            var plural = votes > 1 || votes == 0 ? "s" : "";
            choice_list += "<li>"+data["choices"][choice]["choice_text"]+" -- "+ votes +" vote"+plural+"</li>";
          }
          $("#results").html(choice_list + "</ul>");

        });
      });
    }

    var loadQuestion = function (question_id) {
      $.get("/polls/api/questions/"+question_id).success(function (data) {
        $("#question_text").html(data["question_text"]);
        var form = "";
        for (choice in data["choices"]) {
          form += "<input type='radio' name='choice' id='choice"+choice+"' value='"+data["choices"][choice]["id"]+"' />";
          form += "<label for='choice"+choice+"'>"+data["choices"][choice]["choice_text"]+"</label><br />";
        }
        $("#question_form").append(form + "<input type='button' onclick='sendVote("+question_id+")' value='Vote' />");
      });
    }
    </script>
  </body>
</html>

Now, the project is finished. We used our API to get questions and choices. We used some JQuery to put it all together into one coherent whole. There is only one problem. This won’t allow you to vote again. The original program will let you vote again, what’s the deal? It has something to do with CSRF Token in POST requests. And I can think of atleast 3 ways you can figure out how to get it to work.

So, now that we are officially done with learning how to create REST APIs in Django and how to use them in your apps using JQuery, I have some serious homework for you to do!

Homework

  1. Do all the steps above to get yours to work. Then, compare your work with mine.
  2. Answer the following questions and either think about them on your own or email me and I’ll reply with the answer:
    • Why am I creating an AJAX POST request to /polls/{question_id}/vote instead of /polls/api/questions/{question_id}?
    • Why am I keeping a list of questions in our Single Page Application?
  3. In today’s lesson, I didn’t add a link to “Vote again?”. There are three possible ways you can do this. Can you think of a way and implement it? HINT: It has something to do with the CSRF Token. If you give up, you can download my answer here

Congratulations!

You made it through the Django REST Framework Email Course! I want to thank you very much for taking this course. Again, if you have any questions, concerns, love, hate just reply to this lesson’s email and send it to me. I really hope you learned a TON about REST. But, the show isn’t over yet! For sticking through this series, I’m sending you 2 bonus lessons!!

  1. Bonus Lesson #1: Django + REST + AngularJS
  2. Bonus Lesson #2: Django REST Framework User Authentication

Stick around! There’s more to come!

Thanks so much,

Chris