Skip to main content

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)

Source distribution for django-storage-thumbnails 0.1.1
File Size Uploaded
django_storage_thumbnails-0.1.1.tar.gz 12.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-storage-thumbnails 0.1.1
File Interpreter ABI Platform
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}

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page