diff --git a/api/authenticator.py b/api/authenticator.py index 8c17f10fb..0925d735c 100644 --- a/api/authenticator.py +++ b/api/authenticator.py @@ -2703,6 +2703,7 @@ def oauth_authentication_callback(self, _db, params): patron_info=patron_info, root_lane=root_lane, is_new=patrondata.is_new, + age_group=patron.external_type, ) return redirect(client_redirect_uri + "#" + urllib.parse.urlencode(params)) @@ -2828,6 +2829,7 @@ def basic_auth_temp_token(self, params, _db): expires_in=BasicAuthTempTokenController.TOKEN_DURATION.seconds, root_lane=root_lane, is_new=is_new, + age_group=patron.external_type, ) return flask.jsonify(data) diff --git a/core/facets.py b/core/facets.py index 343d768f0..b2b2be410 100644 --- a/core/facets.py +++ b/core/facets.py @@ -42,6 +42,7 @@ class FacetConstants(object): ORDER_SERIES_POSITION = 'series' ORDER_WORK_ID = 'work_id' ORDER_RANDOM = 'random' + ORDER_RELEVANCE = 'relevance' # Some order facets, like series and work id, # only make sense in certain contexts. # These are the options that can be enabled @@ -51,6 +52,7 @@ class FacetConstants(object): ORDER_AUTHOR, ORDER_ADDED_TO_COLLECTION, ORDER_RANDOM, + ORDER_RELEVANCE, ] ORDER_ASCENDING = "asc" @@ -88,6 +90,7 @@ class FacetConstants(object): ORDER_SERIES_POSITION: _('Series Position'), ORDER_WORK_ID : _('Work ID'), ORDER_RANDOM : _('Random'), + ORDER_RELEVANCE : _('Relevance'), AVAILABLE_NOW : _("Available now"), AVAILABLE_ALL : _("All"), diff --git a/core/opds.py b/core/opds.py index 475ef35bb..9dec1c0eb 100644 --- a/core/opds.py +++ b/core/opds.py @@ -1,6 +1,6 @@ import datetime import logging -from urllib.parse import quote +from urllib.parse import quote, parse_qsl from collections import ( defaultdict, ) @@ -1019,9 +1019,27 @@ def make_link(ep): # Add "up" link. AcquisitionFeed.add_link_to_feed(feed=opds_feed.feed, rel="up", href=annotator.lane_url(lane), title=str(lane.display_name)) - # Add URLs to change faceted views - for args in cls.facet_links(annotator, facets): - AcquisitionFeed.add_link_to_feed(feed=opds_feed.feed, **args) + # Add URLs to change enabled faceted views + enabled_order_facets = list(facets.enabled_facets)[0] + all_order_facets = filter( + lambda facet: "Sort by" in facet.values(), + cls.facet_links(annotator, facets) + ) + original_facet = facets.order + for facet in all_order_facets: + order = dict(parse_qsl(facet["href"])).get('order') + if order in enabled_order_facets: + facets.order = order + if order == original_facet: + facet = cls.facet_link(href=facet["href"], title=facet["title"], facet_group_name="Sort by", is_active=True) + else: + facet = cls.facet_link(href=facet["href"], title=facet["title"], facet_group_name="Sort by", is_active=False) + # cls.facet_links generates a /feed/ url, but we want to use + # search_url to generate a /search/ url. We also want to generate + # the facet url for this given ordering facet instead of the original facet. + facet["href"] = annotator.search_url(lane, query, pagination=None, facets=facets) + AcquisitionFeed.add_link_to_feed(feed=opds_feed.feed, **facet) + facets.order = original_facet # We do not add breadcrumbs to this feed since you're not # technically searching the this lane; you are searching the diff --git a/core/tests/test_facets.py b/core/tests/test_facets.py index 062e47f63..2d01d9ccc 100644 --- a/core/tests/test_facets.py +++ b/core/tests/test_facets.py @@ -38,3 +38,6 @@ def test_enable_facet(self): config.enable_facet(order_by, Facets.ORDER_RANDOM) assert Facets.ORDER_RANDOM in config.enabled_facets(order_by) assert config.default_facet(order_by) != Facets.ORDER_RANDOM + + config.enable_facet(order_by, Facets.ORDER_RELEVANCE) + assert Facets.ORDER_RELEVANCE in config.enabled_facets(order_by) diff --git a/core/tests/test_opds.py b/core/tests/test_opds.py index 7e1f98e08..5cbaa3be2 100644 --- a/core/tests/test_opds.py +++ b/core/tests/test_opds.py @@ -1296,6 +1296,9 @@ def make_page(pagination): assert 'Title' in sort_by_facets assert 'Author' in sort_by_facets + [active_facet] = [facet['title'] for facet in facets if getattr(facet, "activefacet", False) == 'true'] + assert active_facet == 'Author' + # The feed has no breadcrumb links, since we're not # searching the lane -- just using some aspects of the lane # to guide the search. diff --git a/docker/gunicorn.conf.py b/docker/gunicorn.conf.py index 431b453a3..97fbb4ec8 100644 --- a/docker/gunicorn.conf.py +++ b/docker/gunicorn.conf.py @@ -21,6 +21,8 @@ bind = ["127.0.0.1:8000"] # listen on 8000, only on the loopback address workers = (2 * multiprocessing.cpu_count()) + 1 threads = 2 +max_requests = 500 +max_requests_jitter = 50 pythonpath = ",".join([ str(VENV_ACTUAL), SIMPLIFIED_HOME, diff --git a/tests/test_authenticator.py b/tests/test_authenticator.py index 46debeae4..201639ca5 100644 --- a/tests/test_authenticator.py +++ b/tests/test_authenticator.py @@ -2474,6 +2474,7 @@ def test_authentication_creates_missing_patron(self): permanent_id=self._str, authorization_identifier=self._str, fines=Money(1, "USD"), + external_type='H', ) library = self._library() @@ -2490,6 +2491,7 @@ def test_authentication_creates_missing_patron(self): assert (patrondata.authorization_identifier == patron.authorization_identifier) assert provider.patron_is_new is True + assert patron.external_type == patrondata.external_type # Information not relevant to the patron's identity was stored # in the Patron object after it was created. @@ -2793,6 +2795,7 @@ def __init__(self, library, _db, external_authenticate_url, patron, root_lane=No self.patrondata = PatronData(personal_name="Abcd", is_new=is_new) self.root_lane = root_lane self.is_new = self.patrondata.is_new + self.age_group = self.patron.external_type def external_authenticate_url(self, state, _db): return self.url + "?state=" + state @@ -2878,6 +2881,7 @@ def test_oauth_authentication_callback(self): assert self.oauth1.token.credential == provider_token assert str(self.oauth1.root_lane) in fragments.get('root_lane')[0] assert str(self.oauth1.is_new) == fragments.get('is_new')[0] + assert str(self.oauth1.age_group) == fragments.get('age_group')[0] # Successful callback through OAuth provider 2. params = dict(code="foo", state=json.dumps(dict(provider=self.oauth2.NAME)))