Skip to content

Commit

Permalink
Implement for OPDS2 and add tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
dbernstein committed Oct 15, 2024
1 parent 5329e3d commit de43c6e
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 18 deletions.
27 changes: 16 additions & 11 deletions src/palace/manager/feed/serializer/opds.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@
}


def is_sort_link(link: Link) -> bool:
"""A until method that determines if the specified link is a sort link"""
return (
hasattr(link, "facetGroup")
and link.facetGroup
== FacetConstants.GROUP_DISPLAY_TITLES[FacetConstants.ORDER_FACET_GROUP_NAME]
)


class OPDS1Serializer(SerializerInterface[etree._Element], OPDSFeed):
"""An OPDS 1.2 Atom feed serializer"""

Expand Down Expand Up @@ -110,22 +119,16 @@ def serialize_feed(
serialized.append(breadcrumbs)

for link in feed.facet_links:
if self.is_sort_link(link):
if is_sort_link(link):
serialized.append(self._serialize_sort_link(link))
# TODO once the clients are no longer relying on facet based sorting
# an "else" should be introduced here since we only need to provide one style of sorting links
serialized.append(self._serialize_feed_entry("link", link))
# TODO end else here

etree.indent(serialized)
return self.to_string(serialized)

def is_sort_link(self, link) -> bool:
return (
hasattr(link, "facetGroup")
and link.facetGroup
== FacetConstants.GROUP_DISPLAY_TITLES[
FacetConstants.ORDER_FACET_GROUP_NAME
]
)

def _serialize_feed_metadata(self, metadata: FeedMetadata) -> list[etree._Element]:
tags = []
# Compulsory title
Expand Down Expand Up @@ -406,7 +409,9 @@ def content_type(self) -> str:
return OPDSFeed.ACQUISITION_FEED_TYPE

def _serialize_sort_link(self, link: Link) -> etree._Element:
sort_link = Link(href=link.href, title=link.title, rel=AtomFeed.PALACE_REL_NS)
sort_link = Link(
href=link.href, title=link.title, rel=AtomFeed.PALACE_REL_NS + "sort"
)
if link.get("activeFacet", False):
sort_link.add_attributes(dict(activeSort="true"))
return self._serialize_feed_entry("link", sort_link)
23 changes: 18 additions & 5 deletions src/palace/manager/feed/serializer/opds2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import Any

from palace.manager.feed.serializer.base import SerializerInterface
from palace.manager.feed.serializer.opds import is_sort_link
from palace.manager.feed.types import (
Acquisition,
Author,
Expand All @@ -12,7 +13,7 @@
WorkEntryData,
)
from palace.manager.sqlalchemy.model.contributor import Contributor
from palace.manager.util.opds_writer import OPDSMessage
from palace.manager.util.opds_writer import AtomFeed, OPDSMessage

ALLOWED_ROLES = [
"translator",
Expand All @@ -31,6 +32,9 @@
if name.lower() in ALLOWED_ROLES
}

PALACE_REL_SORT = AtomFeed.PALACE_REL_SORT
PALACE_PROPERTIES_ACTIVE_SORT = AtomFeed.PALACE_PROPS_NS + "active-sort"


class OPDS2Serializer(SerializerInterface[dict[str, Any]]):
CONTENT_TYPE = "application/opds+json"
Expand Down Expand Up @@ -191,15 +195,24 @@ def _serialize_feed_links(self, feed: FeedData) -> dict[str, Any]:

facet_links: dict[str, Any] = defaultdict(lambda: {"metadata": {}, "links": []})
for link in feed.facet_links:
group = getattr(link, "facetGroup", None)
if group:
facet_links[group]["links"].append(self._serialize_link(link))
facet_links[group]["metadata"]["title"] = group
if is_sort_link(link):
link_data["links"].append(self._serialize_sort_link(link))
else:
group = getattr(link, "facetGroup", None)
if group:
facet_links[group]["links"].append(self._serialize_link(link))
facet_links[group]["metadata"]["title"] = group
for _, facets in facet_links.items():
link_data["facets"].append(facets)

return link_data

def _serialize_sort_link(self, link: Link):
sort_link = {"href": link.href, "title": link.title, "rel": PALACE_REL_SORT}
if link.get("activeFacet", False):
sort_link["properties"] = {PALACE_PROPERTIES_ACTIVE_SORT: "true"}
return sort_link

def _serialize_contributor(self, author: Author) -> dict[str, Any]:
result: dict[str, Any] = {"name": author.name}
if author.sort_name:
Expand Down
4 changes: 4 additions & 0 deletions src/palace/manager/util/opds_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ class AtomFeed:
BIB_SCHEMA_NS = "http://bib.schema.org/"

LCP_NS = "http://readium.org/lcp-specs/ns"

PALACE_REL_NS = "http://palaceproject.io/terms/rel/"
PALACE_PROPS_NS = "http://palaceproject.io/terms/properties/"

PALACE_REL_SORT = PALACE_REL_NS + "sort"

nsmap = {
None: ATOM_NS,
"app": APP_NS,
Expand Down
25 changes: 24 additions & 1 deletion tests/manager/feed/test_opds2_serializer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import json

from palace.manager.feed.serializer.opds2 import OPDS2Serializer
from palace.manager.feed.serializer.opds2 import (
PALACE_PROPERTIES_ACTIVE_SORT,
PALACE_REL_SORT,
OPDS2Serializer,
)
from palace.manager.feed.types import (
Acquisition,
Author,
Expand Down Expand Up @@ -227,3 +231,22 @@ def test_serialize_opds_message(self):
assert OPDS2Serializer().serialize_opds_message(
OPDSMessage("URN", 200, "Description")
) == dict(urn="URN", description="Description")

def test_serialize_sort_links(self):
feed_data = FeedData()
link = Link(href="test", rel="test_rel", title="text1")
link.add_attributes(dict(facetGroup="Sort by", activeFacet="true"))
feed_data.facet_links.append(link)
links = OPDS2Serializer()._serialize_feed_links(feed=feed_data)

assert links == {
"links": [
{
"href": "test",
"rel": PALACE_REL_SORT,
"title": "text1",
"properties": {PALACE_PROPERTIES_ACTIVE_SORT: "true"},
}
],
"facets": [],
}
17 changes: 16 additions & 1 deletion tests/manager/feed/test_opds_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import pytz
from lxml import etree

from palace.manager.feed.serializer.opds import OPDS1Serializer
from palace.manager.feed.serializer.opds import OPDS1Serializer, is_sort_link
from palace.manager.feed.serializer.opds2 import PALACE_REL_SORT
from palace.manager.feed.types import (
Acquisition,
Author,
Expand Down Expand Up @@ -255,3 +256,17 @@ def test_serialize_opds_message(self):
serializer = OPDS1Serializer()
result = serializer.serialize_opds_message(message)
assert serializer.to_string(result) == serializer.to_string(message.tag)

def test_serialize_sort_link(self):
link = Link(href="test", rel="test_rel", title="text1")
link.add_attributes(dict(facetGroup="Sort by", activeFacet="true"))
serializer = OPDS1Serializer()
assert is_sort_link(link)
sort_link = serializer._serialize_sort_link(link)
assert sort_link.attrib["title"] == "text1"
assert sort_link.attrib["href"] == "test"
assert sort_link.attrib["rel"] == PALACE_REL_SORT
assert (
sort_link.attrib["{http://palaceproject.io/terms/properties/}active-sort"]
== "true"
)

0 comments on commit de43c6e

Please sign in to comment.