Skip to main content

djiiif

djiiif is a package designed to make integrating the IIIF Image API easier by extending Django's ImageField. By defining one or more named "profiles", your ImageFields expose IIIF-compatible URLs for each profile.

Why djiiif and not ImageKit

I love ImageKit, but I recently worked on a project where we already had IIIF handling image derivative generation and serving, and Django ImageKit just got in the way. I wanted to still register my source images with Django, but serve them through an IIIF server, and this is what I came up with. I have lots of ideas for improvements here, but the initial release is just a santized version of what I used on my most recent project.

Installation

pip install djiiif

Examples

First, let's setup a new field (or convert an existing ImageField):

models.py

from djiiif import IIIFField

original = IIIFField()

Second, configure the relevant settings.

settings.py

IIIF_HOST = 'http://server/'

IIIF_PROFILES = {
    'thumbnail':
        {'host': IIIF_HOST, 
        'region': 'full', 
        'size': '150,',
        'rotation': '0',
        'quality': 'default',
        'format': 'jpg'}
}

Finally, we can access profile(s) as attributes of the iiif attribute on an instance of original.

In Python:

print(instance.original.name)
> uploads/filename.jpg

print(instance.original.iiif.thumbnail)
> http://server/uploads%2Ffilename.jpg/full/150,/0/default.jpg

(The identifier segment is percent-encoded — slashes become %2F, and other reserved characters are encoded too — so the field name occupies a single IIIF path segment.)

In a Django template:

<img src="{{ instance.original.iiif.thumbnail }}">

As of version 0.15, we can also generate a IIIF info.json URL:

print(instance.original.iiif.info)
> http://server/uploads%2Ffilename.jpg/info.json

As of version 0.21, we also expose a plain identifier-only URL (host + identifier, with no /region/size/rotation/quality.format suffix) — handy for handing the image off to viewers like OpenSeadragon that take just the IIIF identifier:

print(instance.original.iiif.identifier)
> http://server/uploads%2Ffilename.jpg

As of version 0.24, iiif.info_document returns the IIIF info.json document itself (a dict), assembled from the image's own dimensions — as opposed to iiif.info, which returns the URL of an external info.json served by an image server. Both remain available; .info is unchanged. This lets a Django view serve a minimal, spec-valid info.json without a separate image server:

from django.http import JsonResponse

def image_info(request, pk):
    asset = MyModel.objects.get(pk=pk)
    return JsonResponse(asset.original.iiif.info_document)
print(instance.original.iiif.info_document)
> {
>     "@context": "http://iiif.io/api/image/3/context.json",
>     "id": "http://server/uploads/filename.jpg",
>     "type": "ImageService3",
>     "protocol": "http://iiif.io/api/image",
>     "profile": "level2",
>     "width": 4000,
>     "height": 3000,
> }

Because it needs real pixel dimensions, accessing .info_document reads the image from storage (via width/height); the URL attributes above never do. For an empty/unset field it returns None. Two optional settings control the output:

  • IIIF_IMAGE_API_VERSION3 (default) emits an Image API 3.0 document (id / type: ImageService3); 2 emits a 2.x document (@id / profile array).
  • IIIF_COMPLIANCE_LEVEL — the advertised compliance level, default "level2".

Typed profiles

As of version 0.24, instead of a raw dict you can use the Profile dataclass, which carries IIIF Image API 3.0-friendly defaults (size="max") and validates its fields:

from djiiif import Profile

IIIF_PROFILES = {
    "thumbnail": Profile(host=IIIF_HOST, size="150,"),
    "square": Profile(host=IIIF_HOST, region="square", size="256,256"),
}

Note the square example: IIIF 3.0's square region replaces the hand-rolled crop math shown below. Profile also handles the two 3.0 features that are easy to get wrong as hand-written strings:

  • mirror=True prefixes the rotation with ! (mirrored image).
  • upscale=True prefixes the size with ^ (permits upscaling beyond the extracted region).

Plain dict and callable profiles keep working exactly as before — Profile is opt-in, and a callable may return either a dict or a Profile.

callable-based profiles

You can also use a callable to dynamically generate a URL. The callable will receive the parent IIIFFieldFile (a subclass of ImageFieldFile) as its sole parameter, parent, and must return a dict with the following keys: host, region, size, rotation, quality, and format. Using a callable allows you to implement more complex logic in your profile, including the ability to access the original file's name, width, and height.

An example of a callable-based profile named square is below, used to generate a square-cropped image.

def squareProfile(original):
    width, height = original.width, original.height

    if width > height:
        x = int((width - height) / 2)
        y = 0
        w = height
        h = height
        region = '{},{},{},{}'.format(x,y,w,h)
    elif width < height:
        x = 0
        y = int((height - width) / 2)
        w = width
        h = width
        region = '{},{},{},{}'.format(x,y,w,h)
    else:
        region = 'full'

    spec = {'host': IIIF_HOST, 
        'region': region, 
        'size': '256,256',
        'rotation': '0',
        'quality': 'default',
        'format': 'jpg'}
    return spec
IIIF_PROFILES = {
    'thumbnail':
        {'host': IIIF_HOST, 
        'region': 'full', 
        'size': '150,',
        'rotation': '0',
        'quality': 'default',
        'format': 'jpg'},
    'preview':
        {'host': IIIF_HOST, 
        'region': 'full', 
        'size': '600,',
        'rotation': '0',
        'quality': 'default',
        'format': 'jpg'},
    'square': squareProfile
}

IIIF manifest (Presentation API)

As of version 0.24, iiif.manifest returns a minimal single-image IIIF Presentation API 3.0 Manifest (a dict) wrapping the image on one canvas — ready to hand to a viewer like Mirador or OpenSeadragon:

return JsonResponse(asset.original.iiif.manifest)

Like info_document, it reads the image's dimensions from storage and returns None for an empty field. The manifest is always Presentation 3.0; its embedded image service follows IIIF_IMAGE_API_VERSION (ImageService3 by default, ImageService2 when set to 2).

Serving info.json and manifests from Django

djiiif can also serve the info.json and manifest documents itself — no separate image server is required for the metadata. Include its URLconf:

from django.urls import include, path

urlpatterns = [
    path("iiif/", include("djiiif.urls")),
]

An image stored as uploads/photo.jpg is then served at /iiif/uploads%2Fphoto.jpg/info.json and /iiif/uploads%2Fphoto.jpg/manifest, each with the application/ld+json content type and the CORS header IIIF clients expect. Each document's id is derived from the request URL, so it always matches where it is served from. The views use the default storage backend and read image dimensions on demand.

Serving identifiers that contain encoded slashes requires your web server to allow encoded slashes in the path (e.g. Apache's AllowEncodedSlashes On); flat identifiers need no such configuration.

All profiles as a dict (as_dict)

iiif.as_dict() returns every profile URL keyed by profile name — handy for iterating in a template or building a JSON response:

print(instance.original.iiif.as_dict())
> {"thumbnail": "http://server/uploads%2Ffilename.jpg/full/150,/0/default.jpg"}

Pass include_meta=True to also include the info and identifier URLs. For an empty field every value is "".

Django REST Framework support

An optional serializer field is available for DRF projects. Install the extra:

pip install djiiif[drf]

Then serialize an IIIFField to its profile URLs (the as_dict() mapping):

from rest_framework import serializers
from djiiif.serializers import IIIFSerializerField

class AssetSerializer(serializers.ModelSerializer):
    original = IIIFSerializerField()          # or IIIFSerializerField(include_meta=True)

    class Meta:
        model = Asset
        fields = ["id", "original"]

The field is read-only and emits {"thumbnail": "…", …}. Importing djiiif itself never imports DRF, so the core package stays dependency-free.

Validating your configuration

With djiiif in INSTALLED_APPS, manage.py check validates IIIF_PROFILES at startup — flagging a non-dict setting, an unsupported profile value, or a dict profile missing required keys before it can produce a broken URL. Callable and Profile entries are accepted as-is (a callable's shape can only be verified when it runs).

IIIF Template Tag

An alternate way to access IIIF URLs for your IIIFField is via the iiif template tag.

First, add djiiif to your INSTALLED_APPS:

INSTALLED_APPS = [
    ...
    'djiiif'
]

Next, load our template tag library iiiftags in your template:

{% load iiiftags %}

Finally, use it in a template:

{% iiif asset.original 'thumbnail' %}

The first parameter (asset.original) is a reference to an IIIFField instance.

The second parameter ('thumbnail') is the name of one of your IIIF profiles.

This tag syntax is effectively the same as:

{{ asset.original.iiif.thumbnail }}

Download files

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

Source Distribution

djiiif-0.24.tar.gz (36.9 kB view details)

Uploaded Source

Built Distribution

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

djiiif-0.24-py3-none-any.whl (18.6 kB view details)

Uploaded Python 3

File details

Details for the file djiiif-0.24.tar.gz.

File metadata

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

File hashes

Hashes for djiiif-0.24.tar.gz
Algorithm Hash digest
SHA256 76adf405c302d1e2bff292ef1a217faf56459af5c743a2484625e10ca5db50dd
MD5 269c5a56acfa7b16401baa453d47842b
BLAKE2b-256 cd94e5186e52818853eb144bf01612da8f4a0aabb4abf9cb8d33ba5099c8cf5c

See more details on using hashes here.

Provenance

The following attestation bundles were made for djiiif-0.24.tar.gz:

Publisher: release.yml on rogerhoward/djiiif

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

File details

Details for the file djiiif-0.24-py3-none-any.whl.

File metadata

  • Download URL: djiiif-0.24-py3-none-any.whl
  • Upload date:
  • Size: 18.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for djiiif-0.24-py3-none-any.whl
Algorithm Hash digest
SHA256 688a22f47ab28fb96fe8d3e3eb6ee825a27faf77acb5da0fd87f55382a4448ac
MD5 4e2045889328f0d2b103bf6306f15a9b
BLAKE2b-256 138ad8e883716df42105180f9a839757a649757ab44eabb8026f2157c30b29dc

See more details on using hashes here.

Provenance

The following attestation bundles were made for djiiif-0.24-py3-none-any.whl:

Publisher: release.yml on rogerhoward/djiiif

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 Sentry Error logging StatusPage Status page