Harvestable streams (Change Discovery)¶
To let aggregators, portals, and search indexes discover what changed since they last looked, djiiif can serve a IIIF Change Discovery 1.0 activity stream — the IIIF equivalent of a sitemap + RSS for your manifests. It’s a natural fit for Django: the stream is just a queryset ordered by a modified timestamp, paginated.
Configure the source¶
Point IIIF_ACTIVITY_SOURCE at a callable (or a dotted-path string) that yields
one entry per resource, in ascending end_time order:
IIIF_ACTIVITY_SOURCE = "myapp.iiif.activities"
def activities():
for photo in Photo.objects.exclude(image="").order_by("modified"):
yield {
"object_id": f"https://example.org/iiif/{photo.slug}/manifest",
"end_time": photo.modified, # an aware datetime
}
Each entry is a plain dict (or an Activity dataclass) with:
Field |
Default |
Meaning |
|---|---|---|
|
— |
the manifest (or collection) URL |
|
— |
the modification timestamp (aware |
|
|
the activity type ( |
|
|
|
The endpoints¶
With the URLconf mounted, the stream is served at:
/iiif/activity/collection— theOrderedCollectionentry point. Harvesters start here and follow thefirst/last/nextlinks./iiif/activity/page/<n>— theOrderedCollectionPages.
Page size comes from IIIF_ACTIVITY_PAGE_SIZE (default 100). Unset
IIIF_ACTIVITY_SOURCE ⇒ the activity URLs 404.
Performance & ordering¶
Returning a queryset (rather than a generator) lets pages slice lazily in the database — the recommended shape for large collections. A generator source is materialized per request, which is fine for thousands of images.
Important
Ordering is trusted, not re-sorted — your source must yield ascending by
end_time. (Re-sorting would defeat lazy queryset slicing.)
Conformance level¶
This is level 1 (Update/Create activities with timestamps — enough for
incremental harvest). Delete tracking (level 2) would require a persisted
tombstone log and is future work.