Beispiel #1
0
class Image(ContextDictSerializer):
    iid = serpy.MethodField(label="@id")

    rtype = StaticField(label="@type", value="dctypes:Image")

    format = StaticField(value="image/jpeg")

    width = serpy.IntField(attr="width_i")

    height = serpy.IntField(attr="height_i")
    service = serpy.MethodField()

    def get_iid(self, obj: SolrResult) -> str:
        cfg = self.context.get('config')
        req = self.context.get('request')

        image_tmpl = cfg['templates']['image_id_tmpl']

        # Images have the suffix "_image" in Solr.
        identifier = re.sub(IMAGE_ID_SUB, "", obj.get("id"))
        return get_identifier(req, identifier, image_tmpl)  # type: ignore

    def get_service(self, obj: SolrResult) -> Dict:
        req = self.context.get('request')
        cfg = self.context.get('config')
        image_tmpl = cfg['templates']['image_id_tmpl']
        identifier = re.sub(IMAGE_ID_SUB, "", obj.get("id"))
        image_id = get_identifier(req, identifier, image_tmpl)  # type: ignore

        return {
            "@context": "http://iiif.io/api/image/2/context.json",
            "profile": "http://iiif.io/api/image/2/level1.json",
            "@id": image_id
        }
Beispiel #2
0
class Activity(ContextDictSerializer):
    ctx = serpy.MethodField(label="@context")
    id = serpy.MethodField()
    # We only publish Create events (for now...)
    type = StaticField(value="Create")
    end_time = serpy.MethodField(label="endTime")
    object = serpy.MethodField()
    actor = StaticField(value=IIIF_ASTREAMS_ACTOR)

    def get_ctx(
        self, obj: SolrResult
    ) -> Optional[List]:  # pylint: disable-msg=unused-argument
        direct: bool = self.context.get('direct_request', False)
        return IIIF_ASTREAMS_CONTEXT if direct else None

    def get_id(self, obj: SolrResult) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')
        activity_create_id_tmpl: str = cfg['templates'][
            'activitystream_create_id_tmpl']

        return get_identifier(req, obj.get('id'), activity_create_id_tmpl)

    def get_end_time(self, obj: SolrResult) -> str:
        return obj.get('accessioned_dt')

    def get_object(self, obj: SolrResult) -> Dict:
        req = self.context.get('request')
        cfg = self.context.get('config')

        manifest_tmpl: str = cfg['templates']['manifest_id_tmpl']
        mfid = get_identifier(req, obj.get('id'), manifest_tmpl)
        label: str = obj.get("full_shelfmark_s")

        return {"id": mfid, "type": "Manifest", "name": label}
class BaseAnnotation(ContextDictSerializer):
    ctx = serpy.MethodField(label="@context")
    aid = serpy.MethodField(label="@id")
    itype = StaticField(label="@type", value="oa:Annotation")
    motivation = StaticField(value="sc:painting")
    on = serpy.MethodField()
    resource = serpy.MethodField()

    def get_ctx(
        self, obj: SolrResult
    ) -> Optional[str]:  # pylint: disable-msg=unused-argument
        """
        If the resource is requested directly (instead of embedded in a manifest)
        return the context object; otherwise it will inherit the context of the parent.

        Note that the 'direct_request' context object is not passed down to children,
        so it will only appear in a top-level object.
        :param obj: Dictionary object to be serialized
        :return: List containing the appropriate context objects.
        """
        direct_request: bool = self.context.get('direct_request')
        return IIIF_V2_CONTEXT if direct_request else None

    def get_aid(self, obj: SolrResult) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')
        annotation_tmpl: str = cfg['templates']['annotation_id_tmpl']

        # The substitution here is only needed for image annotations, but won't affect other annotations
        annotation_id: str = re.sub(IMAGE_ID_SUB, "", obj["id"])

        return get_identifier(req, annotation_id, annotation_tmpl)

    @abstractmethod
    def get_resource(self, obj: SolrResult) -> Union[List[Dict], Dict]:
        """
        Get the body of this annotation - either an Image or a representation of the text for a text annotation
        :param obj:
        :return:
        """

    def get_on(self, obj: SolrResult) -> Union[str, List[Dict]]:
        """
        This method may be overridden in subclasses to e.g. reference a specific region of the canvas
        :param obj:
        :return: the uri for the canvas this annotation is attached to
        """
        req = self.context.get('request')
        cfg = self.context.get('config')
        canvas_tmpl = cfg['templates']['canvas_id_tmpl']

        identifier = re.sub(SURFACE_ID_SUB, "", obj['surface_id'])
        identifier_uri = get_identifier(req, identifier, canvas_tmpl)

        return identifier_uri
Beispiel #4
0
class Sequence(ContextDictSerializer):
    ctx = serpy.MethodField(
        label="@context"
    )
    sid = serpy.MethodField(
        label="@id"
    )
    stype = StaticField(
        label="@type",
        value="sc:Sequence"
    )
    label = StaticField(
        value="Default"
    )
    canvases = serpy.MethodField()

    def get_ctx(self, obj: SolrResult) -> Optional[str]:  # pylint: disable-msg=unused-argument
        direct_request: bool = self.context.get('direct_request')
        return IIIF_V2_CONTEXT if direct_request else None

    def get_sid(self, obj: Dict) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')
        obj_id = obj.get('id')
        sequence_tmpl = cfg['templates']['sequence_id_tmpl']

        return get_identifier(req, obj_id, sequence_tmpl)

    def get_canvases(self, obj: SolrResult) -> Optional[Dict]:
        req = self.context.get('request')
        cfg = self.context.get('config')
        obj_id = obj.get('id')

        # Check if the canvases have annotations. We don't actually
        # need to retrieve them, just get the number of hits.
        has_annotations_res = SolrConnection.search(
            "*:*",
            fq=["type:annotationpage", f"object_id:{obj_id}"],
            rows=0
        )
        has_annotations = has_annotations_res.hits > 0

        manager: SolrManager = SolrManager(SolrConnection)
        fq = ["type:surface", f"object_id:{obj_id}"]
        sort = "sort_i asc"
        fl = ["*,[child parentFilter=type:surface childFilter=type:image]"]
        rows: int = 100
        manager.search("*:*", fq=fq, fl=fl, sort=sort, rows=rows)

        if manager.hits == 0:
            return None

        return Canvas(manager.results, context={'request': req,
                                                'config': cfg,
                                                'has_annotations': has_annotations}, many=True).data
class TextAnnotation(BaseAnnotation):
    # TODO: consider adding the motivation value from the source data
    #  wait until we have mirador to test against
    motivation = StaticField(
        value="supplementing"
    )

    def get_body(self, obj: SolrResult) -> List[Dict]:
        # TODO: once we have an annotation viewer that supports v3,
        #  check if this should be a list or an oa:Choice or something else
        bodies = []

        if '_childDocuments_' in obj:

            for solr_body in obj['_childDocuments_']:
                chars = f'<p dir="{solr_body["direction_s"]}">{html.escape(solr_body["text_s"])}</p>'
                bodies.append({
                    "@type": "cnt:ContentAsText",
                    "chars": chars,
                    "format": "text/html",
                    "language": solr_body['language_s']
                })

        return bodies

    def get_target(self, obj: SolrResult):
        """
        Get the base canvas uri using the BaseAnnotation super class's implementation of get_target

        :param obj: A Solr result object
        :return: the uri for the canvas this annotation is attached to, with an xywh parameter to show what region the
                  annotation applies to
        """
        target_uri = super().get_target(obj)
        return f"{target_uri}#xywh={obj['ulx_i']},{obj['uly_i']},{obj['width_i']},{obj['height_i']}"
class ImageAnnotation(BaseAnnotation):
    motivation = StaticField(
        value="painting"
    )

    def get_body(self, obj: SolrResult) -> Dict:
        return Image(obj, context={"request": self.context.get('request'),
                                   "config": self.context.get("config")}).data
Beispiel #7
0
class AnnotationList(ContextDictSerializer):
    """
        Serializes a list of annotations
    """
    cid = serpy.MethodField(label="@id")

    ctx = StaticField(value=IIIF_V2_CONTEXT, label="@context")

    ctype = StaticField(label="@type", value="sc:AnnotationList")

    label = serpy.StrField(attr='label_s')

    resources = serpy.MethodField()

    def get_cid(self, obj: SolrResult) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')

        tmpl: str = cfg['templates']['annolist_id_tmpl']

        return get_identifier(req, obj['id'], tmpl)

    def get_resources(self, obj: SolrResult) -> List[Dict]:
        req = self.context.get('request')
        cfg = self.context.get('config')

        manager: SolrManager = SolrManager(SolrConnection)
        fq: List = ["type:annotation", f'annotationpage_id:"{obj["id"]}"']
        fl: List = [
            "*",
            "[child parentFilter=type:annotation childFilter=type:annotation_body]"
        ]
        manager.search("*:*", fq=fq, fl=fl, rows=100)

        if manager.hits == 0:
            return []

        return TextAnnotation(list(manager.results),
                              context={
                                  'request': req,
                                  'config': cfg
                              },
                              many=True).data
Beispiel #8
0
class StructureCanvasItem(ContextDictSerializer):
    cid = serpy.MethodField(
        label="id"
    )

    ctype = StaticField(
        label="type",
        value="Canvas"
    )

    def get_cid(self, obj: str) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')
        canvas_tmpl: str = cfg['templates']['canvas_id_tmpl']

        return get_identifier(req, re.sub(SURFACE_ID_SUB, "", obj), canvas_tmpl)
class CollectionCollection(ContextDictSerializer):
    """
        Serializes a collection object for use in a nested collection.
    """
    cid = serpy.MethodField(label="@id")
    ctype = StaticField(label="@type", value="sc:Collection")
    label = serpy.StrField(attr="name_s")
    description = serpy.StrField(attr="description_s")

    def get_cid(self, obj: SolrResult) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')

        tmpl: str = cfg['templates']['collection_id_tmpl']
        cid: str = obj.get('collection_id')

        return get_identifier(req, cid, tmpl)
Beispiel #10
0
class CollectionManifest(ContextDictSerializer):
    """
        A Manifest entry in the items list.
    """
    mid = serpy.MethodField(label="id")

    label = serpy.StrField(attr="full_shelfmark_s")
    type = StaticField(value="Manifest")
    thumbnail = serpy.MethodField()

    def get_mid(self, obj: SolrResult) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')

        iid: str = obj.get('id')
        tmpl: str = cfg['templates']['manifest_id_tmpl']

        return get_identifier(req, iid, tmpl)

    def get_thumbnail(self, obj: SolrResult) -> Optional[List]:
        image_uuid: str = obj.get('thumbnail_id')

        if not image_uuid:
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')

        image_tmpl: str = cfg['templates']['image_id_tmpl']

        image_ident: str = get_identifier(req, image_uuid, image_tmpl)
        thumbsize: str = cfg['common']['thumbsize']

        thumb_service: List = [{
            "@id": f"{image_ident}/full/{thumbsize},/0/default.jpg",
            "service": {
                "type": "ImageService2",
                "profile": "level1",
                "@id": image_ident
            }
        }]

        return thumb_service
Beispiel #11
0
class IIIFRoot(ContextDictSerializer):
    """
    A small serializer that represents the root response for iiif.bodleian.ox.ac.uk. Points to
    browseable endpoints for our IIIF Service.
    """
    ctx = StaticField(label="@context", value=ROOT_CONTEXT)
    rid = serpy.MethodField(label="id")
    items = serpy.MethodField()

    def get_rid(self, obj):  # pylint: disable-msg=unused-argument
        req = self.context.get('request')
        fwd_scheme_header = req.headers.get('X-Forwarded-Proto')
        fwd_host_header = req.headers.get('X-Forwarded-Host')
        scheme = fwd_scheme_header if fwd_scheme_header else req.scheme
        host = fwd_host_header if fwd_host_header else req.host

        return f"{scheme}://{host}/info.json"

    def get_items(self,
                  obj) -> List[Dict]:  # pylint: disable-msg=unused-argument
        req = self.context.get('request')
        cfg = self.context.get('config')

        coll_id_tmpl: str = cfg['templates']['collection_id_tmpl']
        coll_top_id: str = get_identifier(req, "top", coll_id_tmpl)
        coll_all_id: str = get_identifier(req, "all", coll_id_tmpl)

        as_id_tmpl: str = cfg['templates']['activitystream_id_tmpl']
        as_top_id: str = get_identifier(req, "all-changes", as_id_tmpl)

        return [{
            "id": coll_top_id,
            "type": "Collection",
            "name": "Top-level collection"
        }, {
            "id": coll_all_id,
            "type": "Collection",
            "name": "All manifests"
        }, {
            "id": as_top_id,
            "type": "OrderedCollection",
            "name": "ActivityStream"
        }]
Beispiel #12
0
class CollectionCollection(ContextDictSerializer):
    """
        A Collection entry in the items list.
    """
    cid = serpy.MethodField(label="id")
    type = StaticField(value="Collection")
    label = serpy.MethodField()

    def get_cid(self, obj: Dict) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')

        iid: str = obj.get('collection_id')
        tmpl: str = cfg['templates']['collection_id_tmpl']

        return get_identifier(req, iid, tmpl)

    def get_label(self, obj: Dict) -> Dict:
        name: str = obj.get('name_s')
        return {'en': [f"{name}"]}
class CollectionManifest(ContextDictSerializer):
    mid = serpy.MethodField(label="@id")

    ctype = StaticField(label="@type", value="sc:Manifest")

    label = serpy.StrField(attr="full_shelfmark_s")
    thumbnail = serpy.MethodField()

    def get_mid(self, obj: SolrResult) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')
        manifest_id: str = obj.get('id')
        manifest_id_tmpl: str = cfg['templates']['manifest_id_tmpl']

        return get_identifier(req, manifest_id, manifest_id_tmpl)

    def get_thumbnail(self, obj: SolrResult) -> Optional[Dict]:
        image_uuid: str = obj.get('thumbnail_id')

        if not image_uuid:
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')

        image_tmpl: str = cfg['templates']['image_id_tmpl']

        image_ident: str = get_identifier(req, image_uuid, image_tmpl)
        thumbsize: str = cfg['common']['thumbsize']

        thumb_service: Dict = {
            "@id": f"{image_ident}/full/{thumbsize},/0/default.jpg",
            "service": {
                "@context": "http://iiif.io/api/image/2/context.json",
                "profile": "http://iiif.io/api/image/2/level1.json",
                "@id": image_ident
            }
        }

        return thumb_service
Beispiel #14
0
class Manifest(ContextDictSerializer):
    """
    The main class for constructing a IIIF Manifest. Implemented as a serpy
    serializer. This docstring will serve as the documentation for this class, as well as the
    other serializer classes.

    The ContextDictSerializer superclass provides a 'context' object on this class. This
    can be used to pass values down through the various child classes, provided they are also given the same
    context. This lets us pass along things like the original request object, and the
    server configuration object, without needing to resolve it externally.

    For classes that implement de-referenceable objects, they provide a method field that will return
    None if that object is being embedded in a manifest, or the IIIF v3 context array if it's being
    de-referenced directly.

    When the values of this class are serialized, any fields that have a value of None will not
    be emitted in the output. Refer to the `to_value` method on the superclass for the implementation
    and docstring for this function.
    """
    ctx = StaticField(value=IIIF_V3_CONTEXT, label="@context")

    mid = serpy.MethodField(label="id")

    mtype = StaticField(value="Manifest", label="type")

    label = serpy.MethodField()
    summary = serpy.MethodField()

    metadata = serpy.MethodField()
    homepage = serpy.MethodField()
    provider = serpy.MethodField()

    nav_date = serpy.MethodField(label='navDate')
    logo = serpy.MethodField()
    thumbnail = serpy.MethodField()
    required_statement = serpy.MethodField(label="requiredStatement")
    part_of = serpy.MethodField(label="partOf")
    behaviour = serpy.MethodField(label="behavior")
    items = serpy.MethodField()
    structures = serpy.MethodField()

    viewing_direction = serpy.StrField(attr="viewing_direction_s",
                                       label="viewingDirection",
                                       required=False)

    def get_mid(self, obj: SolrResult) -> str:
        req = self.context.get('request')
        conf = self.context.get('config')
        manifest_tmpl: str = conf['templates']['manifest_id_tmpl']

        return get_identifier(req, obj.get('id'), manifest_tmpl)

    def get_label(self, obj: SolrResult) -> Dict:
        return {"en": [f"{obj.get('full_shelfmark_s')}"]}

    def get_summary(self, obj: SolrResult) -> Dict:
        return {"en": [f"{obj.get('summary_s')}"]}

    def get_required_statement(self, obj: SolrResult) -> Dict:
        return {
            "label": {
                "en": ["Terms of Use"]
            },
            "value": {
                "en": [obj.get("use_terms_sni", None)]
            }
        }

    def get_part_of(self, obj: SolrResult) -> Optional[List]:
        colls: List[str] = obj.get('all_collections_link_smni')

        if not colls:
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')
        tmpl: str = cfg['templates']['collection_id_tmpl']

        ret: List[Dict] = []

        for collection in colls:
            cid, label = collection.split("|")
            ret.append({
                "id": get_identifier(req, cid, tmpl),
                "type": "Collection",
                "label": {
                    "en": [label]
                }
            })

        return ret

    def get_homepage(self, obj: SolrResult) -> List:
        req = self.context.get('request')
        cfg = self.context.get('config')

        tmpl: str = cfg['templates']['digital_bodleian_permalink_tmpl']
        uuid: str = obj.get("id")

        conn: SolrManager = SolrManager(SolrConnection)
        fq: List = ['type:link', f"object_id:{uuid}"]

        conn.search("*:*", fq=fq)

        links: List = [{
            'id': get_identifier(req, uuid, tmpl),
            'type': "Text",
            "label": {
                "en": ["View on Digital Bodleian"]
            },
            "format": "text/html",
            "language": ["en"]
        }]

        if conn.hits > 0:
            for r in conn.results:
                links.append({
                    'id': r.get('target_s'),
                    'type': "Text",
                    "label": {
                        "en": [r.get('label_s')]
                    },
                    "format": "text/html",
                    "language": ["en"]
                })

        return links

    def get_logo(self, obj: SolrResult) -> Optional[List]:
        logo_uuid: str = obj.get("logo_id")

        if not logo_uuid:
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')
        image_tmpl: str = cfg['templates']['image_id_tmpl']

        logo_ident: str = get_identifier(req, logo_uuid, image_tmpl)
        thumbsize: str = cfg['common']['thumbsize']

        logo_service: List = [{
            "id": f"{logo_ident}/full/{thumbsize},/0/default.jpg",
            "type": "Image",
            "service": {
                "type": "ImageService2",
                "profile": "level1",
                "id": logo_ident
            }
        }]

        return logo_service

    def get_provider(self, obj: SolrResult) -> Optional[List]:
        """
            If a URI for the organization is not provided, we will not show any
            information about the organization.

        :param obj: A Solr record.
        :return: A 'provider' block.
        """
        uri: Optional[str] = obj.get("institution_uri_s", None)

        if not uri:
            return None

        org_name: Optional[str] = obj.get("holding_institution_s", None)
        org_homepage: Optional[str] = obj.get("institution_homepage_sni", None)

        provider_block: List = [{
            "id": uri,
            "type": "Agent",
            "label": {
                "en": [org_name]
            },
            "homepage": {
                "id": org_homepage,
                "type": "Text",
                "label": {
                    "en": [org_name]
                },
                "format": "text/html"
            },
        }]

        return provider_block

    def get_thumbnail(self, obj: SolrResult) -> Optional[List]:
        image_uuid: str = obj.get('thumbnail_id')

        if not image_uuid:
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')

        image_tmpl: str = cfg['templates']['image_id_tmpl']

        image_ident: str = get_identifier(req, image_uuid, image_tmpl)
        thumbsize: str = cfg['common']['thumbsize']

        thumb_service: List = [{
            "id": f"{image_ident}/full/{thumbsize},/0/default.jpg",
            "service": {
                "type": "ImageService2",
                "profile": "level1",
                "id": image_ident
            }
        }]

        return thumb_service

    def get_behaviour(self, obj: SolrResult) -> List:
        vtype = obj.get('viewing_type_s')

        if vtype and vtype in ["map", "sheet", "binding", "photo"]:
            return ["individuals"]

        return ["paged"]

    def get_metadata(self, obj: SolrResult) -> Optional[List[Dict]]:
        # description_sm is already included in the summary
        metadata: List = get_links(obj, 3)
        metadata += v3_metadata_block(obj)

        return metadata

    def get_items(self, obj: SolrResult) -> Optional[List]:
        req = self.context.get('request')
        cfg = self.context.get('config')
        obj_id: str = obj.get('id')

        # Check if the canvases have annotations. We don't actually
        # need to retrieve them, just get the number of hits.
        has_annotations_res = SolrConnection.search(
            "*:*", fq=["type:annotationpage", f"object_id:{obj_id}"], rows=0)
        has_annotations = has_annotations_res.hits > 0

        manager: SolrManager = SolrManager(SolrConnection)
        fq: List = ["type:surface", f"object_id:{obj_id}"]
        sort: str = "sort_i asc"
        fl: List = [
            "*,[child parentFilter=type:surface childFilter=type:image]"
        ]
        rows: int = 100
        manager.search("*:*", fq=fq, fl=fl, sort=sort, rows=rows)

        if manager.hits == 0:
            return None

        return Canvas(manager.results,
                      context={
                          "request": req,
                          "config": cfg,
                          "has_annotations": has_annotations
                      },
                      many=True).data

    def get_structures(self, obj: SolrResult) -> Optional[List[Dict]]:
        return create_v3_structures(self.context.get("request"), obj.get("id"),
                                    self.context.get("config"))

    def get_nav_date(self, obj: SolrResult) -> Optional[str]:
        year: Optional[int] = obj.get('start_date_i') or obj.get('end_date_i')
        if year is None:
            return None
        return f"{year}-01-01T00:00:00Z"
Beispiel #15
0
class Collection(ContextDictSerializer):
    """
        A top-level IIIF Collection object.
    """
    ctx = StaticField(label="@context", value=IIIF_V3_CONTEXT)
    cid = serpy.MethodField(label="id")

    ctype = StaticField(label="type", value="Collection")

    label = serpy.MethodField()
    summary = serpy.MethodField()
    items = serpy.MethodField()

    def get_cid(self, obj: SolrResult) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')
        tmpl: str = cfg['templates']['collection_id_tmpl']
        cid: str = obj.get('collection_id')

        return get_identifier(req, cid, tmpl)

    def get_label(self, obj: SolrResult) -> Dict:
        return {"en": [f"{obj.get('name_s')}"]}

    def get_summary(self, obj: SolrResult) -> Dict:
        return {"en": [f"{obj.get('description_s')}"]}

    def get_items(self, obj: SolrResult) -> List:
        """
        Gets a list of the child items. In v3 manifests this will either be Manifest objects or Collection objects.

        !!! NB: A collection will ONLY have manifests or Collections. The Solr index does not support mixed
            manifest and sub-collections !!!

        Two Solr queries are necessary to determine whether what is being requested is a parent collection (in
        which case the parent_collection_id field will match the requested path) OR a set of Manifests (in
        which case the first query will return 0 results, and then we re-query for the list of objects.)

        :param obj: A dict representing the Solr record for that collection.
        :return: A list of objects for the `items` array in the Collection.
        """
        req = self.context.get('request')
        cfg = self.context.get('config')

        manager: SolrManager = SolrManager(SolrConnection)
        coll_id: str = obj.get('collection_id')

        # first try to retrieve sub-collections (collections for which this is a parent)
        fq = ["type:collection", f"parent_collection_id:{coll_id}"]
        fl = ["id", "name_s", "description_s", "type", "collection_id"]
        rows: int = 100

        manager.search("*:*", fq=fq, fl=fl, rows=rows, sort="name_s asc")

        if manager.hits > 0:
            # bingo! it was a request for a sub-collection.
            return CollectionCollection(manager.results,
                                        many=True,
                                        context={
                                            'request': req,
                                            'config': cfg
                                        }).data

        # oh well; retrieve the manifest objects.
        fq = ["type:object", f"all_collections_id_sm:{coll_id}"]
        fl = ["id", "title_s", "full_shelfmark_s", "type"]
        sort = "institution_label_s asc, shelfmark_sort_ans asc"

        manager.search("*:*", fq=fq, fl=fl, rows=rows, sort=sort)

        return CollectionManifest(manager.results,
                                  many=True,
                                  context={
                                      'request': req,
                                      'config': cfg
                                  }).data
Beispiel #16
0
class Manifest(ContextDictSerializer):
    ctx = StaticField(value=IIIF_V2_CONTEXT, label="@context")
    # Manifest ID
    mid = serpy.MethodField(label="@id")

    mtype = StaticField(value="sc:Manifest", label="@type")

    label = serpy.StrField(attr="full_shelfmark_s")
    description = serpy.StrField(attr="summary_s")
    metadata = serpy.MethodField()
    nav_date = serpy.MethodField(label='navDate')
    rendering = serpy.MethodField()
    attribution = serpy.MethodField()
    logo = serpy.MethodField()
    thumbnail = serpy.MethodField()

    viewing_hint = serpy.MethodField(label="viewingHint")
    viewing_direction = serpy.StrField(attr="viewing_direction_s",
                                       label="viewingDirection",
                                       required=False)
    sequences = serpy.MethodField()
    structures = serpy.MethodField()

    def get_mid(self, obj: SolrResult) -> str:
        req = self.context.get('request')
        conf = self.context.get('config')
        manifest_tmpl: str = conf['templates']['manifest_id_tmpl']

        return get_identifier(req, obj.get('id'), manifest_tmpl)

    def get_metadata(self, obj: SolrResult) -> Optional[List[Dict]]:
        # Talbot manifests are a bit different, so exclude their metadata
        if 'talbot' in obj.get('all_collections_id_sm', []):  # type: ignore
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')
        tmpl: str = cfg['templates']['digital_bodleian_permalink_tmpl']
        ident: str = get_identifier(req, obj.get('id'), tmpl)
        val: str = '<span><a href="{0}">View on Digital Bodleian</a></span>'.format(
            ident)

        metadata: List = [{"label": "Homepage", "value": val}]

        metadata += get_links(obj, 2)
        metadata += v2_metadata_block(obj)

        return metadata

    def get_rendering(self, obj: SolrResult) -> Optional[Dict]:
        # Talbot manifests are a bit different, so exclude their metadata
        if 'talbot' in obj.get('all_collections_id_sm', []):  # type: ignore
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')
        tmpl = cfg['templates']['digital_bodleian_permalink_tmpl']

        ident: str = get_identifier(req, obj.get('id'), tmpl)

        return {
            "@id": ident,
            "label": "View on Digital Bodleian",
            "format": "text/html"
        }

    def get_viewing_hint(self, obj: SolrResult) -> str:
        """
        The viewing types are controlled in the silo indexer; returns 'paged' by default
        :param obj:
        :return:
        """
        vtype: str = obj.get('viewing_type_s')

        if vtype and vtype in ["map", "sheet", "binding", "photo"]:
            return "individuals"

        return "paged"

    def get_attribution(self, obj: SolrResult) -> str:
        rights: str = obj.get("access_rights_sni", "")
        terms: str = obj.get("use_terms_sni", "")

        # If there is both rights and terms, will separate them with semicolon.
        # If there is only one, will join the empty string and then strip it off for display.
        attrb: str = ". ".join([rights, terms]).strip(". ")
        # The previous line would strip the final period. Put it back in.
        return f"{attrb}."

    def get_logo(self, obj: SolrResult) -> Optional[Dict]:
        logo_uuid: str = obj.get("logo_id")

        if not logo_uuid:
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')
        image_tmpl: str = cfg['templates']['image_id_tmpl']
        thumbsize: str = cfg['common']['thumbsize']

        logo_ident: str = get_identifier(req, logo_uuid, image_tmpl)

        logo_service: Dict = {
            "@id": f"{logo_ident}/full/{thumbsize},/0/default.jpg",
            "service": {
                "@context": "http://iiif.io/api/image/2/context.json",
                "profile": "http://iiif.io/api/image/2/level1.json",
                "@id": logo_ident
            }
        }

        return logo_service

    def get_thumbnail(self, obj: SolrResult) -> Optional[Dict]:
        image_uuid: str = obj.get('thumbnail_id')

        if not image_uuid:
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')

        image_tmpl: str = cfg['templates']['image_id_tmpl']

        image_ident: str = get_identifier(req, image_uuid, image_tmpl)
        thumbsize: str = cfg['common']['thumbsize']

        thumb_service: Dict = {
            "@id": f"{image_ident}/full/{thumbsize},/0/default.jpg",
            "service": {
                "@context": "http://iiif.io/api/image/2/context.json",
                "profile": "http://iiif.io/api/image/2/level1.json",
                "@id": image_ident
            }
        }

        return thumb_service

    def get_sequences(self, obj: SolrResult) -> List[Optional[Sequence]]:
        return [
            Sequence(obj,
                     context={
                         'request': self.context.get('request'),
                         'config': self.context.get('config')
                     }).data
        ]

    def get_structures(self, obj: SolrResult) -> Optional[List[Dict]]:
        return create_v2_structures(self.context.get('request'), obj.get('id'),
                                    self.context.get('config'))

    def get_nav_date(self, obj: SolrResult) -> Optional[str]:
        year: Optional[int] = obj.get('start_date_i') or obj.get('end_date_i')

        if year is None:
            return None

        return f"{year}-01-01T00:00:00Z"
Beispiel #17
0
class StructureRangeItem(ContextDictSerializer):
    ctx = serpy.MethodField(
        label="@context"
    )

    sid = serpy.MethodField(
        label="id"
    )

    stype = StaticField(
        label="type",
        value="Range"
    )

    part_of = serpy.MethodField(
        label="partOf"
    )

    label = serpy.MethodField()
    metadata = serpy.MethodField()
    items = serpy.MethodField()

    def get_ctx(self, obj: SolrResult) -> Optional[List]:  # pylint: disable-msg=unused-argument
        """
        If the resource is requested directly (instead of embedded in a manifest)
        return the context object; otherwise it will inherit the context of the parent.

        Note that the 'direct_request' context object is not passed down to children,
        so it will only appear in a top-level object.
        :param obj: Dictionary object to be serialized
        :return: List containing the appropriate context objects.
        """
        direct_request: bool = self.context.get('direct_request')
        return IIIF_V3_CONTEXT if direct_request else None

    def get_part_of(self, obj: SolrResult) -> Optional[List]:
        """When requested directly, give a within parameter to point back to
           the parent manuscript.
        """
        direct_request: bool = self.context.get('direct_request')

        if not direct_request:
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')
        obj_id: str = obj.get('object_id')

        manifest_tmpl: str = cfg['templates']['manifest_id_tmpl']
        wid: str = get_identifier(req, obj_id, manifest_tmpl)

        return [{
            "id": wid,
            "type": "Manifest"
        }]

    def get_sid(self, obj: SolrResult) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')
        range_tmpl: str = cfg['templates']['range_id_tmpl']

        identifier: str = obj.get("object_id")
        range_id: str = obj.get("work_id")

        return get_identifier(req, identifier, range_tmpl, range_id=range_id)

    def get_label(self, obj: SolrResult) -> Dict:
        return {"en": [f"{obj.get('work_title_s')}"]}

    def get_metadata(self, obj: SolrResult) -> Optional[List[Dict]]:
        return v3_metadata_block(obj, WORKS_METADATA_FIELD_CONFIG)

    def get_items(self, obj: SolrResult) -> List:
        # If the object has a list of child objects,
        # it is a range; if not, it is a canvas.
        req = self.context.get('request')
        cfg = self.context.get('config')
        direct = self.context.get('direct_request')

        if obj.get('_children'):
            return StructureRangeItem(obj['_children'], context={'request': req,
                                                                 'config': cfg,
                                                                 'direct_request': direct}, many=True).data

        return StructureCanvasItem(obj['surfaces_sm'], context={'request': req,
                                                                'config': cfg,
                                                                'direct_request': direct}, many=True).data
class Collection(ContextDictSerializer):
    ctx = StaticField(label="@context", value=IIIF_V2_CONTEXT)

    cid = serpy.MethodField(label="@id")

    ctype = StaticField(label="@type", value="sc:Collection")

    label = serpy.StrField(attr="name_s")
    description = serpy.StrField(attr="description_s")
    manifests = serpy.MethodField()
    collections = serpy.MethodField()

    def get_cid(self, obj: SolrResult) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')
        tmpl: str = cfg['templates']['collection_id_tmpl']
        cid: str = obj.get('collection_id')

        return get_identifier(req, cid, tmpl)

    def get_collections(self, obj: SolrResult) -> Optional[List]:
        coll_id: str = obj.get('collection_id')
        req = self.context.get('request')
        cfg = self.context.get('config')

        manager: SolrManager = SolrManager(SolrConnection)
        fq: List = ["type:collection", f"parent_collection_id:{coll_id}"]
        fl: List = [
            'id', 'name_s', 'description_s', 'collection_id',
            'parent_collection_id'
        ]
        sort: str = "name_s asc"
        rows: int = 100

        manager.search("*:*", fq=fq, fl=fl, sort=sort, rows=rows)

        if manager.hits == 0:
            return None

        return CollectionCollection(manager.results,
                                    many=True,
                                    context={
                                        'request': req,
                                        'config': cfg
                                    }).data

    def get_manifests(self, obj: SolrResult) -> Optional[List]:
        coll_id: str = obj.get('collection_id')

        req = self.context.get('request')
        cfg = self.context.get('config')

        manager: SolrManager = SolrManager(SolrConnection)

        # The 'All' collection is for every object in the collection, so we
        # don't need to restrict it by collection.
        if coll_id == 'all':
            fq = ["type:object"]
        else:
            fq = ["type:object", f"all_collections_id_sm:{coll_id}"]

        sort: str = "institution_label_s asc, shelfmark_sort_ans asc"
        rows: int = 100
        fl = ["id", "title_s", "full_shelfmark_s", "thumbnail_id"]

        manager.search("*:*", fq=fq, sort=sort, fl=fl, rows=rows)

        if manager.hits == 0:
            return None

        return CollectionManifest(manager.results,
                                  many=True,
                                  context={
                                      'request': req,
                                      'config': cfg
                                  }).data
Beispiel #19
0
class OrderedCollectionPage(ContextDictSerializer):
    ctx = StaticField(label="@context", value=IIIF_ASTREAMS_CONTEXT)

    id = serpy.MethodField()

    astype = StaticField(label="type", value="OrderedCollectionPage")

    start_index = serpy.MethodField(label="startIndex")

    part_of = serpy.MethodField(label="partOf")

    prev = serpy.MethodField()
    next = serpy.MethodField()

    ordered_items = serpy.MethodField(label="orderedItems")

    def get_id(self, obj: Dict) -> str:  # pylint: disable-msg=unused-argument
        req = self.context.get('request')
        conf = self.context.get('config')
        streams_tmpl: str = conf['templates']['activitystream_id_tmpl']
        page_id: int = self.context.get('page_id')

        return get_identifier(req, f"page-{page_id}", streams_tmpl)

    def get_part_of(self,
                    obj: Dict) -> Dict:  # pylint: disable-msg=unused-argument
        req = self.context.get('request')
        conf = self.context.get('config')
        streams_tmpl: str = conf['templates']['activitystream_id_tmpl']

        parent_id = get_identifier(req, 'all-changes', streams_tmpl)

        return {"id": parent_id, "type": "OrderedCollection"}

    def get_start_index(
            self, obj: Dict) -> int:  # pylint: disable-msg=unused-argument
        cfg = self.context.get('config')

        pagesize: int = int(cfg['solr']['pagesize'])
        page_id: int = self.context.get('page_id')

        # The start index for the page is always one more than the end index of the previous.
        idx: int = (pagesize * page_id) + 1

        return idx

    def get_prev(
            self, obj: Dict
    ) -> Optional[Dict]:  # pylint: disable-msg=unused-argument
        req = self.context.get('request')
        cfg = self.context.get('config')

        page_id: int = self.context.get("page_id")
        prev_page: int = page_id - 1

        # If we're on the first page, don't show the 'prev' key
        if prev_page < 0:
            return None

        page_tmpl: str = cfg['templates']['activitystream_id_tmpl']
        prev_page_id: str = get_identifier(req, f"page-{prev_page}", page_tmpl)

        return {"id": prev_page_id, "type": "OrderedCollectionPage"}

    def get_next(
            self, obj: Dict
    ) -> Optional[Dict]:  # pylint: disable-msg=unused-argument
        req = self.context.get('request')
        cfg = self.context.get('config')

        hits: int = obj.get('results').hits
        pagesize: int = int(cfg['solr']['pagesize'])

        next_page = self.context.get("page_id") + 1
        last_page = math.floor(hits / pagesize)

        # If we're on the last page, don't show the next key
        if next_page > last_page:
            return None

        page_tmpl: str = cfg['templates']['activitystream_id_tmpl']
        next_page_id: str = get_identifier(req, f"page-{next_page}", page_tmpl)

        return {"id": next_page_id, "type": "OrderedCollectionPage"}

    def get_ordered_items(self, obj: Dict) -> Dict:
        activities: List = obj.get('results').docs

        return Activity(activities,
                        many=True,
                        context={
                            'request': self.context.get('request'),
                            'config': self.context.get('config')
                        }).data
Beispiel #20
0
class OrderedCollection(ContextDictSerializer):
    """
    Unlike other serializers in the manifest server, this one takes a dictionary that
    has a single key, 'results', which in turn wraps a pysolr.Results instance. We query
    that instance in this serializer to work out the numbers for the ordered collection,
    but we don't actually ever need the results. (See `create_ordered_collection` above
    for the Solr query that is serialized.)
    """
    ctx = StaticField(
        label="@context",
        value=IIIF_ASTREAMS_CONTEXT
    )

    id = serpy.MethodField()

    astype = StaticField(
        label="type",
        value="OrderedCollection"
    )

    total_items = serpy.MethodField(
        label="totalItems"
    )

    first = serpy.MethodField()
    last = serpy.MethodField()

    def get_id(self, obj: Dict) -> str:  # pylint: disable-msg=unused-argument
        req = self.context.get('request')
        conf = self.context.get('config')
        streams_tmpl: str = conf['templates']['activitystream_id_tmpl']

        return get_identifier(req, 'all-changes', streams_tmpl)

    def get_total_items(self, obj: Dict) -> int:
        return obj.get('results').hits

    def get_first(self, obj: Dict) -> Dict:  # pylint: disable-msg=unused-argument
        req = self.context.get('request')
        cfg = self.context.get('config')
        page_tmpl: str = cfg['templates']['activitystream_id_tmpl']

        return {
            "id": get_identifier(req, 'page-0', page_tmpl),
            "type": "OrderedCollectionPage"
        }

    def get_last(self, obj: Dict) -> Dict:
        req = self.context.get('request')
        cfg = self.context.get('config')

        pagesize: int = int(cfg['solr']['pagesize'])
        hits: int = int(obj.get('results').hits)
        page_no: int = math.floor(hits / pagesize)
        page_id: str = f"page-{page_no}"
        page_tmpl: str = cfg['templates']['activitystream_id_tmpl']

        return {
            "id": get_identifier(req, page_id, page_tmpl),
            "type": "OrderedCollectionPage"
        }
Beispiel #21
0
class Canvas(ContextDictSerializer):
    ctx = serpy.MethodField(label="@context")
    cid = serpy.MethodField(label="@id")

    ctype = StaticField(label="@type", value="sc:Canvas")

    label = serpy.StrField(attr="label_s")

    width = serpy.MethodField()
    height = serpy.MethodField()

    images = serpy.MethodField()
    within = serpy.MethodField()

    metadata = serpy.MethodField()

    other_content = serpy.MethodField(label="otherContent")

    def get_ctx(
        self, obj: SolrResult
    ) -> Optional[str]:  # pylint: disable-msg=unused-argument
        direct_request: bool = self.context.get('direct_request')
        return IIIF_V2_CONTEXT if direct_request else None

    def get_cid(self, obj: SolrResult) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')
        canvas_tmpl = cfg['templates']['canvas_id_tmpl']

        # Surfaces have the suffix "_surface" in Solr. Strip it off for this identifier
        canvas_id = re.sub(SURFACE_ID_SUB, "", obj.get("id"))

        return get_identifier(req, canvas_id, canvas_tmpl)

    def get_images(self, obj: SolrResult) -> List[Dict]:
        return ImageAnnotation(obj.get("_childDocuments_"),
                               context={
                                   "request": self.context.get('request'),
                                   "config": self.context.get('config')
                               },
                               many=True).data

    def get_width(self, obj: SolrResult) -> int:
        """
        Width and Height are required. If (for some reason) there is no image attached to this canvas
        then return a 0. This will allow manifest parsers to load the manifest so that any correct images
        will still be shown.

        :param obj: A Solr result
        :return: An integer representing the width of the canvas.
        """
        if "_childDocuments_" not in obj:
            return 0

        return obj.get('_childDocuments_')[0]['width_i']

    def get_height(self, obj: SolrResult) -> int:
        """
        See the comment for width above.

        :param obj: A Solr result
        :return: An integer representing the height of the canvas.
        """
        if "_childDocuments_" not in obj:
            return 0

        return obj.get("_childDocuments_")[0]['height_i']

    def get_within(self, obj: SolrResult) -> Optional[List]:
        """
        When requested directly, give a within parameter to point back to
        the parent manuscript.
        """
        direct_request: bool = self.context.get('direct_request')

        if not direct_request:
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')

        manifest_tmpl: str = cfg['templates']['manifest_id_tmpl']
        wid: str = get_identifier(req, obj.get('object_id'), manifest_tmpl)

        # get object shelfmark for the label
        fq = [f'id:"{obj.get("object_id")}"', 'type:object']
        fl = ['full_shelfmark_s']
        res = SolrConnection.search(q='*:*', fq=fq, fl=fl, rows=1)

        if res.hits == 0:
            return None

        object_record = res.docs[0]

        return [{
            "@id": wid,
            "@type": "Manifest",
            "label": object_record.get('full_shelfmark_s')
        }]

    def get_metadata(self, obj: SolrResult) -> Optional[List[Dict]]:
        return v2_metadata_block(obj, CANVAS_FIELD_CONFIG)

    def get_other_content(self, obj: SolrResult) -> Optional[List]:
        """
        If the canvas has annotations, add them to the response. A call to check whether there are any
        annotations at all for this manifest is performed in the `Manifest` serializer, and the result
        is passed down here as an optimization, so that we don't have to check every canvas in Solr for
        annotations when the bulk of our manifests do not have any.

        :param obj: A Solr result object
        :return: A List object containing a pointer to the annotation pages, or None if no annotations.
        """
        has_annotations = self.context.get("has_annotations")

        if not has_annotations and not self.context.get('direct_request'):
            return None

        # check if the canvas has any non-image annotation pages
        sid = obj["id"]
        req = self.context.get('request')
        cfg = self.context.get('config')

        fq = ["type:annotationpage", f'surface_id:"{sid}"']
        fl = ["id"]
        manager: SolrManager = SolrManager(SolrConnection)
        manager.search(q='*:*', fq=fq, fl=fl)

        if manager.hits == 0:
            return None

        annotation_list_tmpl: str = cfg['templates']['annolist_id_tmpl']

        annotation_ids = [{
            "@id":
            get_identifier(req, annotation_list['id'], annotation_list_tmpl),
            "@type":
            "sc:AnnotationList"
        } for annotation_list in manager.results]
        return annotation_ids
Beispiel #22
0
class Structure(ContextDictSerializer):
    ctx = serpy.MethodField(label="@context")

    sid = serpy.MethodField(label="@id")

    stype = StaticField(label="@type", value="sc:Range")

    label = serpy.StrField(attr="work_title_s")
    within = serpy.MethodField()

    viewing_hint = serpy.MethodField(label="viewingHint", required=False)

    # members = serpy.MethodField()
    canvases = serpy.MethodField()
    ranges = serpy.MethodField()
    metadata = serpy.MethodField()

    def get_ctx(
        self, obj: SolrResult
    ) -> Optional[str]:  # pylint: disable-msg=unused-argument
        direct_request: bool = self.context.get('direct_request')
        return IIIF_V2_CONTEXT if direct_request else None

    def get_sid(self, obj: Dict) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')
        range_tmpl: str = cfg['templates']['range_id_tmpl']

        identifier: str = obj.get("object_id")
        range_id: str = obj.get("work_id")

        return get_identifier(req, identifier, range_tmpl, range_id=range_id)

    def get_within(self, obj: SolrResult) -> Optional[List]:
        """When requested directly, give a within parameter to point back to
           the parent manuscript.
        """
        direct_request: bool = self.context.get('direct_request')

        if not direct_request:
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')

        manifest_tmpl: str = cfg['templates']['manifest_id_tmpl']
        wid: str = get_identifier(req, obj.get('object_id'), manifest_tmpl)

        return [{"id": wid, "type": "Manifest"}]

    def get_viewing_hint(self, obj: SolrResult) -> Optional[str]:
        if not obj.get("parent_work_id"):
            return "top"
        return None

    def get_ranges(self, obj: SolrResult) -> Optional[List]:
        hierarchy = self.context.get('hierarchy')
        wk_id = obj.get("work_id")

        # If the work ID is not in the hierarchy, it contains
        # a list of canvases; return None.
        if wk_id not in hierarchy:
            return None

        return hierarchy[wk_id]

    def get_canvases(self, obj: SolrResult) -> Optional[List]:
        hierarchy = self.context.get('hierarchy')
        wk_id = obj.get("work_id")

        # If the work id is in the hierarchy, this contains
        # a list of ranges; return None.
        if wk_id in hierarchy:
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')
        surfaces: List = obj.get('surfaces_sm')
        canvas_tmpl: str = cfg['templates']['canvas_id_tmpl']

        ret: List = []
        for s in surfaces:
            ret.append(
                get_identifier(req, re.sub(SURFACE_ID_SUB, "", s),
                               canvas_tmpl))

        return ret

    def get_metadata(self, obj: SolrResult) -> Optional[List[Dict]]:
        return v2_metadata_block(obj, WORKS_METADATA_FIELD_CONFIG)
Beispiel #23
0
class Canvas(ContextDictSerializer):
    # Context will only be emitted if the canvas is being de-referenced directly;
    # otherwise, it will return None and will not appear when embedded in a manifest.
    ctx = serpy.MethodField(label="@context")

    cid = serpy.MethodField(label="id")

    ctype = StaticField(label="type", value="Canvas")

    label = serpy.MethodField()

    width = serpy.MethodField()
    height = serpy.MethodField()

    items = serpy.MethodField()
    part_of = serpy.MethodField(label="partOf")
    annotations = serpy.MethodField()

    metadata = serpy.MethodField()

    def get_label(self, obj: SolrResult) -> Dict:
        return {"en": [f"{obj.get('label_s')}"]}

    def get_width(self, obj: SolrResult) -> int:
        """
        Width and Height are not stored on the canvas, but on the child documents. Assume
        that the first child document, if there is one, contains the width/height for the
        canvas.
        """
        if "_childDocuments_" not in obj:
            return 0

        return obj.get('_childDocuments_')[0]['width_i']

    def get_height(self, obj: SolrResult) -> int:
        if "_childDocuments_" not in obj:
            return 0
        return obj.get("_childDocuments_")[0]['height_i']

    def get_cid(self, obj: SolrResult) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')
        canvas_tmpl: str = cfg['templates']['canvas_id_tmpl']

        # Surfaces have the suffix "_surface" in Solr. Strip it off for this identifier
        canvas_id: str = re.sub(SURFACE_ID_SUB, "", obj.get("id"))

        return get_identifier(req, canvas_id, canvas_tmpl)

    def get_items(self, obj: SolrResult) -> List[Dict]:
        req = self.context.get('request')
        cfg = self.context.get('config')
        image_annotation_page = ImageAnnotationPage(obj,
                                                    context={
                                                        "request": req,
                                                        "config": cfg
                                                    }).data
        return [image_annotation_page]

    def get_ctx(
        self, obj: SolrResult
    ) -> Optional[List]:  # pylint: disable-msg=unused-argument
        """
        If the resource is requested directly (instead of embedded in a manifest)
        return the context object; otherwise it will inherit the context of the parent.

        Note that the 'direct_request' context object is not passed down to children,
        so it will only appear in a top-level object.
        :param obj: Dictionary object to be serialized
        :return: List containing the appropriate context objects.
        """
        direct_request: bool = self.context.get('direct_request')
        return IIIF_V3_CONTEXT if direct_request else None

    def get_part_of(self, obj: SolrResult) -> Optional[List]:
        """
        When requested directly, give a within parameter to point back to
        the parent manuscript.
        """
        direct_request: bool = self.context.get('direct_request')

        if not direct_request:
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')

        manifest_tmpl: str = cfg['templates']['manifest_id_tmpl']
        wid: str = get_identifier(req, obj.get('object_id'), manifest_tmpl)

        # get object shelfmark for the label
        fq = [f'id:"{obj.get("object_id")}"', 'type:object']
        fl = ['full_shelfmark_s']
        res = SolrConnection.search(q='*:*', fq=fq, fl=fl, rows=1)

        if res.hits == 0:
            return None

        object_record = res.docs[0]

        return [{
            "id": wid,
            "type": "Manifest",
            "label": object_record.get('full_shelfmark_s')
        }]

    def get_metadata(self, obj: SolrResult) -> Optional[List[Dict]]:
        return v3_metadata_block(obj, CANVAS_FIELD_CONFIG)

    def get_annotations(self, obj: SolrResult) -> Optional[List[Dict]]:
        """
        If the canvas has annotations, add them to the response. A call to check whether there are any
        annotations at all for this manifest is performed in the `Manifest` serializer, and the result
        is passed down here as an optimization, so that we don't have to check every canvas in Solr for
        annotations when the bulk of our manifests do not have any.

        :param obj: A Solr result object
        :return: A List object containing a pointer to the annotation pages, or None if no annotations.
        """
        has_annotations = self.context.get("has_annotations")

        if not has_annotations and not self.context.get("direct_request"):
            return None

        # check if the canvas has any non-image annotation pages
        sid = obj["id"]
        req = self.context.get('request')
        cfg = self.context.get('config')

        fq = ["type:annotationpage", f'surface_id:"{sid}"']
        fl = ["id"]
        manager: SolrManager = SolrManager(SolrConnection)
        manager.search(q='*:*', fq=fq, fl=fl)

        if manager.hits == 0:
            return None

        annotation_list_tmpl: str = cfg['templates']['annopage_id_tmpl']

        annotation_ids = [{
            "id":
            get_identifier(req, annotation_page['id'], annotation_list_tmpl),
            "type":
            "AnnotationPage"
        } for annotation_page in manager.results]
        return annotation_ids
Beispiel #24
0
class BaseAnnotationPage(ContextDictSerializer):
    ctx = serpy.MethodField(label="@context")

    aid = serpy.MethodField(label="id")
    atype = StaticField(label="type", value="AnnotationPage")

    items = serpy.MethodField()
    part_of = serpy.MethodField(label="partOf")

    def get_aid(self, obj: SolrResult) -> str:
        req = self.context.get('request')
        cfg = self.context.get('config')
        annopage_tmpl: str = cfg['templates']['annopage_id_tmpl']

        # Surfaces have the suffix "_surface" in Solr. Strip it off for this identifier
        # this isn't necessary for annotationpage ids, but also won't affect them
        annopage_id: str = re.sub(SURFACE_ID_SUB, "", obj.get("id"))

        return get_identifier(req, annopage_id, annopage_tmpl)

    def get_ctx(
        self, obj: SolrResult
    ) -> Optional[List]:  # pylint: disable-msg=unused-argument
        """
        If the resource is requested directly (instead of embedded in a manifest)
        return the context object; otherwise it will inherit the context of the parent.

        Note that the 'direct_request' context object is not passed down to children,
        so it will only appear in a top-level object.
        :param obj: Dictionary object to be serialized
        :return: List containing the appropriate context objects.
        """
        direct_request: bool = self.context.get('direct_request')
        return IIIF_V3_CONTEXT if direct_request else None

    @abstractmethod
    def get_items(self, obj: SolrResult) -> List[Dict]:
        """
        Return a list of Annotations for this page - the type of annotation will depend on the AnnotationPage subclass
        :param obj:
        :return:
        """

    def get_part_of(self, obj: SolrResult) -> Optional[List]:
        """When requested directly, give a within parameter to point back to
           the parent manuscript.
        """
        direct_request: bool = self.context.get('direct_request', False)

        if not direct_request:
            return None

        req = self.context.get('request')
        cfg = self.context.get('config')

        manifest_tmpl: str = cfg['templates']['manifest_id_tmpl']
        wid: str = get_identifier(req, obj.get('object_id'), manifest_tmpl)

        # get object shelfmark for the label
        fq = [f'id:"{obj.get("object_id")}"', 'type:object']
        fl = ['full_shelfmark_s']

        res = SolrConnection.search(q='*:*', fq=fq, fl=fl, rows=1)

        if res.hits == 0:
            return None

        object_record = res.docs[0]

        return [{
            "id": wid,
            "type": "Manifest",
            "label": object_record.get('full_shelfmark_s')
        }]