From 65c1fc41041a4ff344301b51a0e2ef327fdb5dbc Mon Sep 17 00:00:00 2001 From: Ben Silverman Date: Tue, 20 Oct 2026 13:51:03 -0700 Subject: [PATCH 01/13] Link folios to IIIF canvases by canvas ID --- manuscript/admin.py | 45 +++- .../commands/link_folio_canvases.py | 215 +++++++++++++++++ .../migrations/0128_folio_iiif_canvas.py | 65 +++++ manuscript/models.py | 46 +++- manuscript/tests.py | 228 +++++++++++++++++- manuscript/utils.py | 102 ++++++-- static/js/tify-sync.js | 151 +++++++----- templates/stanzas.html | 2 +- 8 files changed, 756 insertions(+), 98 deletions(-) create mode 100644 manuscript/management/commands/link_folio_canvases.py create mode 100644 manuscript/migrations/0128_folio_iiif_canvas.py diff --git a/manuscript/admin.py b/manuscript/admin.py index ab11857..2f89d12 100644 --- a/manuscript/admin.py +++ b/manuscript/admin.py @@ -59,10 +59,28 @@ class StanzaInline(admin.StackedInline): ) +class FolioAdminForm(forms.ModelForm): + class Meta: + model = Folio + fields = "__all__" + + def clean(self): + cleaned_data = super().clean() + if "canvas_id" in self.changed_data: + # the recorded canvas details describe the previously linked + # canvas, so drop them when the canvas ID is changed by hand + self.instance.canvas_index = None + self.instance.canvas_label = None + self.instance.image_id = None + return cleaned_data + + class FolioInline(admin.StackedInline): model = Folio + form = FolioAdminForm classes = ("collapse",) extra = 1 + readonly_fields = ("canvas_index", "canvas_label", "image_id") class DetailInline(admin.StackedInline): @@ -283,20 +301,41 @@ def get_queryset(self, request): @admin.register(Folio) class FolioAdmin(ImportExportModelAdmin): resource_class = FolioResource + form = FolioAdminForm - list_display = ["manuscript", "folio_number", "line_range_display", "stanza_count"] - list_filter = ["manuscript"] + list_display = [ + "manuscript", + "folio_number", + "canvas_label", + "line_range_display", + "stanza_count", + ] + list_filter = ["manuscript", ("canvas_id", admin.EmptyFieldListFilter)] search_fields = [ "folio_number", "manuscript__siglum", "line_code_range_start", "line_code_range_end", + "canvas_label", ] - readonly_fields = ["stanza_list", "line_range_display", "stanza_count"] + readonly_fields = [ + "stanza_list", + "line_range_display", + "stanza_count", + "canvas_index", + "canvas_label", + "image_id", + ] fieldsets = ( ("Folio Information", {"fields": ("manuscript", "folio_number")}), + ( + "IIIF Canvas", + { + "fields": ("canvas_id", "canvas_label", "canvas_index", "image_id"), + }, + ), ( "Line Range", { diff --git a/manuscript/management/commands/link_folio_canvases.py b/manuscript/management/commands/link_folio_canvases.py new file mode 100644 index 0000000..2f8ad0f --- /dev/null +++ b/manuscript/management/commands/link_folio_canvases.py @@ -0,0 +1,215 @@ +import re +from collections import Counter, defaultdict + +from django.core.management.base import BaseCommand +from django.db import transaction + +from manuscript.models import SingleManuscript +from manuscript.utils import fetch_manifest, get_canvases, normalize_folio_label + +# folio numbers copied from the viewer's page menu: " :