# django-avatar and the small job of putting a face on a Django user

> django-avatar is a Jazzband-packaged Django app that stores user avatars, generates thumbnails through Pillow, writes them to your configured storage backend, and falls back to Gravatar when a user has no image. It is a narrow package with a wide compatibility claim, from Django 4.2 to 6.0.

**jazzband/django-avatar** — A Django app for handling user avatars.

- Repository: https://github.com/jazzband/django-avatar
- Stars: 827 · Forks: 298
- Language: Python
- License: BSD-3-Clause
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/jazzband-django-avatar

## A user image, a thumbnail, and a Gravatar when nothing is uploaded

django-avatar exists to solve a small problem that most Django projects solve badly: where a profile picture comes from and how it is served. The package describes itself as a reusable application for handling user avatars, and it has two defining behaviours. It defaults to Gravatar when no avatar is found for a given user, and it automatically generates thumbnails and stores them to your default file storage backend so they can be retrieved later.

That second behaviour is the one with architectural consequences. Generated files are not part of the application package and are not written into a static directory by hand. They go through the storage backend that the project has already configured, which means the same settings decide whether avatars live on local disk, on object storage, or behind a CDN.

For teams that is the right shape, because avatar storage then follows the same rules as every other uploaded file in the project. For teams whose avatars are URLs pointing at an external profile service, the package solves a problem they do not have, and a plain URL field on the user model is a smaller decision.

## Thumbnails go through the storage backend, not through static files

The data flow has two ends. On the write side, an uploaded image is resized by the thumbnail generator and the result is handed to the default file storage backend. On the read side, the stored file is retrieved later by the application and rendered through a template.

The template layer is part of the package rather than something a project writes. The packaging configuration lists the shipped data explicitly, and the avatar package carries HTML templates plus locale files for translations, alongside a default image used when nothing else is available:

```python
package_data={
    "avatar": [
        "templates/notification/*/*.*",
        "templates/avatar/*.html",
        "locale/*/LC_MESSAGES/*",
        "media/avatar/img/default.jpg",
    ],
}
```

The presence of notification templates and a bundled default image is a small design decision with a visible consequence: an account with no picture and no Gravatar still renders something, and the notification templates cover the case where a user is told their avatar changed. The application code lives in the `avatar/` package directory, with a `docs/` tree, a `tests/` directory and a `test_proj/` project used for exercising the package in a realistic Django layout.

## Three dependencies, and the two that carry real weight

The runtime requirements are short, which is unusual for a package that manipulates images and talks to an email-based avatar service:

```
Pillow>=10.0.1
django-appconf>=1.0.5
dnspython>=2.3.0
```

Pillow does the image work behind the automatic thumbnails. django-appconf is the Jazzband convention for application configuration, where settings live in a per-app config object rather than in a single global settings module, and that is the mechanism behind per-project options such as avatar size and the Gravatar default. dnspython is a DNS resolver library, and its presence in a requirements file that otherwise contains an imaging library and a configuration library is a hint that email domains are resolved somewhere in the Gravatar path. The documentation excerpt available here does not spell out how it is used, so the exact mechanism would need to be read in the source before assuming anything about it.

Three pinned floors and nothing else means the dependency footprint of adopting this package is small: an image library you probably already have, a configuration helper in the same family as the rest of the Jazzband collection, and a resolver.

## The version number lives in the package, not in the packaging file

The build is set up with setuptools and pulls its version dynamically from the package source rather than duplicating it:

```python
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M)
```

The packaging configuration points at that attribute, and the regular expression raises a RuntimeError when the version string cannot be found, so a missing `__version__` fails the build loudly instead of shipping an unnamed release.

A legacy `setup.py` sits next to the modern `pyproject.toml` and still carries the package data list, the exclusion of the tests package from the distribution, and `zip_safe=False`. That last flag is a deliberate choice for a Django app: the package must be installed as unpacked files on disk so its templates and media can be found on the filesystem path rather than inside a zipped egg. Keeping both files means a project on an older toolchain can still install the package, which is consistent with the wide Django range the metadata claims.

## Django 4.2 through 6.0, and a release history with a gap

The declared support matrix is broad. The classifiers list Django 4.2, 5.0, 5.2 and 6.0, and Python 3.10 through 3.14, with the project status marked as Development Status 5, Production/Stable.

```
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.0",
"Framework :: Django :: 5.2",
"Framework :: Django :: 6.0",
```

The release history, though, has an uneven shape. The newest tag is v9.0.0, released on 2026-01-07 with the note about closing all open files, and before it sits v8.0.1 from 2024-09-05, which was a Django 5.1 compatibility release. Between those two points, a version jump happened, and before v8.0.1 the recorded tag is 3.1.0 from 2016-09-10, which suggests the numbering scheme was reset along the way. The last push to the repository was on 2026-10-19 and the repository is not archived, so the code is still moving, but the tags are not a reliable guide to how recently a given Django version was actually tested.

The documentation is hosted at django-avatar.readthedocs.io, and the repository carries a CHANGELOG.rst, a CONTRIBUTING.md and a pre-commit configuration, which tells you where the change history lives.

## Adding the app to a project and generating a thumbnail

The project pages point at the package on PyPI and at the hosted documentation for the setup instructions, and the README does not print a shell command, so the installation step is a normal package install of `django-avatar` followed by adding the `avatar` app to your Django project's INSTALLED_APPS list. The application package is the `avatar/` directory, and the templates it renders come from the package data listed earlier, so nothing needs copying into your project tree.

In a local checkout the package is installed from the source tree, with the same requirements installed first:

```
Pillow>=10.0.1
django-appconf>=1.0.5
dnspython>=2.3.0
```

The first real use is the ordinary Django one. A user model with an avatar field is pointed at the app's field and form, an upload is accepted by the form, and the application generates a thumbnail and hands it to the default storage backend. Because the thumbnail lands in whatever storage the project has configured, the file then appears in that backend rather than in a media directory you manage by hand, and the template URL comes from the storage API.

For a request with no uploaded image, the Gravatar default applies. The upstream documentation at django-avatar.readthedocs.io is where the field options and the configuration keys for that behaviour are described.

## When an avatar field or a service URL is the better answer

There are projects for which this package is the wrong shape. If your users authenticate through a service that already exposes a profile picture URL, a URL field on the user model and a template tag is a smaller solution with no image processing and no storage writes. The package's value comes from generating and storing derivatives, and that value disappears if you never need a derivative.

There is a second limit worth naming. The Gravatar default ties the fallback avatar to an email address, which means the avatar identity follows the account's email. Projects where the account identifier is a username, an employee number or a hardware token need to check that the fallback path behaves for them, and the documentation excerpt available here does not describe how the fallback is resolved for a user without an email.

The upstream project itself is the honest alternative for teams that only need email-based images. The Gravatar service gives a stable image for an email hash with no server-side storage at all. django-avatar is worth the extra dependency when you need uploaded images, generated thumbnails and a uniform template, and it is heavier than necessary when the account already carries a picture.

## Conclusion

django-avatar is a sensible fit for a Django project that already stores per-user images in its own storage backend and wants Gravatar as the zero-setup default. It is the wrong choice if your avatars are external URLs, if you need avatars tied to something other than an email address, or if you want a component that keeps pace with new Django releases on a predictable schedule, since the last push was on 2026-10-19 while the newest tagged release is v9.0.0 from 2026-01-07. Verify first that your Django version falls inside the declared range, and read the LICENSE.txt file before vendoring the code into a product.

## FAQ

### What happens when a Django user has no avatar uploaded?

The app defaults to Gravatar when no avatar is found for a user. If there is no uploaded image, the avatar falls back to the Gravatar representation of the account, and the package also ships a default image as a further fallback.

### Where are django-avatar thumbnails stored?

Thumbnails are generated automatically and written to your default file storage backend for later retrieval, rather than being placed in a media directory by the app itself. The shipped templates under `templates/avatar/` render the stored file.

### Which Django and Python versions does django-avatar support?

The packaging metadata declares support for Django 4.2, 5.0, 5.2 and 6.0, and for Python 3.10 through 3.14, with the project marked as Production/Stable. Requirements are Pillow 10.0.1 or newer, django-appconf 1.0.5 or newer, and dnspython 2.3.0 or newer.

### How is the django-avatar version number determined during a build?

The version is read dynamically from the `__version__` attribute in the package source, using a regular expression that raises a RuntimeError when the version string is missing. The `avatar` package directory also carries the templates, locale files and default image as package data.

### What licence is django-avatar released under?

It is a BSD licensed package, with the licence declared in the packaging metadata and a LICENSE.txt file at the repository root. The project also ships a CHANGELOG.rst and a CONTRIBUTING.md, and is distributed through the Jazzband collection.

## Sources

- [Issues](https://github.com/jazzband/django-avatar/issues)
- [jazzband/django-avatar on GitHub](https://github.com/jazzband/django-avatar)
- [License: BSD-3-Clause](https://github.com/jazzband/django-avatar/blob/main/LICENSE)
- [README](https://github.com/jazzband/django-avatar/blob/main/README.md)
- [Releases](https://github.com/jazzband/django-avatar/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jazzband-django-avatar
