Skip to content

Commit

Permalink
book sponsorship eligibility check (#2328)
Browse files Browse the repository at this point in the history
- calls out to 3rd party library for eligibility check, gracefully falls bacl
- enables sponsorship when not logged in, when ?sponsorship=true params present
- adds a /sponsorship/eligibility/{isbn} json endpoint for archive.org/donate
- DRYs archive.org url, moves to config
  • Loading branch information
mekarpeles committed Aug 21, 2019
1 parent 40291e0 commit fe5e26e
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 25 deletions.
4 changes: 3 additions & 1 deletion openlibrary/core/lending.py
Expand Up @@ -47,6 +47,7 @@
config_ia_availability_api_v1_url = None
config_ia_availability_api_v2_url = None
config_ia_access_secret = None
config_ia_domain = None
config_ia_ol_shared_key = None
config_ia_ol_xauth_s3 = None
config_ia_s3_auth_url = None
Expand Down Expand Up @@ -74,10 +75,11 @@ def setup(config):
config_ia_ol_metadata_write_s3, config_ia_xauth_api_url, \
config_http_request_timeout, config_ia_s3_auth_url, \
config_ia_users_loan_history, config_ia_loan_api_developer_key, \
config_ia_civicrm_api
config_ia_civicrm_api, config_ia_domain

config_loanstatus_url = config.get('loanstatus_url')
config_bookreader_host = config.get('bookreader_host', 'archive.org')
config_ia_domain = config.get('ia_base_url', 'https://archive.org')
config_ia_loan_api_url = config.get('ia_loan_api_url')
config_ia_availability_api_v1_url = config.get('ia_availability_api_v1_url')
config_ia_availability_api_v2_url = config.get('ia_availability_api_v2_url')
Expand Down
26 changes: 26 additions & 0 deletions openlibrary/core/models.py
Expand Up @@ -18,6 +18,8 @@
from openlibrary.core.helpers import private_collection_in
from openlibrary.core.bookshelves import Bookshelves
from openlibrary.core.ratings import Ratings
from openlibrary.utils.isbn import to_isbn_13, isbn_13_to_isbn_10
from openlibrary.core.vendors import create_edition_from_amazon_metadata

# relative imports
from lists.model import ListMixin, Seed
Expand Down Expand Up @@ -369,6 +371,30 @@ def get_ia_download_link(self, suffix):
if filename:
return "https://archive.org/download/%s/%s" % (self.ocaid, filename)

@classmethod
def get_by_isbn(cls, isbn):
"""Attempts to fetch an edition by isbn, or if no edition is found,
attempts to import from amazon
"""
isbn13 = to_isbn_13(isbn)
isbn10 = isbn_13_to_isbn_10(isbn)

# Attempt to fetch book from OL
for isbn in [isbn13, isbn10]:
if isbn:
matches = web.ctx.site.things({
"type": "/type/edition", 'isbn_%s' % len(isbn): isbn
})
if matches:
return web.ctx.site.get(matches[0])

# Attempt to create from amazon, then fetch from OL
key = next(
create_edition_from_amazon_metadata(isbn)
for isbn in [isbn13, isbn10])
if key:
return web.ctx.site.get(key)

def is_ia_scan(self):
metadata = self.get_ia_meta_fields()
# all IA scans will have scanningcenter field set
Expand Down
92 changes: 73 additions & 19 deletions openlibrary/core/sponsorships.py
@@ -1,18 +1,32 @@
import json
import urllib
import requests
import web
from infogami import config
from infogami.utils.view import public
from openlibrary import accounts
from openlibrary.core.lending import get_work_availability
from openlibrary.core.vendors import get_betterworldbooks_metadata
from openlibrary.core import models
from openlibrary.core.lending import get_work_availability, config_ia_domain
from openlibrary.core.vendors import (
get_betterworldbooks_metadata, create_edition_from_amazon_metadata)
from openlibrary.accounts import get_internet_archive_id
from openlibrary.core.lending import config_ia_civicrm_api
from openlibrary.utils.isbn import to_isbn_13

try:
from booklending_utils.sponsorship import eligibility_check
except ImportError:
def eligibility_check(edition):
"""For testing if Internet Archive book sponsorship check unavailable"""
work = edition.works[0]
authors = [w.author.name for w in work.authors]
if authors:
return True

CIVI_ISBN = 'custom_52'
CIVI_USERNAME = 'custom_51'
CIVI_CONTEXT = 'custom_53'
PRICE_LIMIT = 50.00
PRICE_LIMIT_CENTS = 5000


def get_sponsored_editions(user):
Expand Down Expand Up @@ -78,27 +92,67 @@ def get_sponsorships_by_contact_id(contact_id, isbn=None):
} for t in txs]


def isbn_qualifies_for_sponsorship(isbn):
"""Checks possible isbn10 + isbn13 variations to"""
edition = models.Edition.get_by_isbn(isbn)
if edition:
return qualifies_for_sponsorship(edition)

@public
def qualifies_for_sponsorship(edition):
# User must be logged in and in /usergroup/sponsors list
# defaults
dwhi = None
eligibility = False
price = None

work = edition.works and edition.works[0]
edition.isbn13 = to_isbn_13(edition.isbn_13 and edition.isbn_13[0] or
edition.isbn_10 and edition.isbn_10[0])
req_fields = all(edition.get(x) for x in [
'publishers', 'title', 'publish_date', 'covers', 'number_of_pages'
'publishers', 'title', 'publish_date', 'covers',
'number_of_pages', 'isbn13'
])
isbn = (edition.isbn_13 and edition.isbn_13[0] or
edition.isbn_10 and edition.isbn_10[0])
if work and req_fields and isbn:
work_id = work.key.split("/")[-1]
num_pages = int(edition.get('number_of_pages'))
availability = get_work_availability(work_id)
dwwi = availability.get(work_id, {}).get('status', 'error') == 'error'
if dwwi:
bwb_price = get_betterworldbooks_metadata(isbn).get('price_amt')
if bwb_price:
scan_price = 3.0 + (.12 * num_pages)
total_price = scan_price + float(bwb_price)
return total_price <= PRICE_LIMIT
return False
if not (work and req_fields and edition.isbn13):
return {
'do_we_have_it': dwhi,
'is_eligibile': eligibility,
'price': price,
'error': ("Open Library is missing book metadata "
"necessary for sponsorship"),
}

work_id = work.key.split("/")[-1]
num_pages = int(edition.get('number_of_pages'))
availability = get_work_availability(work_id)
dwhi = (availability.get(work_id, {}).get('status', 'error') != 'error') and availability
if not dwhi:
bwb_price = get_betterworldbooks_metadata(
edition.isbn13).get('price_amt')
if bwb_price:
SETUP_COST_CENTS = 300
PAGE_COST_CENTS = 12
scan_price_cents = SETUP_COST_CENTS + (PAGE_COST_CENTS * num_pages)
book_cost_cents = int(float(bwb_price) * 100)
total_price_cents = scan_price_cents + book_cost_cents
price = {
'book_cost_cents': book_cost_cents,
'scan_price_cents': scan_price_cents,
'total_price_cents': total_price_cents
}
if total_price_cents <= PRICE_LIMIT_CENTS:
eligibility = eligibility_check(edition)
params = {
'campaign': 'pilot',
'type': 'sponsorship',
'context': 'ol',
'isbn': edition.isbn13
}
return {
'is_eligible': eligibility,
'do_we_have_it': dwhi,
'price': price,
'url': config_ia_domain + '/donate?' + urllib.urlencode(params)
}


def get_all_sponsors():
Expand Down
10 changes: 5 additions & 5 deletions openlibrary/macros/LoanStatus.html
Expand Up @@ -81,17 +81,17 @@
<a href="$viewbook.replace('XXX', page.ocaid)" title="Use BookReader to read online" class="cta-btn cta-btn--available">Read</a>

$elif (not page.get('ocaid') or page.is_access_restricted()) and editions_page:
$if user and (input(sponsorship=None) or user.is_sponsor()) and qualifies_for_sponsorship(page):
$ sponsorship = qualifies_for_sponsorship(page)
$if (input(sponsorship=None) or user and user.is_sponsor()) and sponsorship.get('is_eligible'):
$ isbn = (page.isbn_13 and page.isbn_13[0] or page.isbn_10 and page.isbn_10[0])
$ user_id = user.key.split("/")[-1]
$ donate_url = "https://archive.org/donate?context=ol&userid=%s&campaign=pilot&type=book-sponsorship&isbn=%s" % (user_id, isbn)
<a href="$donate_url"
$ user_id = user.key.split("/")[-1] if user else ''
<a href="$(sponsorship['url'])&userid=$(user_id)"
class="cta-btn cta-btn--sponsor"
data-ol-link-track="book-sponsorship">Sponsor eBook</a>
<p>
We don’t have this book yet. You can add it to our Lending
Library with a \$50 tax deductible donation.
<a href="$donate_url">Learn More</a>
<a href="$$donate_url">Learn More</a>
</p>
$else:
<form>
Expand Down
8 changes: 8 additions & 0 deletions openlibrary/plugins/openlibrary/api.py
Expand Up @@ -15,6 +15,7 @@
from openlibrary.utils import extract_numeric_id_from_olid
from openlibrary.plugins.worksearch.subjects import get_subject
from openlibrary.core import ia, db, models, lending, helpers as h
from openlibrary.core.sponsorships import isbn_qualifies_for_sponsorship
from openlibrary.core.vendors import (
get_amazon_metadata, create_edition_from_amazon_metadata,
get_betterworldbooks_metadata)
Expand Down Expand Up @@ -242,6 +243,13 @@ def get_works_data(self, author, limit, offset):
"entries": works
}

class sponsorship_eligibility_check(delegate.page):
path = r'/sponsorship/eligibility/(.*)'

@jsonapi
def GET(self, isbn):
return simplejson.dumps(isbn_qualifies_for_sponsorship(isbn))

class price_api(delegate.page):
path = r'/prices'

Expand Down
4 changes: 4 additions & 0 deletions openlibrary/utils/isbn.py
Expand Up @@ -47,6 +47,10 @@ def isbn_10_to_isbn_13(isbn_10):
isbn_13 = '978' + isbn_10[:-1]
return isbn_13 + check_digit_13(isbn_13)

def to_isbn_13(isbn):
isbn = normalize_isbn(isbn)
return isbn and (isbn if len(isbn) == 13 else isbn_10_to_isbn_13(isbn))

def opposite_isbn(isbn): # ISBN10 -> ISBN13 and ISBN13 -> ISBN10
for f in isbn_13_to_isbn_10, isbn_10_to_isbn_13:
alt = f(canonical(isbn))
Expand Down

0 comments on commit fe5e26e

Please sign in to comment.