From bffda05eb1800d226bcbf4b1f9cbbb4e453f7ac3 Mon Sep 17 00:00:00 2001 From: Daniel Bernstein Date: Mon, 28 Oct 2024 15:08:28 -0700 Subject: [PATCH] Add support for http://palaceproject.io/terms/properties/default property on the sort and facet relations in OPDS1V2 and OPDS2V2 feeds. --- src/palace/manager/feed/acquisition.py | 21 +++++++++++++++------ src/palace/manager/feed/serializer/opds.py | 11 ++++++++--- src/palace/manager/feed/serializer/opds2.py | 11 ++++++++++- src/palace/manager/sqlalchemy/model/lane.py | 4 ++-- src/palace/manager/util/opds_writer.py | 1 + tests/manager/feed/test_opds2_serializer.py | 10 ++++++++-- tests/manager/feed/test_opds_serializer.py | 8 +++++++- 7 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/palace/manager/feed/acquisition.py b/src/palace/manager/feed/acquisition.py index fdef9abaa..e82c958f0 100644 --- a/src/palace/manager/feed/acquisition.py +++ b/src/palace/manager/feed/acquisition.py @@ -146,7 +146,7 @@ def facet_links( circumstances apply. You need to decide whether to call add_entrypoint_links in addition to calling this method. """ - for group, value, new_facets, selected in facets.facet_groups: + for group, value, new_facets, selected, default_facet in facets.facet_groups: url = annotator.facet_url(new_facets) if not url: continue @@ -160,11 +160,18 @@ def facet_links( # system. It may be left over from an earlier version, # or just weird junk data. continue - yield cls.facet_link(url, str(facet_title), str(group_title), selected) + yield cls.facet_link( + url, str(facet_title), str(group_title), selected, default_facet + ) @classmethod def facet_link( - cls, href: str, title: str, facet_group_name: str, is_active: bool + cls, + href: str, + title: str, + facet_group_name: str, + is_active: bool, + is_default: bool, ) -> Link: """Build a set of attributes for a facet link. @@ -174,8 +181,8 @@ def facet_link( e.g. "Sort By". :param is_active: True if this is the client's currently selected facet. - - :retusrn: A dictionary of attributes, suitable for passing as + :param is_default: True if this is the default facet + :return: A dictionary of attributes, suitable for passing as keyword arguments into OPDSFeed.add_link_to_feed. """ args = dict(href=href, title=title) @@ -183,6 +190,8 @@ def facet_link( args["facetGroup"] = facet_group_name if is_active: args["activeFacet"] = "true" + if is_default: + args["defaultFacet"] = "true" return Link.create(**args) def as_error_response(self, **kwargs: Any) -> OPDSFeedResponse: @@ -266,7 +275,7 @@ def _entrypoint_link( url = url_generator(entrypoint) is_selected = entrypoint is selected_entrypoint - link = cls.facet_link(url, display_title, group_name, is_selected) + link = cls.facet_link(url, display_title, group_name, is_selected, is_default) # Unlike a normal facet group, every link in this facet # group has an additional attribute marking it as an entry diff --git a/src/palace/manager/feed/serializer/opds.py b/src/palace/manager/feed/serializer/opds.py index a0716c8cd..bbce2fd59 100644 --- a/src/palace/manager/feed/serializer/opds.py +++ b/src/palace/manager/feed/serializer/opds.py @@ -43,6 +43,7 @@ "facetGroup": f"{{{OPDSFeed.OPDS_NS}}}facetGroup", "facetGroupType": f"{{{OPDSFeed.SIMPLIFIED_NS}}}facetGroupType", "activeFacet": f"{{{OPDSFeed.OPDS_NS}}}activeFacet", + "defaultFacet": f"{{{OPDSFeed.PALACE_PROPS_NS}}}default", "ratingValue": f"{{{OPDSFeed.SCHEMA_NS}}}ratingValue", "activeSort": f"{{{OPDSFeed.PALACE_PROPS_NS}}}active-sort", } @@ -413,7 +414,7 @@ def _serialize_facet_links(self, facet_links: list[Link]) -> list[Link]: links.append(self._serialize_feed_entry("link", link)) return links - def _serialize_sort_links(self, facet_links): + def _serialize_sort_links(self, facet_links: list[Link]) -> list[Link]: return [] @@ -437,14 +438,18 @@ def _serialize_sort_links(self, facet_links: list[Link]) -> list[Link]: if facet_links: for link in facet_links: if is_sort_link(link): - links.append(self._serialize_sort_link("link", link)) + links.append(self._serialize_sort_link(link)) return links def _serialize_sort_link(self, link: Link) -> etree._Element: sort_link = Link( href=link.href, title=link.title, rel=AtomFeed.PALACE_REL_NS + "sort" ) + attributes: dict[str, Any] = dict() if link.get("activeFacet", False): - sort_link.add_attributes(dict(activeSort="true")) + attributes.update(dict(activeSort="true")) + if link.get("defaultFacet", False): + attributes.update(dict(defaultFacet="true")) + sort_link.add_attributes(attributes) return self._serialize_feed_entry("link", sort_link) diff --git a/src/palace/manager/feed/serializer/opds2.py b/src/palace/manager/feed/serializer/opds2.py index 614e056b8..9531a22a0 100644 --- a/src/palace/manager/feed/serializer/opds2.py +++ b/src/palace/manager/feed/serializer/opds2.py @@ -34,6 +34,7 @@ PALACE_REL_SORT = AtomFeed.PALACE_REL_SORT PALACE_PROPERTIES_ACTIVE_SORT = AtomFeed.PALACE_PROPS_NS + "active-sort" +PALACE_PROPERTIES_DEFAULT = AtomFeed.PALACE_PROPERTIES_DEFAULT class OPDS2Version1Serializer(SerializerInterface[dict[str, Any]]): @@ -255,6 +256,14 @@ def _serialize_sort_link(cls, link: Link) -> dict[str, Any]: "title": link.title, "rel": PALACE_REL_SORT, } + + properties: dict[str, str] = {} + + sort_link["properties"] = properties + if link.get("activeFacet", False): - sort_link["properties"] = {PALACE_PROPERTIES_ACTIVE_SORT: "true"} + properties.update({PALACE_PROPERTIES_ACTIVE_SORT: "true"}) + + if link.get("defaultFacet", False): + properties.update({PALACE_PROPERTIES_DEFAULT: "true"}) return sort_link diff --git a/src/palace/manager/sqlalchemy/model/lane.py b/src/palace/manager/sqlalchemy/model/lane.py index 6bcd34a65..e1e8e08f9 100644 --- a/src/palace/manager/sqlalchemy/model/lane.py +++ b/src/palace/manager/sqlalchemy/model/lane.py @@ -97,8 +97,8 @@ def query_string(self): @property def facet_groups(self): - """Yield a list of 4-tuples - (facet group, facet value, new Facets object, selected) + """Yield a list of 5-tuples + (facet group, facet value, new Facets object, selected, default) for use in building OPDS facets. This does not include the 'entry point' facet group, diff --git a/src/palace/manager/util/opds_writer.py b/src/palace/manager/util/opds_writer.py index 43d5b8fb2..a212b8698 100644 --- a/src/palace/manager/util/opds_writer.py +++ b/src/palace/manager/util/opds_writer.py @@ -47,6 +47,7 @@ class AtomFeed: PALACE_PROPS_NS = "http://palaceproject.io/terms/properties/" PALACE_REL_SORT = PALACE_REL_NS + "sort" + PALACE_PROPERTIES_DEFAULT = PALACE_PROPS_NS + "default" nsmap = { None: ATOM_NS, diff --git a/tests/manager/feed/test_opds2_serializer.py b/tests/manager/feed/test_opds2_serializer.py index 53d1cdaba..bad53e98b 100644 --- a/tests/manager/feed/test_opds2_serializer.py +++ b/tests/manager/feed/test_opds2_serializer.py @@ -2,6 +2,7 @@ from palace.manager.feed.serializer.opds2 import ( PALACE_PROPERTIES_ACTIVE_SORT, + PALACE_PROPERTIES_DEFAULT, PALACE_REL_SORT, OPDS2Version1Serializer, OPDS2Version2Serializer, @@ -236,7 +237,9 @@ def test_serialize_opds_message(self): def test_serialize_v2_sort_links(self): feed_data = FeedData() link = Link(href="test", rel="test_rel", title="text1") - link.add_attributes(dict(facetGroup="Sort by", activeFacet="true")) + link.add_attributes( + dict(facetGroup="Sort by", activeFacet="true", defaultFacet="true") + ) feed_data.facet_links.append(link) links = OPDS2Version2Serializer()._serialize_feed_links(feed=feed_data) @@ -246,7 +249,10 @@ def test_serialize_v2_sort_links(self): "href": "test", "rel": PALACE_REL_SORT, "title": "text1", - "properties": {PALACE_PROPERTIES_ACTIVE_SORT: "true"}, + "properties": { + PALACE_PROPERTIES_ACTIVE_SORT: "true", + PALACE_PROPERTIES_DEFAULT: "true", + }, } ], "facets": [], diff --git a/tests/manager/feed/test_opds_serializer.py b/tests/manager/feed/test_opds_serializer.py index da31c099b..df5f9409c 100644 --- a/tests/manager/feed/test_opds_serializer.py +++ b/tests/manager/feed/test_opds_serializer.py @@ -263,7 +263,9 @@ def test_serialize_opds_message(self): def test_serialize_sort_link(self): link = Link(href="test", rel="test_rel", title="text1") - link.add_attributes(dict(facetGroup="Sort by", activeFacet="true")) + link.add_attributes( + dict(facetGroup="Sort by", activeFacet="true", defaultFacet="true") + ) serializer = OPDS1Version2Serializer() assert is_sort_link(link) sort_link = serializer._serialize_sort_link(link) @@ -274,3 +276,7 @@ def test_serialize_sort_link(self): sort_link.attrib["{http://palaceproject.io/terms/properties/}active-sort"] == "true" ) + assert ( + sort_link.attrib["{http://palaceproject.io/terms/properties/}default"] + == "true" + )