Home Blog Super Simple, Persistent, Dynamically Generated Django Avatars

DEV

Super Simple, Persistent, Dynamically Generated Django Avatars

Posted by julian.a on Jan. 27, 2017, 8:40 a.m.

You have probably noticed by now that lots of websites with a social element will have persistent avatars for users even if they've never uploaded one themselves. Probably the most common solution is just to rely on a third party service like Gravatar, and that's a reasonable choice. If you want to do that, implementation in Django is dead simple. That said if you don't want to rely on a third party service, or if you want avatars specific to your site (rather than the "Globally Recognized" avatars Gravatar promises), it turns out that implementing this yourself in Django is actually really easy.

The basic problem is a simple one - we want to generate a randomized image for a user, and we want that image to be persistent. The naive approach is to generate an image once and store it on the server. This works, but at the cost of storing redundant data; we can do better. Since each user already has a unique, persistant id we can use that to generate a distinct image procedurally without storing any files or data. Better yet, by using a custom Django template tag we can implement this with a minimum of code. Here's a simple implementation:

# projects/templatetags/avatar.py
from django import template
from django.template.loader import render_to_string

import hashlib

register = template.Library()

NUM_COLORS = 10


@register.filter
def avatar(user):
    letter = user.name[0].upper() if user.name else '?'
    color_ix = int(hashlib.md5(str(user.id).encode()).hexdigest(), 16) % NUM_COLORS
    context = {'color_ix': color_ix, 'letter': letter}
    return render_to_string('accounts/avatar.svg', context)

# accounts/templates/accounts/avatar.svg
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" class="avatar color-{{ color_ix }}">
  <rect x="0" y="0" width="100%" height="100%"></rect>
  <text x="50%" y="50%">{{ letter }}</text>
</svg>

We just {% load avatar %} in our template and use the tag with {{ user|avatar }} where user is a user object. For instance, if we want to show the logged in user's avatar somewhere we'd use {{ request.user|avatar }} (assuming we have context_processors.request installed). Since we're just rendering inline SVGs, we can use CSS to manage fonts, colors, and layout. Another advantage of the SVGs is that there are no external dependencies.

Fancied up with a little CSS (thanks to Justin!) here's an example of the output:

avatar.png

For the sake of clarity, I've ignored unicode issues and stuck to just 10 distinct images per letter. The md5 hash is just there to shuffle things up so that the colors aren't an obvious function of user id. This example bases the color on the user id and includes the first letter of the user's name in the avatar. This has the advantage that the color will always be the same for a given user, though the letter could change if a user changes his name. An avatar based only on the id would be immutable, which might be better for some purposes. There's also an argument for basing the avatar on the email address like gravatar does. There are a lot of options, and they're all pretty easy to implement! For instance, I wanted users to be able to upload their own avatars, so I check for a custom avatar in the filter before falling back on the generated one.

This particular avatar is designed to be as simple as possible, but the possibilities are endless. All we need to do is to write Python code that generates images based on an integer. A general approach would be to use the random module to generate an image and then we can use random.seed with the hash to make the output consistent per user. Generating a unique image like this for each user wouldn't be hard!

So with about 20 lines of code (and a little CSS) we've implemented basic, persistent, custom avatars. It definitely makes me happy to be using Django!