Adding AJAX actions with jQuery

Now we are going to add AJAX actions to our application. AJAX comes from Asynchronous JavaScript and XML. This term encompasses a group of techniques to make asynchronous HTTP requests. It consists in sending and retrieving data from the server asynchronously, without reloading the whole page. Despite the name, XML is not required. You can send or retrieve data in other formats such as JSON, HTML, or plain text.

We are going to add a link to the image detail page to let users click it to like an image. We will perform this action with an AJAX call to avoid reloading the whole page. First, we are going to create a view for users to like/unlike images. Edit the views.py file of the images application and add the following code to it:

from django.http import JsonResponse
from django.views.decorators.http import require_POST

@login_required
@require_POST
def image_like(request):
    image_id = request.POST.get('id')
    action = request.POST.get('action')
    if image_id and action:
        try:
            image = Image.objects.get(id=image_id)
            if action == 'like':
                image.users_like.add(request.user)
            else:
                image.users_like.remove(request.user)
            return JsonResponse({'status':'ok'})
        except:
            pass
    return JsonResponse({'status':'ko'})

We are using two decorators for our view. The login_required decorator prevents users that are not logged in from accessing this view. The require_GET decorator returns an HttpResponseNotAllowed object (status code 405) if the HTTP request is not done via GET. This way we only allow GET requests for this view. Django also provides a require_POST decorator to only allow POST requests and a require_http_methods decorator to which you can pass a list of allowed methods as an argument.

In this view we use two GET parameters:

  • image_id: The ID of the image object on which the user is performing the action
  • action: The action that the user wants to perform, which we assume to be a string with the value like or unlike

We use the manager provided by Django for the users_like many-to-many field of the Image model in order to add or remove objects from the relationship using the add() or remove() methods. Calling add() passing an object that is already present in the related object set does not duplicate it and thus, calling remove() passing an object that is not in the related object set does nothing. Another useful method of the many-to-many manager is clear(), which removes all objects from the related object set.

Finally, we use the JsonResponse class provided by Django, which returns an HTTP response with an application/json content type, converting the given object into a JSON output.

Edit the urls.py file of the images application and add the following URL pattern to it:

url(r'^like/$', views.image_like, name='like'),

Loading j Query

We need to add the AJAX functionality to our image detail template. In order to use jQuery in our templates, we are going to include it in the base.html template of our project first. Edit the base.html template of the account application and include the following code before the closing </body> HTML tag:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
  $(document).ready(function(){
    {% block domready %}
    {% endblock %}
     });
</script>

We load the jQuery framework from Google, which hosts popular JavaScript frameworks in a high-speed reliable content delivery network. You can also download jQuery from http://jquery.com/ and add it to the static directory of your application.

We add a <script> tag to include JavaScript code. $(document).ready() is a jQuery function that takes a handler that is executed when the DOM hierarchy has been fully constructed. DOM comes from Document Object Model. The DOM is created by the browser when a webpage is loaded, and it is constructed as a tree of objects. By including our code inside this function we make sure all HTML elements we are going to interact with are loaded in the DOM. Our code will be only executed once the DOM is ready.

Inside the document ready handler function, we include a Django template block called domready, in which templates that extend the base template will be able to include specific JavaScript.

Don't get confused with JavaScript code and Django template tags. Django template language is rendered in the server side outputting the final HTML document and JavaScript is executed in the client side. In some cases, it is useful to generate JavaScript code dynamically using Django.

Note

In the examples of this chapter we include JavaScript code in Django templates. The preferred way to include JavaScript code is by loading .js files, which are served as static files, especially when they are large scripts.

Cross-Site Request Forgery in AJAX requests

You have learned about Cross-Site Request Forgery in Chapter 2, Enhancing Your Blog With Advanced Features. With the CSRF protection active, Django checks for a CSRF token in all POST requests. When you submit forms you can use the {% csrf_token %} template tag to send the token along with the form. However, it is a bit inconvenient for AJAX requests to pass the CSRF token as POST data in with every POST request. Therefore, Django allows you to set a custom X-CSRFToken header in your AJAX requests with the value of the CSRF token. This allows you to set up jQuery or any other JavaScript library, to automatically set the X-CSRFToken header in every request.

In order to include the token in all requests, you need to:

  1. Retrieve the CSRF token form the csrftoken cookie, which is set if CSRF protection is active.
  2. Send the token in the AJAX request using the X-CSRFToken header.

You can find more information about CSRF protection and AJAX at https://docs.djangoproject.com/en/1.8/ref/csrf/#ajax.

Edit the last code you included in your base.html template and make it look like the following:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src=" http://cdn.jsdelivr.net/jquery.cookie/1.4.1/jquery.cookie.min.js "></script>
<script>
  var csrftoken = $.cookie('csrftoken');
  function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
  }
  $.ajaxSetup({
    beforeSend: function(xhr, settings) {
      if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
        xhr.setRequestHeader("X-CSRFToken", csrftoken);
      }
    }
  });
  $(document).ready(function(){
    {% block domready %}
    {% endblock %}
  });
</script>

The code above is as follows:

  1. We load the jQuery Cookie plugin from a public CDN, so that we can interact with cookies.
  2. We read the value of the csrftoken cookie.
  3. We define the csrfSafeMethod() function to check whether an HTTP method is safe. Safe methods don't require CSRF protection. These are GET, HEAD, OPTIONS, and TRACE.
  4. We setup jQuery AJAX requests using $.ajaxSetup(). Before each AJAX request is performed, we check if the request method is safe and the current request is not cross-domain. If the request is unsafe, we set the X-CSRFToken header with the value obtained from the cookie. This setup will apply to all AJAX requests performed with jQuery.

The CSRF token will be included in all AJAX request that use unsafe HTTP methods such as POST or PUT.

Performing AJAX requests with jQuery

Edit the images/image/detail.html template of the images application and replace the following line:

{% with total_likes=image.users_like.count %}

With the following one:

{% with total_likes=image.users_like.count users_like=image.users_like.all %}

Then modify the <div> element with the image-info class as follows:

<div class="image-info">
  <div>
    <span class="count">
      <span class="total">{{ total_likes }}</span>
      like{{ total_likes|pluralize }}
    </span>
    <a href="#" data-id="{{ image.id }}" data-action="{% if request.user in users_like %}un{% endif %}like" class="like button">
      {% if request.user not in users_like %}
        Like
      {% else %}
        Unlike
      {% endif %}
    </a>
  </div>
  {{ image.description|linebreaks }}
</div>

First, we add another variable to the {% with %} template tag to store the results of the image.users_like.all query and avoid executing it twice. We display the total number of users that like this image and include a link to like/unlike the image: We check if the user is in the related object set of users_like to display either like or unlike based on the current relationship of the user and this image. We add the following attributes to the <a> HTML element:

  • data-id: The ID of the image displayed.
  • data-action: The action to run when the user clicks the link. This can be like or unlike.

We will send the value of both attributes in the AJAX request to the image_like view. When a user clicks the like/unlike link, we need to perform the following actions on the client side:

  1. Call the AJAX view passing the image ID and the action parameters to it.
  2. If the AJAX request is successful, update the data-action attribute of the <a> HTML element with the opposite action (like / unlike), and modify its display text accordingly.
  3. Update the total number of likes that is displayed.

Add the domready block at the bottom of the images/image/detail.html template with the following JavaScript code:

{% block domready %}
  $('a.like').click(function(e){
    e.preventDefault();
    $.post('{% url "images:like" %}',
      {
        id: $(this).data('id'),
        action: $(this).data('action')
      },
      function(data){
        if (data['status'] == 'ok')
        {
          var previous_action = $('a.like').data('action');
                   
          // toggle data-action
          $('a.like').data('action', previous_action == 'like' ? 'unlike' : 'like');
          // toggle link text
          $('a.like').text(previous_action == 'like' ? 'Unlike' : 'Like');
                   
          // update total likes
          var previous_likes = parseInt($('span.count .total').text());
          $('span.count .total').text(previous_action == 'like' ? previous_likes + 1 : previous_likes - 1);              
        }
      }
    );
  });
{% endblock %}

This code works as follows:

  1. We use the $('a.like') jQuery selector to find all <a> elements of the HTML document with the class like.
  2. We define a handler function for the click event. This function will be executed every time the user clicks the like/unlike link.
  3. Inside the handler function we use e.preventDefault() to avoid the default behavior of the <a> element. This will prevent from the link taking us anywhere.
  4. We use $.post() to perform an asynchronous POST request to the server. jQuery also provides a $.get() method to perform GET requests and a low-level $.ajax() method.
  5. We use Django's {% url %} template tag to build the URL for the AJAX request.
  6. We build the POST parameters dictionary to send in the request. These are the ID and action parameters expected by our Django view. We retrieve these values from the <a> element's data-id and data-action attributes.
  7. We define a callback function that is executed when the HTTP response is received. It takes a data attribute that contains the content of the response.
  8. We access the status attribute of the data received and check if it equals to ok. If the returned data is as expected, we toggle the data-action attribute of the link and its text. This allows the user to undo his action.
  9. We increase or decrease the total likes count by one, depending on the action performed.

Open the image detail page in your browser for an image you have uploaded. You should be able to see the following initial likes count and the LIKE button as follows:

Performing AJAX requests with jQuery

Click the LIKE button. You will see that the total likes count increases in one and the button text changes into UNLIKE like this:

Performing AJAX requests with jQuery

When you click the UNLIKE button the action is performed, the button's text changes back to LIKE and the total count changes accordingly.

When programming JavaScript, especially when performing AJAX requests, it is recommended to use a tool such as Firebug for debugging. Firebug is a Firefox add-on that allows you to debug JavaScript and monitor CSS and HTML changes. You can download Firebug from http://getfirebug.com/. Other browsers such as Chrome or Safari also include developer tools to debug JavaScript. In those browsers you can right-click anywhere in the website and click on Inspect element to access the web developer tools.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset