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_VERSION—3(default) emits an Image API 3.0 document (id/type: ImageService3);2emits a 2.x document (@id/profilearray).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=Trueprefixes the rotation with!(mirrored image).upscale=Trueprefixes 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
76adf405c302d1e2bff292ef1a217faf56459af5c743a2484625e10ca5db50dd
|
|
| MD5 |
269c5a56acfa7b16401baa453d47842b
|
|
| BLAKE2b-256 |
cd94e5186e52818853eb144bf01612da8f4a0aabb4abf9cb8d33ba5099c8cf5c
|
Provenance
The following attestation bundles were made for djiiif-0.24.tar.gz:
Publisher:
release.yml on rogerhoward/djiiif
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
djiiif-0.24.tar.gz -
Subject digest:
76adf405c302d1e2bff292ef1a217faf56459af5c743a2484625e10ca5db50dd - Sigstore transparency entry: 2063568135
- Sigstore integration time:
-
Permalink:
rogerhoward/djiiif@73cb2f5ab9c9b2c085dd384bd5a8ef310dda3434 -
Branch / Tag:
refs/tags/v0.24 - Owner: https://github.com/rogerhoward
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@73cb2f5ab9c9b2c085dd384bd5a8ef310dda3434 -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
688a22f47ab28fb96fe8d3e3eb6ee825a27faf77acb5da0fd87f55382a4448ac
|
|
| MD5 |
4e2045889328f0d2b103bf6306f15a9b
|
|
| BLAKE2b-256 |
138ad8e883716df42105180f9a839757a649757ab44eabb8026f2157c30b29dc
|
Provenance
The following attestation bundles were made for djiiif-0.24-py3-none-any.whl:
Publisher:
release.yml on rogerhoward/djiiif
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
djiiif-0.24-py3-none-any.whl -
Subject digest:
688a22f47ab28fb96fe8d3e3eb6ee825a27faf77acb5da0fd87f55382a4448ac - Sigstore transparency entry: 2063568147
- Sigstore integration time:
-
Permalink:
rogerhoward/djiiif@73cb2f5ab9c9b2c085dd384bd5a8ef310dda3434 -
Branch / Tag:
refs/tags/v0.24 - Owner: https://github.com/rogerhoward
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@73cb2f5ab9c9b2c085dd384bd5a8ef310dda3434 -
Trigger Event:
release
-
Statement type: