A nicer way of using the Django 1.2 messages framework

May 11, 2010

Update: Someone asked me "why?" recently, claiming you could just order messages in the view. It slipped my mind that the whole point of this solution is that it's usable in any template without extra work, so you can render the messages out in your base template and then forget about it.


In Django versions prior to 1.2 adding user feedback messages was cumbersome. In your view you'd use a snippet like this request.user.message_set.create(message='My message.').

to add a message to be retrieved and displayed to the user in the template. Messages were also attached to the current user and had no severity level.

Version 1.2 brings an easy to use messages framework. Adding a new message becomes as simple as messages.success(request, 'Your details have been updated.') or messages.warning(request, 'Your password expires in one week.'). Built-in levels are debug, info, success, warning and error, and adding your own is easy.

To retrieve the messages in the template you need to make sure your view renders the template with the current request as the context_instance like so:

def my_view(request):
    # ...
    return render_to_response('my_template.html',
    	my_dict,
        context_instance=RequestContext(request))

And them spit them out in the template like this:

{% if messages %}
  <ul>
    {% for message in messages %}
      <li>{{ message }}</li>
    {% endfor %}
  </ul>
{% endif %}

This is where I think the framework falls down. All your info, success, warning and error messages are in the same list. Whilst you can check the tag/level and include it as a CSS class for the <li> you can't get around that it's still the same list. It's quite common to see success and warning messages styled differently in <div>s with red and green backgrounds, and as far as I can tell there's no way of doing this with the framework without hackery.

To counter this I wrote a template tag that accepts the messages instance and separates all the messages out into separate <ul>s ordered by level so I can style up something like this.

Example messages screenshot

The template tag is included in full below and you are free to use it as you wish. (Working example project on Github.)

from django import template
register = template.Library()

class MessagesNode(template.Node):
    """ Outputs grouped Django Messages Framework messages in separate
        lists sorted by level. """
    
    def __init__(self, messages):
        self.messages = messages
        
    def render(self, context):
        try:
            messages = context[self.messages]
            
            # Make a dictionary of messages grouped by tag, sorted by level.
            grouped = {}
            for m in messages:
                # Add message
                if (m.level, m.tags) in grouped:
                    grouped[(m.level, m.tags)].append(m.message)
                else:
                    grouped[(m.level, m.tags)] = [m.message]

            # Create a list of messages for each tag.
            out_str = ''
            for level, tag in sorted(grouped.iterkeys()):
                out_str += '<div> class="messages %s">\n<ul">' % tag
                for m in grouped[(level, tag)]:
                    out_str += '<li>%s</li>' % (m)
                out_str += '</ul>\n</div>\n'
                
            return out_str
            
        except KeyError:
            return ''
        
@register.tag(name='render_messages')
def render_messages(parser, token):
    parts = token.split_contents()
    if len(parts) != 2:
        raise template.TemplateSyntaxError("%r tag requires a single argument"
                                           % token.contents.split()[0])
    return MessagesNode(parts[1])

Calling the tag form your template is then done like this:

{% load messages %}
...
{% render_messages messages %}

Easy!

This gives you a separate list, in a <div>, for each tag/level in the messages object (see below) so you're free to style them all differently.

<div class="messages success">
  <ul>
    <li>This is a success message.</li>
  </ul>
</div>
<div class="messages error">
  <ul>
    <li>This is an error message.</li>
    <li>This is another error message.</li>
  </ul>
</div>

Visit Github for an example site using all the code, with sample CSS and images ready to drop into your own projects.

Categories: Django, Programming, Python

View Comments

blog comments powered by Disqus