Skip to main content

WBR Media

Python Django Status Tests License

Portable media infrastructure for Django.

wbr_media provides a clean, consistent way to store, manage, render, and now port media assets between Django installations without requiring a full CMS.


Why WBR Media?

Django provides excellent support for uploading files, but it intentionally leaves higher-level media management to individual applications.

wbr_media fills that gap by providing:

  • structured media storage
  • automatic metadata extraction
  • consistent template rendering
  • safe file lifecycle management
  • complete import/export portability

without introducing the complexity of a full content management system.


Why not just use Django FileField?

You certainly can—but most projects eventually end up rebuilding the same infrastructure:

  • metadata extraction
  • image dimension detection
  • MIME type detection
  • cleanup of replaced files
  • cleanup of deleted files
  • rendering helpers
  • import/export tooling

wbr_media packages those capabilities into a small, reusable application.


✨ Features

  • Structured MediaAsset model
  • Automatic file metadata extraction
  • Image-specific metadata (dimensions, format, alpha, DPI)
  • Safe file replacement and deletion
  • Flexible template rendering
  • Media portability with checksum validation
  • Complete export/import workflow for media libraries

📷 Screenshots

Media Index

A lightweight media library view with previews and metadata.

Media index

Media Detail

Asset inspection with preview, metadata, and image properties.

Media detail


📸 Rendering Media

Load the template tags:

{% load wbr_media_tags %}

Render using the default presentation:

{% render_media asset %}

Or customize the presentation:

{% render_media asset display="bare" class_name="card-image" %}

Optional Arguments

Argument Description
size Named size (currently returns the original file)
display figure (default for images), bare, or link (default for non-images)
class_name CSS class applied to the rendered element

🚀 Installation

pip install -e .

Add the application:

INSTALLED_APPS = [
    ...
    "wbr_media",
]

Run migrations:

python manage.py migrate

Admin upload panel

WBR Media includes a reusable admin mixin for models that relate to a MediaAsset. Set media_field to the relationship field on your model. The mixin adds the complete two-column upload panel automatically:

# models.py
from django.db import models
from wbr_media.models import MediaAsset


class Article(models.Model):
    title = models.CharField(max_length=200)
    image = models.ForeignKey(
        MediaAsset,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
# admin.py
from django.contrib import admin

from wbr_media.admin_mixins import MediaAssetUploadMixin

from .models import Article


@admin.register(Article)
class ArticleAdmin(MediaAssetUploadMixin, admin.ModelAdmin):
    media_field = "image"

The panel accepts the same file types as MediaAsset, prepopulates metadata when editing an existing asset, and shows an image preview when a new image is selected.

For models with multiple media relationships, use media_fields instead. The mixin creates a separate namespaced panel for each relationship:

class PortfolioAdmin(MediaAssetUploadMixin, admin.ModelAdmin):
    media_fields = {
        "hero_media": "Hero image",
        "thumbnail_media": "Thumbnail image",
    }

Each panel has independent upload, selector, preview, and metadata fields. Blank uploads preserve the existing relationship and metadata. Existing single-field admins using media_field require no migration; they can adopt media_fields incrementally by moving the relationship into the mapping. If both settings are present, media_fields takes precedence.


⚙️ Configuration

The configured upload path is appended to Django's MEDIA_ROOT.

Available settings include the upload path and named image profiles:

WBR_MEDIA = {
    "UPLOAD_TO": "assets/originals/%Y/%m/",
    "IMAGE_PROFILES": {
        "thumbnail": {
            "width": 360,
            "height": 640,
            "fit": "crop",
            "position": "center",
        },
        "card": {
            "width": 640,
            "height": 360,
            "fit": "crop",
            "position": "center",
        },
    },
}

Image profiles

Image profiles define named renditions that can be generated from uploaded images. A profile must provide positive width and height values in output pixels.

Supported profile options are:

Option Description
width Target width in output pixels.
height Target height in output pixels.
fit crop, scale, or contain; defaults to crop.
position Crop position or focal point, such as center or top.
format Optional output format.
quality Optional encoder quality from 1 to 100.
upscale Whether smaller source images may be enlarged.
version Optional profile version for invalidating older renditions.

The fit modes have different results:

  • crop fills the target box and trims overflow, producing exactly the configured dimensions.
  • scale preserves the complete image within the configured bounds, so the actual output dimensions may be smaller than the requested dimensions.
  • contain preserves the complete image within an exact canvas and may leave empty space.

The requested profile dimensions and the actual generated dimensions should be tracked separately. DPI is preserved as image metadata but does not determine thumbnail dimensions for web display.

Profile configuration is checked by Django's system-check framework. Invalid dimensions and unsupported values produce errors. Exact duplicate profiles produce warnings because they may generate redundant files, but they are not configuration errors.

Configured renditions are generated when an image is uploaded. The original file remains the canonical source. Generated filenames use the actual output dimensions, for example:

sunset.jpg
sunset-640x360.jpg
sunset-360x240.jpg

When profiles have the same configured dimensions, their names are included to keep the files distinct:

sunset-card-360x640.jpg
sunset-thumbnail-360x640.jpg

Thumbnail generation is best effort. If an image cannot be processed, the original upload remains available and thumbnails can be retried with generate_thumbnails.


📦 Media Portability

One of the primary goals of wbr_media is complete portability.

A media library consists of two distinct pieces:

  • Media metadata stored in the database
  • Physical media files stored on disk

wbr_media exports and restores both as a single portable bundle.

Exporting a Media Library

Create a complete export:

python manage.py export_wbr_media --output ./backups/wbr_media_export.zip

The export process:

  1. Exports all MediaAsset and ImageMetadata records.
  2. Copies physical media assets.
  3. Generates a manifest describing every exported file.
  4. Calculates SHA-256 checksums for each asset.
  5. Validates the completed archive.
  6. Produces a portable bundle.

Bundle Layout

wbr_media_export.zip
├── data.json
└── media_export.zip
    ├── media_manifest.json
    └── files/

The media manifest records:

  • exported file path
  • existence
  • file size
  • SHA-256 checksum

These checksums are verified before any restore operation proceeds.


Importing a Media Library

Restore a previously exported bundle:

python manage.py import_wbr_media ./backups/wbr_media_export.zip

The import process:

  1. Opens the bundle.
  2. Validates the manifest.
  3. Verifies SHA-256 checksums.
  4. Restores physical media assets.
  5. Restores media database records.

If validation fails, restoration is aborted before modifying the destination installation.

Scoped Exports

Exports can be limited to a caller-selected collection of MediaAsset records. The host application owns the selection rules; WBR Media does not inspect site IDs, host models, or multisite relationships.

Pass either a QuerySet or an iterable of MediaAsset objects to the export APIs:

from wbr_media.models import MediaAsset
from wbr_media.transfer import MediaFileExporter, WBRMediaHandler

assets = MediaAsset.objects.filter(title__startswith="Campaign")

data = WBRMediaHandler().export_data(assets=assets)
MediaFileExporter(
    site=None,
    output_dir="./exports/media",
    assets=assets,
).run()

For a combined export, resolve the selection once and pass that same collection to both metadata and file export steps. This guarantees that data.json and the physical-file archive contain the same assets:

assets = list(MediaAsset.objects.filter(title__startswith="Campaign").order_by("file"))

data = WBRMediaHandler().export_data(assets=assets)
media_result = MediaFileExporter(
    site=None,
    output_dir="./exports/media",
    assets=assets,
).run()

When assets is omitted, the existing behavior is preserved and all media assets are exported.


Low-Level Commands

Generate configured thumbnails for all image assets:

python manage.py generate_thumbnails

Regenerate thumbnails for selected assets by ID:

python manage.py generate_thumbnails --asset-id 12 --asset-id 18

Portable exports contain the canonical originals and media metadata, not generated thumbnails. During import, originals are restored first and the destination site's configured profiles are regenerated in its storage. This keeps portability bundles smaller and supports the usual development-to- production workflow.

The application also exposes lower-level commands for working directly with physical media.

Export physical assets:

python manage.py export_media_files --output ./exports/media

Inspect a media archive:

python manage.py inspect_media_import ./exports/media_export.zip

Restore physical assets:

python manage.py restore_media_files ./exports/media_export.zip

These commands are primarily intended for development, debugging, and testing. In most cases, export_wbr_media and import_wbr_media should be preferred.


🧪 Development

A demo project is included.

cd demo
python manage.py runserver

Visit:

http://127.0.0.1:8000/media-demo/

Testing

Run the complete test suite:

python -m pytest -q -W error

Validation checks

Install the development and test tools from the repository root:

python -m pip install -e ".[dev,test]"

Run the same quality and package checks used by pull-request CI:

python -m ruff check .
python -m ruff format --check .
python -m pytest -q -W error
python -m build --outdir dist
python -m twine check --strict dist/*
python scripts/validate_distribution.py dist
python scripts/validate_installation.py dist

Run the dependency audit separately:

python -m pip install -e ".[security]"
python -m pip check
python -m pip uninstall --yes wbr-media
python -m pip_audit --local --strict --progress-spinner off

Run the repository secret scan with Docker:

docker run --rm \
  --volume "$PWD:/repo" \
  zricethezav/gitleaks:v8.24.2 \
  detect --source=/repo --no-banner --redact --exit-code 1

GitHub Actions runs these checks for pushes and pull requests. Dependabot checks Python and GitHub Actions dependencies weekly. See docs/releasing.md for release gates and publishing.


📦 What This Is

  • A lightweight media layer for Django
  • Consistent media metadata management
  • Flexible template rendering
  • Safe file lifecycle management
  • Portable media transfer between installations

🚫 What This Is Not

  • A CMS
  • A digital asset management system
  • A replacement for WordPress or Drupal
  • A complete media workflow solution

wbr_media is intentionally focused on providing a clean infrastructure layer that can be integrated into larger Django applications.


🛣️ Roadmap

Future improvements include:

  • Generated image renditions
  • Pluggable storage backends
  • Media usage tracking
  • Project-specific rendering extensions

The project intentionally avoids becoming a full CMS.


📄 License

MIT License.

Release files for wbr-media 0.3.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wbr-media 0.3.2
File Size Uploaded
wbr_media-0.3.2.tar.gz 36.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for wbr-media 0.3.2
File Interpreter ABI Platform
wbr_media-0.3.2-py3-none-any.whl Python 3 none any Details

Total release size: 77.5 kB

Release files / wbr_media-0.3.2.tar.gz

Download URL wbr_media-0.3.2.tar.gz
Size 36.2 kB
Tags Source
SHA-256 checksum
How to use checksums
361d428a40f1cdef24687fe50970cbc849c132c35e73b0de258717e4e42fe09c
BLAKE2b-256 checksum
How to use checksums
2a0fafc530e0fa5a9e393d583076bc07ba0995472a418749ce8f319cbae22937
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log

Release files / wbr_media-0.3.2-py3-none-any.whl

Download URL wbr_media-0.3.2-py3-none-any.whl
Size 41.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
834e5c892490876dfd34249c5cb5f6c5e612f443cd3c27daae906727b0e97f59
BLAKE2b-256 checksum
How to use checksums
2d817f0101374497d298e074eca523902c32e05650005151826eb81e49d31720
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.2 This release

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

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