Displaying Django User Messages with Angular.js

Django’s Messages framework is an elegant workhorse, and I’ve never built a Django site that didn’t use it for displaying success/failure/info messages to users after certain actions are taken (like logging in successfully or adding an item to a cart).

But wouldn’t it be cool if you could use that functionality client-side, delivering user messages to be processed as JSON data rather than statically outputting messages to generated HTML? On a recent project, I needed to do this because Varnish caching doesn’t let you mark page fragments as non-cacheable, so statically generated messages were not an option. But there are all sorts of reasons you might want to handle Django Messages client-side.

messagedisplay

Here’s how to accomplish the job in a really lightweight way, without the need for a full-blown REST API app like Django Rest Framework or Tastypie, and with Angular.js (which is, IMO, the best of the current crop of JavaScript application frameworks).

If you don’t have a full-blown API generator stack for your app, you can always use simple Django serialization to generate JSON, but unfortunately Django’s native serializer only serializes querysets, not custom data sets. This simple utility class can live in some re-usable corner of your project and be used for all sorts of things:

# json_response.py
from django.http import HttpResponse
from django.utils import simplejson

class JsonResponse(HttpResponse):
    def __init__(self, data):
        content = simplejson.dumps(
          data,
          indent=2,
          ensure_ascii=False
        )
        super(JsonResponse, self).__init__(content=content,
                                           mimetype='application/json; charset=utf8')

It can then be imported into any view and used like:

from myapp.utils import JsonResponse
bar = JsonResponse(data)

So in our case, we need to ask Django’s internal API for any outstanding user messages, append them to a standard Python list, and hand them off to our custom serializer:

# views.py
from django.contrib.messages import get_messages
from myapp.utils.json_response import JsonResponse

def get_user_messages(request):
    """Return stored Django messages as JSON data."""

    storage = get_messages(request)
    data = []

    for message in storage:
        data.append({
            "text" : message.message,
            "status" : message.tags,
        })

    return JsonResponse(data)

Finally, wire it up in urls.py:

from myapp.views import get_user_messages

url(r'^messages/$', get_user_messages, name='user_messages'),

Now accessing `/messages` in a browser will display a JSON dump of pending messages for the requesting user.

messagesapi2

The nature of Django messages is that they’re marked as ‘read’ as soon as they’re viewed, so if you refresh the feed in your browser, they’ll be gone. So for testing purposes, you’ll want a way to constantly regenerate new messages. Add some temporary code like this to the same view, so that a new message is generated every time you refresh the feed. Remember to remove this before deploying!

from django.contrib import messages
messages.add_message(request, messages.INFO, 'Hello world.')

OK, your feed is done – time to consume it with Angular. Assuming Angular is already installed and working on your site, you’ll need a simple controller like this:

function ListMessages($scope, $http) {
    // Obtain Django user messages via simple API

    $http({method: 'GET', url: '/messages'}).
      success(function(data, status, headers, config) {
        // Called asynchronously when the response is available
        $scope.messages_list = data;
      }).
      error(function(data, status, headers, config) {
        // Called asynchronously if an error occurs
        console.log("Failed to retrieve user messages");
      });
}

That controller will of course need to be included in your app in the usual way. Then all you need is a corresponding `ng-app` in your HTML to display it with. Since you want to display messages on any page of your site in the same way, this should probably be in your base template, or live in a separate file that’s included in your base template:

{% verbatim %}
<div ng-controller="ListMessages">
    <div ng-show="messages_list.length">
        <div
            ng-cloak
            data-alert
            class="ajaxmessage alert-box alert-{{ message.status }}"
            ng-repeat="message in messages_list">
            {{ message.text }}
            <a href="#" class="close">&times;</a>
        </div>
    </div>
</div>
{% endverbatim %}

A couple of things to note here. We use Django’s `verbatim` tag because both Django and Angular use the same double-curly-brace syntax and we don’t want Django to parse the block.

`ng-show` prevents the div from being displayed at all if the returned data has no length (is empty).

`ng-cloak` prevents any flickering in the display as Angular does its stuff behind the scenes – with that in place, the div won’t display until all messages have been retrieved from the API, processed, and are ready for display.

{{message.status}} appends ‘success’, ‘failure’, or ‘info’ to the class name depending on the status the message was given when it was generated (this is just for CSS styling).

data-alert and the “Close” link are just there to let the user dismiss the alert when they’re done reading. In this case, I’m using Zurb Foundation’s Alert Box classes to handle this so I don’t need to write anything special.

And that’s it! Ajaxified Django Messages, lightweight and simple.

3 Replies to “Displaying Django User Messages with Angular.js”

  1. You can use django’s encoder to serialize custom data this way:

    from django.core.serializers.json import DjangoJSONEncoder
    import json

    json.dumps(data, cls=DjangoJSONEncoder)

  2. Interesting – I had not seen DjangoJSONEncoder in the docs before. But trying this technique, I get:

    `’str’ object has no attribute ‘delete_cookie’`

    Probably solvable, but am going to stick with what works for now.

Leave a Reply

Your email address will not be published. Required fields are marked *