From 012a35fff03f461c7f3436520f7e1fab55e140d9 Mon Sep 17 00:00:00 2001 From: Atte Moisio Date: Wed, 14 Feb 2024 20:23:42 +0200 Subject: [PATCH] Implement language facet for OPDS feed 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. --- api/lanes.py | 1 + core/configuration/library.py | 28 +++++++++ core/external_search.py | 24 +++++++- core/facets.py | 26 ++++++++ core/lane.py | 36 +++++++++++ tests/api/controller/test_work.py | 5 +- tests/api/feed/test_opds_acquisition_feed.py | 2 +- tests/api/test_lanes.py | 4 +- tests/core/models/test_library.py | 2 + tests/core/test_external_search.py | 20 ++++-- tests/core/test_lane.py | 65 +++++++++++++++++--- 11 files changed, 193 insertions(+), 20 deletions(-) diff --git a/api/lanes.py b/api/lanes.py index 13f269bde..4479426e4 100644 --- a/api/lanes.py +++ b/api/lanes.py @@ -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 diff --git a/core/configuration/library.py b/core/configuration/library.py index eddf736b9..7e79effd0 100644 --- a/core/configuration/library.py +++ b/core/configuration/library.py @@ -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( diff --git a/core/external_search.py b/core/external_search.py index c3fe8af23..8479761c3 100644 --- a/core/external_search.py +++ b/core/external_search.py @@ -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, @@ -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__( @@ -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: @@ -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: diff --git a/core/facets.py b/core/facets.py index 692967fca..652fdd450 100644 --- a/core/facets.py +++ b/core/facets.py @@ -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" @@ -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 = { @@ -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 = { @@ -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 = { @@ -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 @@ -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 @@ -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 = { diff --git a/core/lane.py b/core/lane.py index 4d04fcd3f..0188a2f7b 100644 --- a/core/lane.py +++ b/core/lane.py @@ -324,6 +324,7 @@ def default( entrypoint=None, distributor=None, collection_name=None, + language=None, ): return cls( library, @@ -332,6 +333,7 @@ def default( order=order, distributor=distributor, collection_name=collection_name, + language=language, entrypoint=entrypoint, ) @@ -436,12 +438,21 @@ 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) + # In the case of language, don't want to limit the input to only the + # available facet values as we might get searches from elsewhere with + # other languages. + 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( @@ -451,6 +462,7 @@ def _values_from_request( distributor=distributor, collection_name=collection_name, enabled_facets=enabled, + language=language, # Finland ) @classmethod @@ -484,6 +496,7 @@ def __init__( order, distributor, collection_name, + language, order_ascending=None, enabled_facets=None, entrypoint=None, @@ -536,6 +549,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: @@ -551,6 +567,7 @@ def navigate( entrypoint=None, distributor=None, collection_name=None, + language=None, ): """Create a slightly different Facets object from this one.""" return self.__class__( @@ -560,6 +577,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, @@ -577,6 +595,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): @@ -595,6 +615,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, []) @@ -606,6 +627,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) @@ -625,6 +647,7 @@ def facet_groups(self): collection_facets, distributor_facets, collection_name_facets, + language_facets, ) = self.enabled_facets def dy(new_value): @@ -674,6 +697,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. @@ -692,6 +722,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) @@ -973,6 +1007,7 @@ def __init__(self, **kwargs): kwargs.setdefault("availability", None) kwargs.setdefault("distributor", None) kwargs.setdefault("collection_name", None) + kwargs.setdefault("language", None) order = kwargs.setdefault("order", None) if order in (None, self.ORDER_BY_RELEVANCE): @@ -2935,6 +2970,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) diff --git a/tests/api/controller/test_work.py b/tests/api/controller/test_work.py index 70796b177..884a384d2 100644 --- a/tests/api/controller/test_work.py +++ b/tests/api/controller/test_work.py @@ -23,6 +23,7 @@ from core.classifier import Classifier from core.entrypoint import AudiobooksEntryPoint from core.external_search import SortKeyPagination, mock_search_index +from core.facets import FacetConstants from core.feed.acquisition import OPDSAcquisitionFeed from core.feed.annotator.circulation import LibraryAnnotator from core.feed.types import WorkEntry @@ -139,7 +140,7 @@ def test_contributor(self, work_fixture: WorkFixture): facet_links = [ link for link in links if link["rel"] == "http://opds-spec.org/facet" ] - assert 10 == len(facet_links) + assert 10 + len(FacetConstants.LANGUAGE_FACETS) == len(facet_links) # At this point we don't want to generate real feeds anymore. # We can't do a real end-to-end test without setting up a real @@ -905,7 +906,7 @@ def test_series(self, work_fixture: WorkFixture): facet_links = [ link for link in links if link["rel"] == "http://opds-spec.org/facet" ] - assert 11 == len(facet_links) + assert 11 + len(FacetConstants.LANGUAGE_FACETS) == len(facet_links) # The facet link we care most about is the default sort order, # put into place by SeriesFacets. diff --git a/tests/api/feed/test_opds_acquisition_feed.py b/tests/api/feed/test_opds_acquisition_feed.py index a8231bb82..7fe79458e 100644 --- a/tests/api/feed/test_opds_acquisition_feed.py +++ b/tests/api/feed/test_opds_acquisition_feed.py @@ -1151,7 +1151,7 @@ def run(wl=None, facets=None, pagination=None): # The make_link function that was passed in calls # TestAnnotator.feed_url() when passed an EntryPoint. The # Facets object's other facet groups are propagated in this URL. - first_page_url = "http://wl/?available=all&collection=full&collectionName=All&distributor=All&entrypoint=Book&order=author" + first_page_url = "http://wl/?available=all&collection=full&collectionName=All&distributor=All&entrypoint=Book&language=all&order=author" assert first_page_url == make_link(EbooksEntryPoint) # Pagination information is not propagated through entry point links diff --git a/tests/api/test_lanes.py b/tests/api/test_lanes.py index f4379e50a..1a716ba2e 100644 --- a/tests/api/test_lanes.py +++ b/tests/api/test_lanes.py @@ -918,6 +918,7 @@ def test_default(self, db: DatabaseTransactionFixture): assert facets.collection == CrawlableFacets.COLLECTION_FULL assert facets.availability == CrawlableFacets.AVAILABLE_ALL assert facets.order == CrawlableFacets.ORDER_LAST_UPDATE + assert facets.language == CrawlableFacets.LANGUAGE_ALL assert facets.order_ascending is False [ @@ -926,10 +927,11 @@ def test_default(self, db: DatabaseTransactionFixture): collection, distributor, collectionName, + language, ] = facets.enabled_facets # The default facets are the only ones enabled. - for facet in [order, availability, collection]: + for facet in [order, availability, collection, language]: assert len(facet) == 1 # Except for distributor and collectionName, which have the default diff --git a/tests/core/models/test_library.py b/tests/core/models/test_library.py index 73b16d261..2a6519c76 100644 --- a/tests/core/models/test_library.py +++ b/tests/core/models/test_library.py @@ -187,6 +187,8 @@ def test_explain(self, db: DatabaseTransactionFixture): facets_default_available='all' facets_enabled_collection='['full', 'featured']' facets_default_collection='full' +facets_enabled_language='['all', 'fin', 'swe', 'eng', 'others']' +facets_default_language='all' help_web='http://library.com/support' default_notification_email_address='noreply@thepalaceproject.org' color_scheme='blue' diff --git a/tests/core/test_external_search.py b/tests/core/test_external_search.py index b92d1b17a..8d57cc4dd 100644 --- a/tests/core/test_external_search.py +++ b/tests/core/test_external_search.py @@ -973,6 +973,7 @@ def pages(worklist): order=Facets.ORDER_TITLE, distributor=None, collection_name=None, + language=None, ) pages = [] while pagination: @@ -1178,6 +1179,7 @@ def expect(availability, collection, works): order=Facets.ORDER_TITLE, distributor=None, collection_name=None, + language=None, ) fixture.expect_results(works, None, Filter(facets=facets), ordered=False) @@ -1455,6 +1457,7 @@ def assert_order(sort_field, order, **filter_kwargs): order=sort_field, distributor=None, collection_name=None, + language=None, order_ascending=True, ) expect(order, None, Filter(facets=facets, **filter_kwargs)) @@ -2540,7 +2543,7 @@ def from_facets(*args, **kwargs): return built # When using the 'featured' collection... - built = from_facets(Facets.COLLECTION_FEATURED, None, None, None, None) + built = from_facets(Facets.COLLECTION_FEATURED, None, None, None, None, None) # There is no nested filter. assert [] == built.nested_filter_calls @@ -2556,7 +2559,7 @@ def from_facets(*args, **kwargs): # When using the AVAILABLE_OPEN_ACCESS availability restriction... built = from_facets( - Facets.COLLECTION_FULL, Facets.AVAILABLE_OPEN_ACCESS, None, None, None + Facets.COLLECTION_FULL, Facets.AVAILABLE_OPEN_ACCESS, None, None, None, None ) # An additional nested filter is applied. @@ -2571,7 +2574,7 @@ def from_facets(*args, **kwargs): # When using the AVAILABLE_NOW restriction... built = from_facets( - Facets.COLLECTION_FULL, Facets.AVAILABLE_NOW, None, None, None + Facets.COLLECTION_FULL, Facets.AVAILABLE_NOW, None, None, None, None ) # An additional nested filter is applied. @@ -2598,7 +2601,7 @@ def from_facets(*args, **kwargs): # When using the AVAILABLE_NOT_NOW restriction... built = from_facets( - Facets.COLLECTION_FULL, Facets.AVAILABLE_NOT_NOW, None, None, None + Facets.COLLECTION_FULL, Facets.AVAILABLE_NOT_NOW, None, None, None, None ) # An additional nested filter is applied. @@ -2628,6 +2631,7 @@ def from_facets(*args, **kwargs): None, DataSource.OVERDRIVE, None, + None, ) [datasource_only] = built.nested_filter_calls nested_filter = datasource_only["query"] @@ -2638,7 +2642,12 @@ def from_facets(*args, **kwargs): # Collection Name builds collection = db.default_collection() built = from_facets( - Facets.COLLECTION_FULL, Facets.AVAILABLE_ALL, None, None, collection.name + Facets.COLLECTION_FULL, + Facets.AVAILABLE_ALL, + None, + None, + collection.name, + None, ) [collection_only] = built.nested_filter_calls nested_filter = collection_only["query"] @@ -2665,6 +2674,7 @@ def from_facets(*args, **kwargs): order=Facets.ORDER_AUTHOR, distributor=None, collection_name=None, + language=None, order_ascending=False, ) diff --git a/tests/core/test_lane.py b/tests/core/test_lane.py index 2676883d2..8a52841b5 100644 --- a/tests/core/test_lane.py +++ b/tests/core/test_lane.py @@ -311,12 +311,13 @@ def test_facet_groups(self, db: DatabaseTransactionFixture): Facets.ORDER_TITLE, Facets.DISTRIBUTOR_ALL, Facets.COLLECTION_NAME_ALL, + Facets.LANGUAGE_ALL, ) all_groups = list(facets.facet_groups) # By default, there are 10 facet transitions: two groups of three # and one group of two and 2 datasource groups and 2 for collection names - assert 12 == len(all_groups) + assert 12 + len(FacetConstants.LANGUAGE_FACETS) == len(all_groups) # available=all, collection=full, and order=title are the selected # facets. @@ -326,6 +327,7 @@ def test_facet_groups(self, db: DatabaseTransactionFixture): ("collection", "full"), ("collectionName", "All"), ("distributor", "All"), + ("language", "all"), ("order", "title"), ] == selected @@ -344,7 +346,7 @@ def test_facet_groups(self, db: DatabaseTransactionFixture): self._configure_facets(library, test_enabled_facets, test_default_facets) facets = Facets( - db.default_library(), None, None, Facets.ORDER_TITLE, None, None + db.default_library(), None, None, Facets.ORDER_TITLE, None, None, None ) all_groups = list(facets.facet_groups) # We have disabled almost all the facets, so the list of @@ -357,6 +359,11 @@ def test_facet_groups(self, db: DatabaseTransactionFixture): ["collectionName", db.default_collection().name, False], ["distributor", "All", True], ["distributor", DataSource.AMAZON, False], + ["language", "all", True], + ["language", "eng", False], + ["language", "fin", False], + ["language", "others", False], + ["language", "swe", False], ["order", "title", True], ["order", "work_id", False], ] @@ -379,6 +386,7 @@ def __init__(self, library, **kwargs): order=None, distributor=None, collection_name=None, + language=None, entrypoint=None, ) == facets.kwargs @@ -445,14 +453,14 @@ def test_default_availability( } library = db.default_library() self._configure_facets(library, test_enabled_facets, test_default_facets) - facets = Facets(library, None, None, None, None, None) + facets = Facets(library, None, None, None, None, None, None) assert Facets.AVAILABLE_ALL == facets.availability # However, if the library does not allow holds, we only show # books that are currently available. settings = library_fixture.settings(library) settings.allow_holds = False - facets = Facets(library, None, None, None, None, None) + facets = Facets(library, None, None, None, None, None, None) assert Facets.AVAILABLE_NOW == facets.availability # Unless 'now' is not one of the enabled facets - then we keep @@ -461,7 +469,7 @@ def test_default_availability( Facets.AVAILABLE_ALL ] self._configure_facets(library, test_enabled_facets, test_default_facets) - facets = Facets(library, None, None, None, None, None) + facets = Facets(library, None, None, None, None, None, None) assert Facets.AVAILABLE_ALL == facets.availability def test_facets_can_be_enabled_at_initialization( @@ -487,6 +495,7 @@ def test_facets_can_be_enabled_at_initialization( Facets.ORDER_TITLE, Facets.DISTRIBUTOR_ALL, Facets.COLLECTION_NAME_ALL, + Facets.LANGUAGE_ALL, enabled_facets=enabled_facets, ) all_groups = list(facets.facet_groups) @@ -510,6 +519,7 @@ def test_facets_dont_need_a_library(self): Facets.ORDER_TITLE, Facets.DISTRIBUTOR_ALL, Facets.COLLECTION_NAME_ALL, + Facets.LANGUAGE_ALL, enabled_facets=enabled_facets, ) all_groups = list(facets.facet_groups) @@ -527,6 +537,7 @@ def test_items(self, db: DatabaseTransactionFixture): Facets.ORDER_TITLE, Facets.DISTRIBUTOR_ALL, Facets.COLLECTION_NAME_ALL, + Facets.LANGUAGE_ALL, entrypoint=AudiobooksEntryPoint, ) assert [ @@ -535,6 +546,7 @@ def test_items(self, db: DatabaseTransactionFixture): ("collectionName", Facets.COLLECTION_NAME_ALL), ("distributor", Facets.DISTRIBUTOR_ALL), ("entrypoint", AudiobooksEntryPoint.INTERNAL_NAME), + ("language", Facets.LANGUAGE_ALL), ("order", Facets.ORDER_TITLE), ] == sorted(facets.items()) @@ -548,6 +560,7 @@ def test_default_order_ascending(self, db: DatabaseTransactionFixture): order=order, distributor=Facets.DISTRIBUTOR_ALL, collection_name=Facets.COLLECTION_NAME_ALL, + language=Facets.LANGUAGE_ALL, ) assert True == f.order_ascending @@ -564,6 +577,7 @@ def test_default_order_ascending(self, db: DatabaseTransactionFixture): order=order, distributor=Facets.DISTRIBUTOR_ALL, collection_name=Facets.COLLECTION_NAME_ALL, + language=Facets.LANGUAGE_ALL, ) assert False == f.order_ascending @@ -581,6 +595,7 @@ def test_navigate(self, db: DatabaseTransactionFixture): F.ORDER_TITLE, Facets.DISTRIBUTOR_ALL, Facets.COLLECTION_NAME_ALL, + Facets.LANGUAGE_ALL, entrypoint=ebooks, ) @@ -724,6 +739,7 @@ class Mock(Facets): collection=[Facets.COLLECTION_FULL], distributor=[Facets.DISTRIBUTOR_ALL], collectionName=[Facets.COLLECTION_NAME_ALL], + language=[Facets.LANGUAGE_ALL], ) @classmethod @@ -745,6 +761,7 @@ def default_facet(cls, config, facet_group_name): collection, distributor, collection_name, + language, ) = Mock.available_facets_calls # available_facets was called three times, to ask the Mock class what it thinks # the options for order, availability, and collection should be. @@ -753,6 +770,7 @@ def default_facet(cls, config, facet_group_name): assert (library, "collection") == collection assert (library, "distributor") == distributor assert (library, "collectionName") == collection_name + assert (library, "language") == language # default_facet was called three times, to ask the Mock class what it thinks # the default order, availability, and collection should be. @@ -762,12 +780,14 @@ def default_facet(cls, config, facet_group_name): collection_d, distributor_d, collection_name_d, + language_d, ) = Mock.default_facet_calls assert (library, "order") == order_d assert (library, "available") == available_d assert (library, "collection") == collection_d assert (library, "distributor") == distributor_d assert (library, "collectionName") == collection_name_d + assert (library, "language") == language_d # Finally, verify that the return values from the mocked methods were actually used. @@ -781,6 +801,7 @@ def default_facet(cls, config, facet_group_name): assert Facets.COLLECTION_FULL == result.collection assert Facets.DISTRIBUTOR_ALL == result.distributor assert Facets.COLLECTION_NAME_ALL == result.collection_name + assert Facets.LANGUAGE_ALL == result.language def test_modify_search_filter(self, db: DatabaseTransactionFixture): # Test superclass behavior -- filter is modified by entrypoint. @@ -791,6 +812,7 @@ def test_modify_search_filter(self, db: DatabaseTransactionFixture): None, None, None, + None, entrypoint=AudiobooksEntryPoint, ) filter = Filter() @@ -805,6 +827,7 @@ def test_modify_search_filter(self, db: DatabaseTransactionFixture): order=Facets.ORDER_ADDED_TO_COLLECTION, distributor=DataSource.OVERDRIVE, collection_name=None, + language=None, order_ascending="yep", ) facets.modify_search_filter(filter) @@ -833,13 +856,21 @@ def test_modify_search_filter(self, db: DatabaseTransactionFixture): # Specifying an invalid sort order doesn't cause a crash, but you # don't get a sort order. - facets = Facets(db.default_library(), None, None, "invalid order", None, None) + facets = Facets( + db.default_library(), None, None, "invalid order", None, None, None + ) filter = Filter() facets.modify_search_filter(filter) assert None == filter.order facets = Facets( - db.default_library(), None, None, None, None, db.default_collection().name + db.default_library(), + None, + None, + None, + None, + db.default_collection().name, + None, ) filter = Filter() facets.modify_search_filter(filter) @@ -885,7 +916,9 @@ def test_modify_database_query(self, db: DatabaseTransactionFixture): ), (Facets.AVAILABLE_NOT_NOW, [not_available]), ]: - facets = Facets(db.default_library(), None, availability, None, None, None) + facets = Facets( + db.default_library(), None, availability, None, None, None, None + ) modified = facets.modify_database_query(db.session, qu) assert (availability, sorted(x.title for x in modified)) == ( availability, @@ -902,7 +935,14 @@ def test_modify_database_query(self, db: DatabaseTransactionFixture): (Facets.COLLECTION_FEATURED, [open_access]), ]: facets = Facets( - db.default_library(), collection, Facets.AVAILABLE_NOW, None, None, None + db.default_library(), + collection, + Facets.AVAILABLE_NOW, + None, + None, + None, + None, + None, ) modified = facets.modify_database_query(db.session, qu) assert (collection, sorted(x.title for x in modified)) == ( @@ -1068,6 +1108,7 @@ def order(facet, ascending=None): order=facet, distributor=None, collection_name=None, + language=None, order_ascending=ascending, ) return f.order_by()[0] @@ -1150,7 +1191,7 @@ def facetify( order=Facets.ORDER_TITLE, ): f = DatabaseBackedFacets( - db.default_library(), collection, available, order, None, None + db.default_library(), collection, available, order, None, None, None ) return f.modify_database_query(db.session, qu) @@ -1462,6 +1503,7 @@ def from_request(**extra): ) facets = from_request(extra="value") + # The SearchFacets implementation uses the order and language values submitted by the admin. assert "author" == facets.order assert ["fre"] == facets.languages @@ -2333,6 +2375,7 @@ def works_for_hits(self, _db, work_ids, facets=None): order=Facets.ORDER_TITLE, distributor=None, collection_name=None, + language=None, ) mock_pagination = object() mock_debug = object() @@ -2885,6 +2928,7 @@ def test_works_from_database_end_to_end(self, db: DatabaseTransactionFixture): order=Facets.ORDER_TITLE, distributor=None, collection_name=None, + language=None, ) pagination = Pagination(offset=1, size=1) assert [oliver_twist] == wl.works_from_database( @@ -4275,6 +4319,7 @@ def test_groups( order=Facets.ORDER_TITLE, distributor=None, collection_name=None, + language=None, ) for lane in [ fiction,