From 9bd4a80ba182c574c27580a70495338adfe7b8b4 Mon Sep 17 00:00:00 2001 From: Michael Benowitz Date: Tue, 7 Nov 2023 09:06:20 -0500 Subject: [PATCH 1/7] SMA-523 Add since/until params for open sources materials In order to allow libraries to set an expected expiration date for open source materials this adds `opds:since` and `opds:until` to the availability object for these materials. This leverages functionality that is already used for loans to create a "loan-like" behavior for these materials (when desired). This should have no side effects since it's leveraging existing attributes but this should be tested to be true. --- api/opds.py | 4 +++- core/opds.py | 14 ++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/api/opds.py b/api/opds.py index eb7c6fb4f..0f893e510 100644 --- a/api/opds.py +++ b/api/opds.py @@ -1,4 +1,5 @@ import urllib.request, urllib.parse, urllib.error +from datetime import timedelta, datetime import copy import logging from flask import url_for @@ -425,6 +426,7 @@ def open_access_link(self, pool, lpdm): always_available = OPDSFeed.makeelement( "{%s}availability" % OPDSFeed.OPDS_NS, status="available" ) + link_tag.append(always_available) return link_tag @@ -1130,7 +1132,7 @@ def fulfill_link(self, license_pool, active_loan, delivery_mechanism, active_loan=active_loan ) - children = AcquisitionFeed.license_tags(license_pool, active_loan, None) + children = AcquisitionFeed.license_tags(license_pool, active_loan, None, rel=rel, library=self.library) link_tag.extend(children) children = self.drm_device_registration_tags( diff --git a/core/opds.py b/core/opds.py index 9dec1c0eb..d842d2ae8 100644 --- a/core/opds.py +++ b/core/opds.py @@ -1650,7 +1650,7 @@ def indirect_acquisition(cls, indirect_types): return top_level_parent @classmethod - def license_tags(cls, license_pool, loan, hold): + def license_tags(cls, license_pool, loan, hold, rel=None, library=None): # Generate a list of licensing tags. These should be inserted # into a tag. tags = [] @@ -1688,10 +1688,16 @@ def license_tags(cls, license_pool, loan, hold): else: status = 'reserved' since = hold.start - elif (license_pool.open_access or license_pool.unlimited_access or license_pool.self_hosted or ( + elif (license_pool.open_access or rel == OPDSFeed.OPEN_ACCESS_REL): + status = 'available' + + default_loan_period = collection.default_loan_period(library) if library else collection.STANDARD_DEFAULT_LOAN_PERIOD + + since = license_pool.availability_time + until = datetime.datetime.utcnow() + datetime.timedelta(days=default_loan_period) + elif (license_pool.unlimited_access or license_pool.self_hosted or ( license_pool.licenses_available > 0 and - license_pool.licenses_owned > 0) - ): + license_pool.licenses_owned > 0)): status = 'available' else: status='unavailable' From cea05a4328da5f836aa5c55986e3e94cfaf68fa5 Mon Sep 17 00:00:00 2001 From: Michael Benowitz Date: Mon, 13 Nov 2023 17:50:16 -0500 Subject: [PATCH 2/7] SMA-523 Add unit test --- core/tests/test_opds.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/core/tests/test_opds.py b/core/tests/test_opds.py index 5cbaa3be2..4c5455eec 100644 --- a/core/tests/test_opds.py +++ b/core/tests/test_opds.py @@ -1625,6 +1625,33 @@ def test_license_tags_show_self_hosted_books(self): assert 'status' in tags[0].attrib assert 'available' == tags[0].attrib['status'] + def test_license_tags_open_access(self): + # Arrange + edition, pool = self._edition(with_license_pool=True) + pool.open_access = True + pool.self_hosted = False + pool.unlimited_access = False + creation_time = datetime.datetime.utcnow() + + # Act + tags = AcquisitionFeed.license_tags( + pool, None, None + ) + + # Assert + assert 1 == len(tags) + + [tag] = tags + + assert ('status' in tag.attrib) == True + assert 'available' == tag.attrib['status'] + assert 'since' in tag.attrib + assert tag.attrib['since'] == pool.availability_time.strftime('%Y-%m-%dT%H:%M:%S+00:00') + assert 'until' in tag.attrib + assert tag.attrib['until'] == (creation_time + datetime.timedelta(days=21)).strftime('%Y-%m-%dT%H:%M:%SZ') + assert ('holds' in tag.attrib) == False + assert ('copies' in tag.attrib) == False + def test_single_entry(self): # Here's a Work with two LicensePools. From d8033d869527ba246c2ef02cb3ad587af9c6ee25 Mon Sep 17 00:00:00 2001 From: Michael Benowitz Date: Mon, 13 Nov 2023 18:04:15 -0500 Subject: [PATCH 3/7] NOREF Add New NCES IDs These IDs correspond to Title I schools which should be eligible for the Open eBooks project --- api/clever/title_i.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/clever/title_i.json b/api/clever/title_i.json index c1515179b..99d9de733 100644 --- a/api/clever/title_i.json +++ b/api/clever/title_i.json @@ -8681,6 +8681,9 @@ "063432013803", "063432013878", "063432013916", + "063432014499", + "063432014500", + "063432014617", "063441002774", "063441005369", "063441005585", From d30c7d014744e30e47d65c7b29796d6783c6ee07 Mon Sep 17 00:00:00 2001 From: Michael Benowitz Date: Mon, 13 Nov 2023 18:06:49 -0500 Subject: [PATCH 4/7] NOREF Migrate 4th Grade from Middle to Early for Clever --- api/clever/__init__.py | 2 +- tests/clever/test_clever.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/clever/__init__.py b/api/clever/__init__.py index 93a67c400..f69b7cf98 100644 --- a/api/clever/__init__.py +++ b/api/clever/__init__.py @@ -57,7 +57,7 @@ "1": "E", "2": "E", "3": "E", - "4": "M", # Middle + "4": "E", # Middle "5": "M", "6": "M", "7": "M", diff --git a/tests/clever/test_clever.py b/tests/clever/test_clever.py index 2a8e6cf02..691cd17b0 100644 --- a/tests/clever/test_clever.py +++ b/tests/clever/test_clever.py @@ -46,11 +46,11 @@ def test_external_type_from_clever_grade(self): THEN: The matching external_type value should be returned, or None if the match fails """ for e_grade in [ - "InfantToddler", "Preschool", "PreKindergarten", "TransitionalKindergarten", "Kindergarten", "1", "2", "3" + "InfantToddler", "Preschool", "PreKindergarten", "TransitionalKindergarten", "Kindergarten", "1", "2", "3", "4" ]: assert external_type_from_clever_grade(e_grade) == "E" - for m_grade in ["4", "5", "6", "7", "8"]: + for m_grade in ["5", "6", "7", "8"]: assert external_type_from_clever_grade(m_grade) == "M" for h_grade in ["9", "10", "11", "12", "13", "PostGraduate"]: From b5796925a0c47533b55687935a3d30fecfdecd57 Mon Sep 17 00:00:00 2001 From: Michael Benowitz Date: Thu, 16 Nov 2023 16:52:35 -0500 Subject: [PATCH 5/7] SMA-523 Remove unused import --- api/opds.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/opds.py b/api/opds.py index 0f893e510..9261217a6 100644 --- a/api/opds.py +++ b/api/opds.py @@ -1,5 +1,4 @@ import urllib.request, urllib.parse, urllib.error -from datetime import timedelta, datetime import copy import logging from flask import url_for From dcd95c31d515318201b5f9ce851a2e7ee9a78e28 Mon Sep 17 00:00:00 2001 From: Michael Benowitz Date: Tue, 5 Dec 2023 17:55:12 -0500 Subject: [PATCH 6/7] NOREF Move 5th grade to early grades category. --- api/clever/__init__.py | 3 +-- tests/clever/test_clever.py | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/clever/__init__.py b/api/clever/__init__.py index f69b7cf98..87b4ed9a9 100644 --- a/api/clever/__init__.py +++ b/api/clever/__init__.py @@ -58,7 +58,7 @@ "2": "E", "3": "E", "4": "E", # Middle - "5": "M", + "5": "E", "6": "M", "7": "M", "8": "M", @@ -72,7 +72,6 @@ "Ungraded": None, } - def external_type_from_clever_grade(grade): """Maps a 'grade' value returned by the Clever API for student users to an external_type""" return CLEVER_GRADE_TO_EXTERNAL_TYPE_MAP.get(grade, None) diff --git a/tests/clever/test_clever.py b/tests/clever/test_clever.py index 691cd17b0..c5dd4f349 100644 --- a/tests/clever/test_clever.py +++ b/tests/clever/test_clever.py @@ -46,11 +46,12 @@ def test_external_type_from_clever_grade(self): THEN: The matching external_type value should be returned, or None if the match fails """ for e_grade in [ - "InfantToddler", "Preschool", "PreKindergarten", "TransitionalKindergarten", "Kindergarten", "1", "2", "3", "4" + "InfantToddler", "Preschool", "PreKindergarten", "TransitionalKindergarten", + "Kindergarten", "1", "2", "3", "4", "5" ]: assert external_type_from_clever_grade(e_grade) == "E" - for m_grade in ["5", "6", "7", "8"]: + for m_grade in ["6", "7", "8"]: assert external_type_from_clever_grade(m_grade) == "M" for h_grade in ["9", "10", "11", "12", "13", "PostGraduate"]: From 509e1cb2776e469691832ee8897bd73a8c5eb7b0 Mon Sep 17 00:00:00 2001 From: Michael Benowitz Date: Tue, 5 Dec 2023 17:57:09 -0500 Subject: [PATCH 7/7] NOREF Update Comment --- api/clever/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/clever/__init__.py b/api/clever/__init__.py index 87b4ed9a9..fe60dabb5 100644 --- a/api/clever/__init__.py +++ b/api/clever/__init__.py @@ -57,9 +57,9 @@ "1": "E", "2": "E", "3": "E", - "4": "E", # Middle + "4": "E", "5": "E", - "6": "M", + "6": "M", # Middle "7": "M", "8": "M", "9": "H", # High