Changelog¶
The full, version-by-version history is maintained in the project’s
CHANGELOG.md and reproduced here.
All notable changes to djiiif are documented here.
The format is based on Keep a Changelog,
and this project aims to follow Semantic Versioning. Per
the backwards-compatibility policy in CLAUDE.md, any breaking change to the
public API is called out under a Breaking heading and triggers a MAJOR
version bump.
Entries older than 0.24 were backfilled from git history and may be approximate; dates are the commit dates of the corresponding version bump.
Unreleased¶
1.1.1 - 2026-07-08¶
Changed¶
The project moved from GitHub to GitLab. The canonical home is now https://gitlab.com/iiiris-org/djiiif; the GitHub repository is archived and read-only. Package metadata (
Homepage,Issues,Changelog) and the links in this file now point at GitLab. No code, import path, or public API changed —pip install djiiifis unaffected. Issues and merge requests should be filed on GitLab from now on.CI/CD moved from GitHub Actions to GitLab CI (
.gitlab-ci.yml). Releases are still published to PyPI by OIDC trusted publishing — now minted by GitLab rather than GitHub — and are still triggered by pushing avX.Ytag.
1.1.0 - 2026-07-06¶
Added¶
navPlace extension (geolocated manifests) via GeoDjango. A new opt-in
IIIF_NAVPLACEsetting — a callable (or dotted-path string) receiving the field file and returning a GeoJSON dict, a GEOS geometry, a(geometry, label)pair, orNone— adds a navPlaceFeatureCollectionto the generated manifest, switching its@contextto the required two-element array. Resolution lives in a new optionaldjiiif.geomodule that recognizes GEOS geometries by duck-typing (never importingdjango.contrib.gis), so GeoDjango is not a dependency — plain GeoJSON works without GDAL/GEOS.build_manifest/build_multi_manifestgain anav_placekeyword;IIIFObject.manifestandserve_manifestthread it through. A GEOS geometry whose SRID is set and not 4326 (WGS84) raisesImproperlyConfigured. Unset ⇒ output unchanged. Seebriefs/NAVPLACE-GEO.md.Declarative
info.jsonenrichment. A new opt-inIIIF_INFOsetting — adict, the typedInfoExtrasdataclass, or a callable returning either/None(normalized byresolve_info, mirroringIIIF_AUTH) — passes optional Image API properties through into the generatedinfo.json:sizes,tiles,max_width/max_height/max_area,rights,preferred_formats, andextra_qualities/extra_formats/extra_features. Keys are accepted in both snake_case and spec camelCase (conflicting duplicates raiseImproperlyConfigured); v3 emits all of them, v2 emits onlysizes/tilesand rejects v3-only keys. Threaded throughiiif.info_documentandserve_info_json(where a per-image callable receives the decoded storage name). djiiif only advertises what the operator declares — it does not probe the image server. Unset ⇒ output unchanged. Seebriefs/INFO-JSON-ENRICHMENT.md.Comprehensive documentation on Read the Docs. A multi-page Sphinx site (MyST Markdown + Furo theme) under
docs/, with getting-started guides, a feature guide per API, a full settings reference, and an autodoc API reference generated from the package’s docstrings. Configured for Read the Docs via.readthedocs.yaml; build the toolchain withpip install -e ".[docs]".Web Annotations & Content Search 2.0 (transcriptions, OCR, search-within). A shared frozen
Annotationdataclass (or plain dict;resolve_annotationnormalizes both) is the one hit/annotation type for two paired, opt-in, model-free features:IIIF_ANNOTATIONS_BACKEND— a callable(identifier, request) -> iterableof annotations — powersserve_annotation_pageat/iiif/<identifier>/annotations/1(a W3CAnnotationPageof transcriptions/OCR/commentary targeting the image’s canvas), andserve_manifestgains the canvasannotationsreference when it is set.IIIF_SEARCH_BACKEND— a callable(identifier, q, request) -> iterableof hits — powersserve_searchat/iiif/<identifier>/search?q=(a Content Search 2.0AnnotationPagewith embedded hits and acontextualizingmatch block), andserve_manifestadvertises aSearchService2. When only the annotations backend is set,serve_searchfalls back to a case-insensitive substring filter over it — “serve annotations, get search for free.” Empty/missingq⇒ empty page; unset backends ⇒ 404.
New public builders:
build_annotation,build_annotation_page,build_search_service,build_search_response. Advertisement is view-only (IIIFObject.manifestis unchanged). Seebriefs/WEB-ANNOTATIONS.mdandbriefs/CONTENT-SEARCH.md.IIIF Change Discovery API 1.0 activity stream (harvestable collections). A new opt-in
IIIF_ACTIVITY_SOURCEsetting — a callable (or dotted-path string) returning an iterable of activity entries (plain dicts or the new frozenActivitydataclass;object_id+end_time, optionaltype/object_type, normalized byresolve_activity) — powers two drop-in views mounted bydjiiif.urls:serve_activity_collectionat/iiif/activity/collection(theOrderedCollectionentry point) andserve_activity_pageat/iiif/activity/page/<n>(pagedOrderedCollectionPages in ascendingendTimeorder). Page size fromIIIF_ACTIVITY_PAGE_SIZE(default 100); pagination viadjango.core.paginator(querysets slice lazily, generators are materialized). Level-1 conformance (Create/Update);Delete/level-2 is future work. Module-level buildersbuild_activity/build_ordered_collection/build_collection_pageare public. UnsetIIIF_ACTIVITY_SOURCE⇒ the activity URLs 404. Seebriefs/CHANGE-DISCOVERY.md.Richer Presentation 3.0 manifests — descriptive properties.
build_manifestnow accepts optional keyword descriptorsmetadata,rights,required_statement,summary,thumbnail, andnav_date(each emitted only when given, so default output is byte-identical). A new opt-inIIIF_MANIFEST_DESCRIPTORSsetting — adictof those kwargs or a callable receiving the field file and returning one (orNone) — flows per-image descriptive metadata intoiiif.manifestwithout djiiif knowing your model. An unknown descriptor key raisesImproperlyConfigured.Multi-image manifests. New
build_multi_manifest(id_url, images, *, label, …)builder presents several images as one manifest with indexed canvases;build_manifestis now a thin single-image wrapper over it (output unchanged). A resolvedIIIF_AUTHblock applies to every image body.Collections. New
build_collection(id_url, items, *, label, **descriptors)builder emits a Presentation 3.0Collectionof manifest references, plus an optional drop-inserve_collectionview (mounted at/iiif/collectionbydjiiif.urls) driven by theIIIF_COLLECTION_SOURCEsetting (unset ⇒ 404;IIIF_COLLECTION_LABELsets the collection label). Seebriefs/PRESENTATION-ENRICHMENT.md. All three pieces are purely additive.IIIF Content State API 1.0 helpers for shareable viewer deep links. New module-level functions
encode_content_state/decode_content_state(spec §6 base64url encoding, incl. theencodeURIComponentpercent-encoding step) andbuild_content_state(targets a Manifest, a Canvas withpartOf, or a Canvas region viaxywh), plusiiif.content_state(xywh=..., encoded=...)which derives this image’s own manifest/canvas URIs (the same onesiiif.manifestemits) with no file I/O, and a{% iiif_content_state image %}/{% iiif_content_state image xywh='x,y,w,h' %}template tag. Drop the result into?iiif-content=to open an image — optionally zoomed to a region — in Mirador, Theseus, or any content-state-aware viewer. Purely additive; no new settings, views, or dependencies, and no change to existing output. Seebriefs/CONTENT-STATE.md.
1.0.0 - 2026-07-03¶
First stable release. Marks the public API — IIIFField / IIIFFieldFile /
IIIFObject, the .iiif attributes and generated documents, the IIIF_PROFILES
/ IIIF_AUTH config shapes, the typed helpers, the serving views, and the DRF
field — as stable and covered by the backwards-compatibility policy. No breaking
changes from 0.24; the bump to 1.0 signals maturity, not a break.
Added¶
Optional IIIF Authorization Flow 2.0 support (metadata-only). A new opt-in
IIIF_AUTHsetting (aProbeService, adict, or a callable returning either orNone, mirroringIIIF_PROFILES) embeds an Auth 2.0serviceblock in the generatedinfo_documentandmanifest, so viewers can authenticate against an image server that implements Auth 2.0 (e.g. iiiris). New typed helpersProbeService/AccessService/TokenService/LogoutServicebuild the nested block. djiiif only describes the services; the image server implements and enforces them. Auth 2.0 requiresIIIF_IMAGE_API_VERSION = 3(raisesImproperlyConfiguredon v2). Documents are unchanged whenIIIF_AUTHis unset. Seebriefs/AUTH-API.md.
0.24 - 2026-07-03¶
Added¶
iiif.info_document: returns the IIIFinfo.jsondocument itself (adict, orNonefor an empty field), assembled from the image’s ownwidth/height, so a Django view can serve a minimal, spec-validinfo.jsonwithout a separate image server. Distinct fromiiif.info, which is unchanged and still returns the URL of an externalinfo.json. It is the onlyiiifattribute that reads the file from storage.iiif.manifest: returns a minimal single-image IIIF Presentation API 3.0 Manifest (adict, orNonefor an empty field) wrapping the image on one canvas, ready for viewers like Mirador or OpenSeadragon. The embedded image service followsIIIF_IMAGE_API_VERSION.Profiledataclass: a typed, opt-in alternative to rawdictprofiles, with IIIF 3.0 defaults (size="max") andmirror/upscaleflags that fold into the!-rotation and^-size prefixes. Callable profiles may now return aProfileas well as adict.Drop-in serving views (
djiiif.views.serve_info_json/serve_manifest) and URLconf (djiiif.urls):path("iiif/", include("djiiif.urls"))serves bothinfo.jsonandmanifestfor stored images with the correct content type and CORS header — no separate image server required for the metadata documents.iiif.as_dict(): returns every profile URL keyed by profile name (with aninclude_meta=Trueoption to add theinfo/identifierURLs) — convenient for templates and JSON responses.Optional Django REST Framework support:
djiiif.serializers.IIIFSerializerFieldserializes anIIIFFieldto itsas_dict()mapping. Install via the newdrfextra (pip install djiiif[drf]); importingdjiiifnever imports DRF.System check
check_iiif_profiles(registered by the newDjiiifConfigapp config):manage.py checknow validatesIIIF_PROFILESat startup, flagging a non-dictsetting or adictprofile missing required keys.IIIF_IMAGE_API_VERSIONsetting (default3) selects the generatedinfo_document/manifestshapes —3for Image API 3.0 (id/type: ImageService3),2for the 2.x@id/ array-profileshape. An unknown value raisesImproperlyConfigured.IIIF_COMPLIANCE_LEVELsetting (default"level2") sets the compliance level advertised ininfo_documentand the manifest’s image service.
Changed¶
Identifier encoding now percent-encodes the full IIIF reserved set via
urllib.parse.quote, not just/→%2F. This changes emitted URLs only for field names that contain characters beyond/(spaces,?,#,%, etc.), which previously produced malformed URLs; plain names and names whose only special character is/are unaffected. Not a breaking change under the compatibility policy (it corrects previously-broken output), but noted here for anyone who stored such names.An empty
IIIF_PROFILESno longer raises when a field has a value;infoandidentifierreturn""(and the documentsNone), matching the empty-field behavior.Packaging modernized (PEP 621 + setuptools-scm). All metadata moved to
pyproject.toml; the package version is now derived from the git tag instead of a hand-bumpedsetup.py. Removedsetup.py,Pipfile,Pipfile.lock,MANIFEST.in,requirements.txt, andbuild.sh. No change to the installed package or its runtime dependency (Django). Contributors: install the dev toolchain withpip install -e ".[dev]"(Pipenv is no longer used).
0.23 - 2026-05-27¶
Added¶
iiif.identifier: a plainhost/identifierURL (no/region/size/rotation/quality.formatsuffix) for handing an image to viewers like OpenSeadragon.
Changed¶
Reconciled
masterwith the published 0.22 release.Added a test suite, CI across Python 3.10–3.13, and a trusted-publishing release workflow.
0.22 - 2023-09-05¶
Fixed¶
Corrected an error in URL assembly.
0.21 - 2023-09-05¶
Added¶
Plain, identifier-only URL for easy OpenSeadragon integration (later reconciled under 0.23).
0.20 - 2020-01-09¶
Fixed¶
Made
iiifattribute access safe for empty fields.
0.19 - 2020-01-09¶
Changed¶
Return an empty string for unpopulated (empty/
None-name) fields instead of building a broken URL.
0.15¶
Added¶
iiif.info: the IIIFinfo.jsonURL for a field.
0.1¶
Added¶
Initial implementation:
IIIFField/IIIFFieldFile/IIIFObject,IIIF_PROFILES-driven profile URLs (dict and callable shapes), and the{% iiif %}template tag.