Annotations Tutorial: Moving towards AJAX APIs

In this part of our per-paragraph commenting app, we will replace the regular POST requests to make annotations with AJAX powered requests.


In this tutorial, we will improve upon the POST request views that we made in the previous tutorial and use Django's inbuilt serialization and deserialization techniques to post annotations and retrieve them.

How Django and Ajax work together(Image Credits and Copyright)

With Ajax, some things change, but it definitely makes the life more efficient for the users. They no longer have to deal with page reloads when they make annotations. Also, with the advent of internet apps, and app based models (and mobile apps as well), the loading of resource too is being actively delegated to apps, which come as closed boxes such that their styles do not affect other things on that page. So, the internet model is approaching a plug-and-play model, supplanting such self-contained apps in place of a backend served monolith. This is akin to what assembled computers are to their OEM counterparts. The user gets to choose what he/she wants.

It also makes things a little easier for developers, as they no longer have to deal with redirects on submission of forms to prevent duplication. However, there are new issues too, and also some old wine in new bottles!

While there are many problems that could arise, I have in mind one particular problem. That is of the CSRF token. So far, we haven't used any template (we haven't used any frontend either) but I'd like to stick to it. In case of Django forms, a simple {% csrf_token %} template tag inserted a CSRK Token in the form itself while it was being served up by the backend server. With the frontend now depending on Ajax (where we are not using a backend delivered form), where should we get and keep the CSRF token? Why should I have to put a CSRF token somewhere in the DOM as an element when I am not rendering the full form? I won't! So, we read this piece of Django Documentation

For now, we won't actually need it, for we are not actually making an Ajax call but passing serialized data in JSON format. We haven't hooked up the front-end with the backend yet, and it will stay so for a while. I believe that if interface-specifications are agreed upon and followed properly, integration mustn't be too much of a hassle.

So, this is how we'd change our tests:

def test_POST_annotation(self):
    #use test client to POST a request
    string_data = {
        'content_type': '9',
        'object_id':'1',
        'paragraph':'1',
        'body':'Dreaming is good, day dreaming, not so good.',
        'author':str(self.user.id),
        'privacy':'3',
        'privacy_override': '0',
        'shared_with': ['1'],
        }
    json_data = json.dumps(string_data)
    response = self.client.post(
        '/annotations/',
        content_type='application/json',
        data = json_data,
        )

and

def test_retrieve_annotations_for_post(self):
    #use test client to visit the page
    #create a few annotations first
    string_data = {
        'content_type': '9',
        'object_id':'1',
        'paragraph':'1',
        'body':'Dreaming is good, day dreaming, not so good.',
        'author':str(self.user.id),
        'privacy':'3',
        'privacy_override': '0',
        'shared_with': ['1'],
        }
    json_data = json.dumps(string_data)
    response = self.client.post(
        '/annotations/',
        content_type='application/json',
        data = json_data,
        )
    response = self.client.get('/annotations/?content_type=blogcontent&object_id=1')
    #get must return annotations in an HttpResponse object.
    print(str(json.loads(response.content)))
    self.assertContains(response, 'Dreaming is good, day dreaming, not so good.')

We added a content_type attribute to our POST call, and serialized the Dictionary using JSONs dumps() method. Note one change , however: Now we are passing a list of ' shared_with ' values, rather than just a string. This is because in JSON, it is being sent as a multiple selection. So, even if we don't have it shared with anybody, there must be an empty list.

On receiving it in our view, we will change the code slightly, to handle the JSON.

if request.method == 'POST':
    #Handle the post request
    json_data = json.loads(request.body)
    annotation_form = AnnotationForm(json_data)
    annotation_json = {
        'body': annotation_form.cleaned_data['body'],
        'paragraph':annotation_form.cleaned_data['paragraph'],                           
        }
    return HttpResponse(
        json.dumps(annotation_json),
        content_type='application/json'
        )

Note that now, we no longer redirect to the page (for it is Ajax, and we are not supposed to reload.)

For a GET , we use the built in serializer in Django (for it can handle Foreign Keys):

annotation_json =serializers.serialize('json', annotation)
return HttpResponse(
    annotation_json,
    content_type='application/json'
    )

Running the tests, and everything passes. Good! The output returned looks like:

[{u'pk': 1, u'model': u'annotations.annotation', u'fields': {u'body': u'Dreaming is good, day dreaming, not so good.', u'privacy': 3, u'author': 1, u'object_id': u'1', u'date_created': u'2015-02-23T18:10:58.818Z', u'paragraph': 1, u'content_type': 9, u'privacy_override': True, u'date_modified': u'2015-02-23T18:10:58.818Z'}}]

What have we done until yet?

  1. We can create annotations using JSON objects (Ajax ready)
  2. We can retrieve annotations in JSON format (again, Ajax ready)

What we haven't done yet?

  1. Hooking up the frontend form with the backend.
  2. Creating a private annotation
  3. Fetching annotations made by a user (different user, so only public annotations must be fetched)
  4. Fetching annotations we have made and their reverse URL to the posts they were made on.
  5. Editing an annotation.
  6. Deleting an annotation
  7. Overriding the privacy of annotation (provided we are allowed to do so)
  8. Sending a notification to other users (if annotation is shared)

By the time I had reached here, I had heard that using Ajax frameworks makes our lives very easy. So, I would also like to cut to the chase and get down to using them first and implementing these features (alongwith what we've already developed) in those frameworks. For a learning experience though, if you should insist (and I will need an overwhelming amount of requests to do that, because I have not done it before) I could fill in this place with another tutorial of making those things without anything else but Django only. But that would only prove what we already know, frameworks make lives easier and code shorter. If they've done it already, why repeat it? (expect of course for the learning experience).

Also note that in this tutorial series, we won't be working on points 2,7 and 8. Also, we'd only be providing groundwork for 3 and 4, i.e. the functionality will be present, but not displayed or used from the frontend as it is. That is little more work and I'd like you to do that (as homework) for fun.

So, in our next tutorial, we'll start using Django Rest Framework to power our annotations app rather than regular AJAX, and finally wire the backend with the frontend.