Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PP-1507] Present sorts as links with palaceproject.io namespaced sort rels. #2100

Merged
merged 4 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion src/palace/manager/feed/serializer/opds.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from lxml import etree

from palace.manager.core.facets import FacetConstants
from palace.manager.feed.serializer.base import SerializerInterface
from palace.manager.feed.types import (
Acquisition,
Expand All @@ -15,10 +16,11 @@
FeedEntryType,
FeedMetadata,
IndirectAcquisition,
Link,
WorkEntryData,
)
from palace.manager.util.datetime_helpers import utc_now
from palace.manager.util.opds_writer import OPDSFeed, OPDSMessage
from palace.manager.util.opds_writer import AtomFeed, OPDSFeed, OPDSMessage

TAG_MAPPING = {
"indirectAcquisition": f"{{{OPDSFeed.OPDS_NS}}}indirectAcquisition",
Expand All @@ -42,6 +44,7 @@
"facetGroupType": f"{{{OPDSFeed.SIMPLIFIED_NS}}}facetGroupType",
"activeFacet": f"{{{OPDSFeed.OPDS_NS}}}activeFacet",
"ratingValue": f"{{{OPDSFeed.SCHEMA_NS}}}ratingValue",
"activeSort": f"{{{OPDSFeed.PALACE_PROPS_NS}}}active-sort",
}

AUTHOR_MAPPING = {
Expand All @@ -52,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 @@ -107,7 +119,12 @@ def serialize_feed(
serialized.append(breadcrumbs)

for link in feed.facet_links:
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)
Expand Down Expand Up @@ -390,3 +407,11 @@ def to_string(cls, element: etree._Element) -> str:

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"
)
if link.get("activeFacet", False):
sort_link.add_attributes(dict(activeSort="true"))
return self._serialize_feed_entry("link", sort_link)
30 changes: 25 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,31 @@ 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):
# TODO: When we remove the facet-based sort links [PP-1814],
# this code path will be removed and we'll want to pull the sort
# link data from the feed.sort_links once that is in place.
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) -> dict[str, Any]:
sort_link: dict[str, Any] = {
"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
6 changes: 6 additions & 0 deletions src/palace/manager/util/opds_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ class AtomFeed:

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 All @@ -56,6 +61,7 @@ class AtomFeed:
"bib": BIB_SCHEMA_NS,
"opensearch": OPENSEARCH_NS,
"lcp": LCP_NS,
"palaceproperties": PALACE_PROPS_NS,
}

E = ElementMaker(nsmap=nsmap)
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"
)