Skip to main content

A lightweight Wagtail StreamField block + DRF serializer that exposes responsive WebP thumbnails with dimensions and focal points.

Project description

wagtail-thumbnails

PyPI CI Python License: MIT

A drop-in Wagtail StreamField block + DRF serializer that turns any uploaded image into a multi-variant WebP payload with dimensions and focal points — ready for headless frontends.

What you get

  • A ThumbnailBlock (StructBlock) with image + optional per-instance alt_text override
  • A ThumbnailSerializer that emits:
    • Source URL
    • Resolved alt text (block override → image contextual_alt_textdescriptiontitle)
    • Focal point (from Wagtail's built-in picker)
    • A configurable map of responsive variants (defaults: full_hd, large, medium, small) — each with url, width, height, format
  • Settings-driven variants — ship sensible defaults, override per project

You upload JPEG/PNG. Wagtail/Pillow generates and caches WebP renditions on first request. No user-side conversion required.

Install

pip install wagtail-thumbnails

Add to INSTALLED_APPS:

INSTALLED_APPS = [
    # ...
    "wagtail_thumbnails",
]

Quickstart

As a StreamField block

from wagtail.fields import StreamField
from wagtail_thumbnails.blocks import ThumbnailBlock

class ArticlePage(Page):
    body = StreamField([
        ("thumbnail", ThumbnailBlock()),
        # ... your other blocks
    ])

As a nested serializer

from rest_framework import serializers
from wagtail_thumbnails.serializers import ThumbnailSerializer

class ProductSerializer(serializers.ModelSerializer):
    hero_image = ThumbnailSerializer()

    class Meta:
        model = Product
        fields = ["hero_image"]

Example output

{
  "src": "https://cdn.example.com/media/images/hero.jpg",
  "alt_text": "A sunset over the bay",
  "focal_point": { "x": 400, "y": 300, "width": 100, "height": 100 },
  "variants": {
    "full_hd": { "url": "https://.../hero.fill-1920x1280.format-webp.webp", "width": 1920, "height": 1280, "format": "webp" },
    "large":   { "url": "https://.../hero.fill-800x533.format-webp.webp",   "width": 800,  "height": 533,  "format": "webp" },
    "medium":  { "url": "https://.../hero.fill-450x300.format-webp.webp",   "width": 450,  "height": 300,  "format": "webp" },
    "small":   { "url": "https://.../hero.fill-125x83.format-webp.webp",    "width": 125,  "height": 83,   "format": "webp" }
  }
}

Notes on the shape:

  • focal_point is null when no focal point is set. Its width / height are null when only a centre point was picked (no focal area).
  • alt_text is null when nothing is available. Wagtail titles are intentionally not used as alt text — they're typically filenames, which makes for a poor screen-reader experience.
  • The block emits alt_text: "" (empty string, not null) when the editor ticks the Decorative checkbox. Mark purely visual imagery this way so assistive tech can skip it.
  • Variants never upscale: if the source is narrower than a variant's target width, the variant is generated at the source's native dimensions.

Configuration

All settings live under a single dict. User-supplied keys override defaults (variant maps fully replace, not merge).

WAGTAIL_THUMBNAILS = {
    "VARIANTS": {
        "full_hd": {"width": 1920, "format": "webp", "quality": 80},
        "large":   {"width": 800,  "format": "webp", "quality": 80},
        "medium":  {"width": 450,  "format": "webp", "quality": 80},
        "small":   {"width": 125,  "format": "webp", "quality": 40},
    },
    "MIN_IMAGE_WIDTH": 25,
    "MIN_IMAGE_HEIGHT": 25,
}
Key Default Description
VARIANTS see above Mapping of variant name → {width, format, quality}. Variant names become keys in the API output's variants dict.
MIN_IMAGE_WIDTH 25 Minimum source-image width enforced by image_resolution_validator and ThumbnailBlock.clean().
MIN_IMAGE_HEIGHT 25 Minimum source-image height.

Supported format values: webp (default), jpeg, png. quality is honoured for webp and jpeg.

Misconfigurations (unknown keys, bad variant shapes, unsupported formats, out-of-range quality) raise ImproperlyConfigured at first access — not at request time.

Migrating from a plain ImageBlock

ThumbnailBlock is a StructBlock, so the on-disk JSON shape inside a StreamField differs from a bare ImageBlock. If you're swapping an ImageBlock (whose value is just an image ID) for ThumbnailBlock (whose value is {"image": <id>, "alt_text": "", "decorative": false}), existing rows need a data migration:

# yourapp/migrations/00XX_migrate_image_blocks_to_thumbnail.py
from django.db import migrations

OLD_TYPE = "image_block"
NEW_TYPE = "thumbnail"


def forwards(apps, schema_editor):
    YourPage = apps.get_model("yourapp", "YourPage")
    for page in YourPage.objects.all():
        changed = False
        for block in page.body.raw_data:
            if block["type"] == OLD_TYPE:
                block["type"] = NEW_TYPE
                block["value"] = {"image": block["value"], "alt_text": "", "decorative": False}
                changed = True
        if changed:
            page.save()


class Migration(migrations.Migration):
    dependencies = [("yourapp", "00XX_previous")]
    operations = [migrations.RunPython(forwards, migrations.RunPython.noop)]

For revisions (PageRevision), apply the same transform to revision.content. For images that have to keep the legacy shape (e.g. external API consumers), keep ImageBlock for those entries and use ThumbnailBlock only for new ones.

Compatibility

Python Django Wagtail
3.10 – 3.13 4.2 LTS, 5.1, 5.2 5.2, 6.x, 7.x

Development

git clone https://github.com/profilsoftware/wagtail-thumbnails
cd wagtail-thumbnails
uv sync --extra dev
uv run pytest
uv run ruff check .
uv run mypy src/

Contributing

See CONTRIBUTING.md. Bug reports and PRs welcome.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wagtail_thumbnails-0.1.0.tar.gz (10.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wagtail_thumbnails-0.1.0-py3-none-any.whl (11.6 kB view details)

Uploaded Python 3

File details

Details for the file wagtail_thumbnails-0.1.0.tar.gz.

File metadata

  • Download URL: wagtail_thumbnails-0.1.0.tar.gz
  • Upload date:
  • Size: 10.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for wagtail_thumbnails-0.1.0.tar.gz
Algorithm Hash digest
SHA256 fe23f59fd234f8ac6d1b3e119d3c62c524386573ae68eaba4b748c3593a59e20
MD5 2e8a99ea68c77e8dbe12126bd3319bfa
BLAKE2b-256 abeca0370c6b9c25ebcf7c2dd5e1b0a96a8e61b1281aaaab3e971d386a8014eb

See more details on using hashes here.

Provenance

The following attestation bundles were made for wagtail_thumbnails-0.1.0.tar.gz:

Publisher: publish.yml on profilsoftware/wagtail-thumbnails

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file wagtail_thumbnails-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for wagtail_thumbnails-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fc30ae09a6fb6cbebd3cd9300d6b42635e59a44f3c666718da4b186bb5f1068d
MD5 5a6c8d2b454cd9f07e81a3aea4f6c75e
BLAKE2b-256 b2699a189c03f8017b4bdac1871a7642821401a60434e93de2e0aa926ee240fe

See more details on using hashes here.

Provenance

The following attestation bundles were made for wagtail_thumbnails-0.1.0-py3-none-any.whl:

Publisher: publish.yml on profilsoftware/wagtail-thumbnails

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page