Skip to content

Commit

Permalink
Implement language facet for OPDS feed
Browse files Browse the repository at this point in the history
With a hard-coded list of languages: All, Finnish, Swedish, English, Others.

When creating a library, this will be the default list of language facets.

Default selection is 'All'.

Instead of hard-coded language list, would also be possible to make the list
fully customizable by library admins.
  • Loading branch information
attemoi committed Feb 14, 2024
1 parent 1a58ee4 commit 2ada4e0
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 1 deletion.
1 change: 1 addition & 0 deletions api/lanes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,7 @@ class CrawlableFacets(Facets):
Facets.COLLECTION_FACET_GROUP_NAME: Facets.COLLECTION_FULL,
Facets.DISTRIBUTOR_FACETS_GROUP_NAME: Facets.DISTRIBUTOR_ALL,
Facets.COLLECTION_NAME_FACETS_GROUP_NAME: Facets.COLLECTION_NAME_ALL,
Facets.LANGUAGE_FACET_GROUP_NAME: Facets.LANGUAGE_ALL,
}

@classmethod
Expand Down
28 changes: 28 additions & 0 deletions core/configuration/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,34 @@ class LibrarySettings(BaseSettings):
skip=True,
),
)
# Finland
facets_enabled_language: list[str] = FormField(
FacetConstants.DEFAULT_ENABLED_FACETS[FacetConstants.LANGUAGE_FACET_GROUP_NAME],
form=LibraryConfFormItem(
label="Allow patrons to filter language to",
type=ConfigurationFormItemType.MENU,
options={
facet: FacetConstants.FACET_DISPLAY_TITLES[facet]
for facet in FacetConstants.LANGUAGE_FACETS
},
category="Lanes & Filters",
paired="facets_default_language",
level=Level.SYS_ADMIN_OR_MANAGER,
),
)
facets_default_language: str = FormField(
FacetConstants.LANGUAGE_ALL,
form=LibraryConfFormItem(
label="Default Language",
type=ConfigurationFormItemType.SELECT,
options={
facet: FacetConstants.FACET_DISPLAY_TITLES[facet]
for facet in FacetConstants.LANGUAGE_FACETS
},
category="Lanes & Filters",
skip=True,
),
)
library_description: str | None = FormField(
None,
form=LibraryConfFormItem(
Expand Down
24 changes: 23 additions & 1 deletion core/external_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -1768,8 +1768,10 @@ def from_worklist(cls, _db, worklist, facets):
excluded_audiobook_data_sources = [DataSource.lookup(_db, x) for x in excluded]
if library is None:
allow_holds = True
facets_enabled_language = FacetConstants.LANGUAGE_ALL
else:
allow_holds = library.settings.allow_holds
facets_enabled_language = library.settings.facets_enabled_language
return cls(
collections,
media,
Expand All @@ -1784,6 +1786,7 @@ def from_worklist(cls, _db, worklist, facets):
allow_holds=allow_holds,
license_datasource=license_datasource_id,
lane_building=True,
facets_enabled_language=facets_enabled_language,
)

def __init__(
Expand Down Expand Up @@ -1938,6 +1941,10 @@ def __init__(

self.lane_building = kwargs.pop("lane_building", False)

self.facets_enabled_language = kwargs.pop(
"facets_enabled_language", FacetConstants.LANGUAGE_ALL
)

# At this point there should be no keyword arguments -- you can't pass
# whatever you want into this method.
if kwargs:
Expand Down Expand Up @@ -2065,7 +2072,22 @@ def build(self, _chain_filters=None):
if self.media:
f = chain(f, Terms(medium=scrub_list(self.media)))

if self.languages:
# Finland, logic for LANGUAGE_OTHERS
if self.languages == [FacetConstants.LANGUAGE_OTHERS]:
excluded_terms = [
language
for language in self.facets_enabled_language
if language
not in {
FacetConstants.LANGUAGE_ALL,
FacetConstants.LANGUAGE_OTHERS,
}
]
exclusion_query = Bool(
must_not=[Terms(language=scrub_list(excluded_terms))]
)
f = chain(f, exclusion_query)
elif self.languages:
f = chain(f, Terms(language=scrub_list(self.languages)))

if self.fiction is not None:
Expand Down
26 changes: 26 additions & 0 deletions core/facets.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ class FacetConstants:
AVAILABLE_OPEN_ACCESS,
]

# Finland
# Subset the collection by language.
LANGUAGE_FACET_GROUP_NAME = "language"
LANGUAGE_ALL = "all"
LANGUAGE_FINNISH = "fin"
LANGUAGE_SWEDISH = "swe"
LANGUAGE_ENGLISH = "eng"
LANGUAGE_OTHERS = "others"
LANGUAGE_FACETS: list[str] = [
LANGUAGE_ALL,
LANGUAGE_FINNISH,
LANGUAGE_SWEDISH,
LANGUAGE_ENGLISH,
LANGUAGE_OTHERS,
]

# The names of the order facets.
ORDER_FACET_GROUP_NAME = "order"
ORDER_TITLE = "title"
Expand Down Expand Up @@ -66,6 +82,7 @@ class FacetConstants:
COLLECTION_FACET_GROUP_NAME: COLLECTION_FACETS,
AVAILABILITY_FACET_GROUP_NAME: AVAILABILITY_FACETS,
ORDER_FACET_GROUP_NAME: ORDER_FACETS,
LANGUAGE_FACET_GROUP_NAME: LANGUAGE_FACETS, # Finland
}

GROUP_DISPLAY_TITLES = {
Expand All @@ -74,6 +91,7 @@ class FacetConstants:
COLLECTION_FACET_GROUP_NAME: _("Collection"),
DISTRIBUTOR_FACETS_GROUP_NAME: _("Distributor"),
COLLECTION_NAME_FACETS_GROUP_NAME: _("Collection Name"),
LANGUAGE_FACET_GROUP_NAME: _("Language"), # Finland
}

GROUP_DESCRIPTIONS = {
Expand All @@ -84,6 +102,7 @@ class FacetConstants:
COLLECTION_NAME_FACETS_GROUP_NAME: _(
"Allow patrons to filter by collection name"
),
LANGUAGE_FACET_GROUP_NAME: _("Allow patrons to filter by language"), # Finland
}

FACET_DISPLAY_TITLES = {
Expand All @@ -98,6 +117,11 @@ class FacetConstants:
AVAILABLE_OPEN_ACCESS: _("Yours to keep"),
COLLECTION_FULL: _("Everything"),
COLLECTION_FEATURED: _("Popular Books"),
LANGUAGE_ALL: _("All"),
LANGUAGE_FINNISH: _("Finnish"),
LANGUAGE_SWEDISH: _("Swedish"),
LANGUAGE_ENGLISH: _("English"),
LANGUAGE_OTHERS: _("Others"),
}

# For titles generated based on some runtime value
Expand All @@ -118,6 +142,7 @@ class FacetConstants:
COLLECTION_FACET_GROUP_NAME: [COLLECTION_FULL, COLLECTION_FEATURED],
DISTRIBUTOR_FACETS_GROUP_NAME: [DISTRIBUTOR_ALL],
COLLECTION_NAME_FACETS_GROUP_NAME: [COLLECTION_NAME_ALL],
LANGUAGE_FACET_GROUP_NAME: LANGUAGE_FACETS,
}

# Unless a library offers an alternate configuration, these
Expand All @@ -128,6 +153,7 @@ class FacetConstants:
COLLECTION_FACET_GROUP_NAME: COLLECTION_FULL,
DISTRIBUTOR_FACETS_GROUP_NAME: DISTRIBUTOR_ALL,
COLLECTION_NAME_FACETS_GROUP_NAME: COLLECTION_NAME_ALL,
LANGUAGE_FACET_GROUP_NAME: LANGUAGE_ALL,
}

SORT_ORDER_TO_OPENSEARCH_FIELD_NAME = {
Expand Down
38 changes: 38 additions & 0 deletions core/lane.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,12 +436,26 @@ def _values_from_request(
400,
)

# Finland
g = Facets.LANGUAGE_FACET_GROUP_NAME
language: str = get_argument(g, cls.default_facet(config, g))
language_facets = cls.available_facets(config, g)
if language and not language in language_facets:
return INVALID_INPUT.detailed(
_(
"I don't understand what language '%(language)s' refers to.",
language=language,
),
400,
)

enabled = {
Facets.ORDER_FACET_GROUP_NAME: order_facets,
Facets.AVAILABILITY_FACET_GROUP_NAME: availability_facets,
Facets.COLLECTION_FACET_GROUP_NAME: collection_facets,
Facets.DISTRIBUTOR_FACETS_GROUP_NAME: distributor_facets,
Facets.COLLECTION_NAME_FACETS_GROUP_NAME: collection_name_facets,
Facets.LANGUAGE_FACET_GROUP_NAME: language_facets, # Finland
}

return dict(
Expand All @@ -451,6 +465,7 @@ def _values_from_request(
distributor=distributor,
collection_name=collection_name,
enabled_facets=enabled,
language=language, # Finland
)

@classmethod
Expand Down Expand Up @@ -484,6 +499,7 @@ def __init__(
order,
distributor,
collection_name,
language,
order_ascending=None,
enabled_facets=None,
entrypoint=None,
Expand Down Expand Up @@ -536,6 +552,9 @@ def __init__(
self.collection_name = collection_name or self.default_facet(
library, self.COLLECTION_NAME_FACETS_GROUP_NAME
)
self.language: str = language or self.default_facet(
library, self.LANGUAGE_FACET_GROUP_NAME
)
if order_ascending == self.ORDER_ASCENDING:
order_ascending = True
elif order_ascending == self.ORDER_DESCENDING:
Expand All @@ -551,6 +570,7 @@ def navigate(
entrypoint=None,
distributor=None,
collection_name=None,
language=None,
):
"""Create a slightly different Facets object from this one."""
return self.__class__(
Expand All @@ -560,6 +580,7 @@ def navigate(
order=order or self.order,
distributor=distributor or self.distributor,
collection_name=collection_name or self.collection_name,
language=language or self.language,
enabled_facets=self.facets_enabled_at_init,
entrypoint=(entrypoint or self.entrypoint),
entrypoint_is_default=False,
Expand All @@ -577,6 +598,8 @@ def items(self):
yield (self.DISTRIBUTOR_FACETS_GROUP_NAME, self.distributor)
if self.collection_name:
yield (self.COLLECTION_NAME_FACETS_GROUP_NAME, self.collection_name)
if self.language:
yield (self.LANGUAGE_FACET_GROUP_NAME, self.language)

@property
def enabled_facets(self):
Expand All @@ -595,6 +618,7 @@ def enabled_facets(self):
self.COLLECTION_FACET_GROUP_NAME,
self.DISTRIBUTOR_FACETS_GROUP_NAME,
self.COLLECTION_NAME_FACETS_GROUP_NAME,
self.LANGUAGE_FACET_GROUP_NAME,
]
for facet_type in facet_types:
yield self.facets_enabled_at_init.get(facet_type, [])
Expand All @@ -606,6 +630,7 @@ def enabled_facets(self):
Facets.COLLECTION_FACET_GROUP_NAME,
Facets.DISTRIBUTOR_FACETS_GROUP_NAME,
Facets.COLLECTION_NAME_FACETS_GROUP_NAME,
Facets.LANGUAGE_FACET_GROUP_NAME,
):
yield self.available_facets(self.library, group_name)

Expand All @@ -625,6 +650,7 @@ def facet_groups(self):
collection_facets,
distributor_facets,
collection_name_facets,
language_facets,
) = self.enabled_facets

def dy(new_value):
Expand Down Expand Up @@ -674,6 +700,13 @@ def dy(new_value):
facets = self.navigate(collection_name=facet)
yield (group, facet, facets, facet == current_value)

if len(language_facets) > 1:
for facet in language_facets:
group = self.LANGUAGE_FACET_GROUP_NAME
current_value = self.language
facets = self.navigate(language=facet)
yield (group, facet, facets, facet == current_value)

def modify_search_filter(self, filter):
"""Modify the given external_search.Filter object
so that it reflects the settings of this Facets object.
Expand All @@ -692,6 +725,10 @@ def modify_search_filter(self, filter):
filter.availability = self.availability
filter.subcollection = self.collection

# Finland
if self.language and self.language != self.LANGUAGE_ALL:
filter.languages = [self.language]

# We can only have distributor and collection name facets if we have a library
if self.library:
_db = Session.object_session(self.library)
Expand Down Expand Up @@ -2935,6 +2972,7 @@ def update_size(self, _db, search_engine=None):
order=FacetConstants.ORDER_WORK_ID,
distributor=FacetConstants.DISTRIBUTOR_ALL,
collection_name=FacetConstants.COLLECTION_NAME_ALL,
language=FacetConstants.LANGUAGE_ALL,
entrypoint=entrypoint,
)
filter = self.filter(_db, facets)
Expand Down

0 comments on commit 2ada4e0

Please sign in to comment.