django-storage-thumbnails
Automatically generate image thumbnails whenever a file is saved through a Django storage backend. Works with local filesystem storage and Amazon S3 (via django-storages).
Install
pip install django-storage-thumbnails # local filesystem
pip install "django-storage-thumbnails[s3]" # + S3 support
Configure
Add the sizes you want to your Django settings. Sizes accept either a
(width, height) tuple or a "WxH" string:
THUMBNAIL_SIZES = {
"small": (10, 10),
"med": (20, 20),
}
Then point your model/field (or STORAGES) at a thumbnail-aware storage.
Local filesystem
from thumbnail_storage import ThumbnailFileSystemStorage
storage = ThumbnailFileSystemStorage()
name = storage.save("photos/cat.jpg", my_image_file)
# also writes: thumbnails/small/photos/cat.jpg, thumbnails/med/photos/cat.jpg
Amazon S3
from thumbnail_storage import ThumbnailS3Storage
storage = ThumbnailS3Storage(bucket_name="my-bucket")
storage.save("photos/cat.jpg", my_image_file)
In a model
from django.db import models
from thumbnail_storage import ThumbnailFileSystemStorage
class Photo(models.Model):
image = models.ImageField(
upload_to="photos/",
storage=ThumbnailFileSystemStorage(),
)
Getting thumbnail links
Thumbnails are stored under a dedicated thumbnails/<size>/ prefix so they can
never collide with or overwrite a user's uploaded file. Deleting the original
also removes its thumbnails.
storage.thumbnail_url("photos/cat.jpg", "small")
# -> "/media/thumbnails/small/photos/cat.jpg"
storage.get_thumbnails("photos/cat.jpg")
# -> {"small": "/media/thumbnails/small/photos/cat.jpg",
# "med": "/media/thumbnails/med/photos/cat.jpg"}
Use it with any backend
ThumbnailMixin can be mixed into any Django Storage subclass:
from thumbnail_storage import ThumbnailMixin
from storages.backends.azure_storage import AzureStorage
class ThumbnailAzureStorage(ThumbnailMixin, AzureStorage):
pass
Async generation with Celery (S3)
For S3, you usually don't want to block the upload request while thumbnails are generated. Use the async storage: it saves the original immediately and hands the thumbnail work to a Celery worker, which re-reads the original from S3 — so only the file name travels over the broker, never the image bytes.
pip install "django-storage-thumbnails[s3,celery]"
Register the storage under a key in STORAGES and pass that key as alias
(the worker uses it to rebuild the same backend):
# settings.py
STORAGES = {
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"thumbnails": {
"BACKEND": "thumbnail_storage.AsyncThumbnailS3Storage",
"OPTIONS": {"alias": "thumbnails", "bucket_name": "my-bucket"},
},
}
from django.core.files.storage import storages
storage = storages["thumbnails"]
storage.save("photos/cat.jpg", img) # returns immediately; thumbnails follow
Make sure your Celery app autodiscovers thumbnail_storage.tasks, and that the
worker can reach the same S3 bucket. If Celery isn't installed, generation
quietly falls back to running inline.
Fetching while a thumbnail is still being made
Names are deterministic, so thumbnail_url() returns the URL right away — even
before the worker finishes. Use it with a fallback to the original so there's no
broken image in the brief window before the thumbnail exists:
<img src="{{ small_url }}" onerror="this.onerror=null; this.src='{{ original_url }}'">
Need a guaranteed "ready" signal or to generate sizes only on first view? The
get_thumbnail_name and store_thumbnail methods are overridable — subclass to
record a status row or to generate lazily, without forking the package.
How it works
On _save, if the saved file's extension is a known image type and
THUMBNAIL_SIZES is non-empty, a thumbnail is generated for each size with
Pillow (Image.thumbnail, which preserves aspect ratio — so a "10x10" size is
a bounding box, not an exact crop) and saved under thumbnails/<key>/<name> in
the same backend. Non-images and unreadable files are passed through untouched,
and any per-size failure is logged and skipped without affecting the upload or
the other sizes. Override thumbnail_prefix to change the namespace, or
get_thumbnail_name / store_thumbnail to change naming or write behavior.
Cleanup on delete() removes the thumbnails for the current THUMBNAIL_SIZES
/ thumbnail_prefix. If you remove a size key or change the prefix later,
thumbnails generated under the old config are not tracked and won't be cleaned
up automatically — prune them out of band if needed.
Running the tests
pip install -e ".[test]"
pytest
Metadata
Release files for django-storage-thumbnails 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_storage_thumbnails-0.1.1.tar.gz | 12.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_storage_thumbnails-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 23.7 kB
Release files / django_storage_thumbnails-0.1.1.tar.gz
| Download URL | django_storage_thumbnails-0.1.1.tar.gz |
|---|---|
| Size | 12.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d82d969fe363d32449f164413121d12006651e9f7c6d6c36923b1e228646605e
|
|
BLAKE2b-256 checksum How to use checksums |
483c8c8d55893a0c580c1f62c48085f95706b63c5cd8c3ef73766528757624ce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / django_storage_thumbnails-0.1.1-py3-none-any.whl
| Download URL | django_storage_thumbnails-0.1.1-py3-none-any.whl |
|---|---|
| Size | 11.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dba1320782f794bf2fca46a99688353725f54cb683b3eef85e6825c0b52e2205
|
|
BLAKE2b-256 checksum How to use checksums |
c06fa7bc9fc9fad9bbe0dd83d38251b2aaa386312d7749b23f77721e672b599e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|