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 actionaction
: The action that the user wants to perform, which we assume to be a string with the value like or unlikeWe 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'),
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.
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:
csrftoken
cookie, which is set if CSRF protection is active.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>
csrftoken
cookie.csrfSafeMethod()
function to check whether an HTTP method is safe. Safe methods don't require CSRF protection. These are GET
, HEAD
, OPTIONS
, and TRACE
.$.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
.
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:
data-action
attribute of the <a>
HTML element with the opposite action (like
/ unlike
), and modify its display text accordingly.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:
$('a.like')
jQuery selector to find all <a>
elements of the HTML document with the class like
.click
event. This function will be executed every time the user clicks the like
/unlike
link.e.preventDefault()
to avoid the default behavior of the <a>
element. This will prevent from the link taking us anywhere.$.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.{% url %}
template tag to build the URL for the AJAX request.ID
and action
parameters expected by our Django view. We retrieve these values from the <a>
element's data-id
and data-action
attributes.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.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:
Click the LIKE button. You will see that the total likes count increases in one and the button text changes into UNLIKE like this:
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.