def get_loan(identifier, user_key=None): """Returns the loan object for given identifier, if a loan exists. If user_key is specified, it returns the loan only if that user is borrowed that book. """ _loan = None account = None if user_key: if user_key.startswith('@'): account = OpenLibraryAccount.get(link=user_key) else: account = OpenLibraryAccount.get(key=user_key) d = web.ctx.site.store.get("loan-" + identifier) if d and (user_key is None or (d['user'] == account.username) or \ (d['user'] == account.itemname)): loan = Loan(d) if loan.is_expired(): return loan.delete() try: _loan = _get_ia_loan(identifier, account and userkey2userid(account.username)) except Exception as e: pass try: _loan = _get_ia_loan(identifier, account and account.itemname) except Exception as e: pass return _loan
def new(cls, **kw): user_key = kw['user_key'] itemname = kw.get('itemname', '') if not itemname: account = OpenLibraryAccount.get(key=user_key) itemname = account.itemname _wl_api.join_waitinglist(kw['identifier'], itemname) return cls.find(user_key, kw['identifier'], itemname=itemname)
def get_waitinglist_for_user(user_key): """Returns the list of records for all the books that a user is waiting for.""" waitlist = [] account = OpenLibraryAccount.get(key=user_key) if account.itemname: waitlist.extend(WaitingLoan.query(userid=account.itemname)) waitlist.extend(WaitingLoan.query(userid=lending.userkey2userid(user_key))) return waitlist
def get_loans_of_user(user_key): """TODO: Remove inclusion of local data; should only come from IA""" account = OpenLibraryAccount.get(username=user_key.split('/')[-1]) loandata = web.ctx.site.store.values(type='/type/loan', name='user', value=user_key) loans = [Loan(d) for d in loandata] + (_get_ia_loans_of_user(account.itemname) + _get_ia_loans_of_user(userkey2userid(user_key))) return loans
def get_waitinglist_for_user(user_key): """Returns the list of records for all the books that a user is waiting for. """ waitlist = [] account = OpenLibraryAccount.get(key=user_key) if account.itemname: waitlist.extend(WaitingLoan.query(userid=account.itemname)) waitlist.extend(WaitingLoan.query(userid=lending.userkey2userid(user_key))) return waitlist
def POST(self): user = accounts.get_current_user() if user: account = OpenLibraryAccount.get_by_email(user.email) s3_keys = web.ctx.site.store.get(account._key).get('s3_keys') if s3_keys: response = post_observation(web.data(), s3_keys) return delegate.RawText(response)
def find(cls, user_key, identifier, itemname=None): """Returns the waitingloan for given book_key and user_key. Returns None if there is no such waiting loan. """ if not itemname: account = OpenLibraryAccount.get(key=user_key) itemname = account.itemname result = cls.query(userid=itemname, identifier=identifier) if result: return result[0]
def find(cls, user_key, identifier, itemname=None): """Returns the waitingloan for given book_key and user_key. Returns None if there is no such waiting loan. """ if not itemname: account = OpenLibraryAccount.get(key=user_key) itemname = account.itemname result = (cls.query(userid=itemname, identifier=identifier) or cls.query(userid=lending.userkey2userid(user_key), identifier=identifier)) if result: return result[0]
def get_user_key(self): user_key = self.get("user_key") if user_key: return user_key userid = self.get("userid") username = "" if userid.startswith('@'): account = OpenLibraryAccount.get(link=userid) username = account.username elif userid.startswith('ol:'): username = userid[len("ol:"):] return "/people/%s" % username
def delete(self): loan = dict(self, returned_at=time.time()) user_key = self['user'] account = OpenLibraryAccount.get(key=user_key) if self.get("stored_at") == 'ia': ia_lending_api.delete_loan(self['ocaid'], userkey2userid(user_key)) if account.itemname: ia_lending_api.delete_loan(self['ocaid'], account.itemname) else: web.ctx.site.store.delete(self['_key']) sync_loan(self['ocaid']) # Inform listers that a loan is completed msgbroker.send_message("loan-completed", loan)
def GET(self): user = accounts.get_current_user() if user: account = OpenLibraryAccount.get_by_email(user.email) ia_itemname = account.itemname if account else None if not user or not ia_itemname: web.setcookie(config.login_cookie_name, "", expires=-1) raise web.seeother("/account/login?redirect=/sponsorship/join") try: with accounts.RunAs('archive_support'): models.UserGroup.from_key('sponsors-waitlist').add_user(user.key) except KeyError as e: add_flash_message('error', 'Unable to join waitlist: %s' % e.message) raise web.seeother('/sponsorship')
def from_ia_loan(data): if data['userid'].startswith('ol:'): user_key = '/people/' + data['userid'][len('ol:'):] elif data['userid'].startswith('@'): account = OpenLibraryAccount.get_by_link(data['userid']) user_key = '/people/' + account.username else: user_key = None if data['ol_key']: book_key = data['ol_key'] else: book_key = resolve_identifier(data['identifier']) created = h.parse_datetime(data['created']) # For historic reasons, OL considers expiry == None as un-fulfilled # loan. if data['fulfilled']: expiry = data['until'] else: expiry = None d = { '_key': "loan-{0}".format(data['identifier']), '_rev': 1, 'type': '/type/loan', 'userid': data['userid'], 'user': user_key, 'book': book_key, 'ocaid': data['identifier'], 'expiry': expiry, 'fulfilled': data['fulfilled'], 'uuid': 'loan-{0}'.format(data['id']), 'loaned_at': time.mktime(created.timetuple()), 'resource_type': data['format'], 'resource_id': data['resource_id'], 'loan_link': data['loan_link'], 'stored_at': 'ia' } return Loan(d)
def from_ia_loan(data): if data['userid'].startswith('ol:'): user_key = '/people/' + data['userid'][len('ol:'):] elif data['userid'].startswith('@'): account = OpenLibraryAccount.get_by_link(data['userid']) user_key = '/people/' + account.username else: user_key = None if data['ol_key']: book_key = data['ol_key'] else: book_key = resolve_identifier(data['identifier']) created = h.parse_datetime(data['created']) # For historic reasons, OL considers expiry == None as un-fulfilled # loan. expiry = data.get('until') d = { '_key': "loan-{0}".format(data['identifier']), '_rev': 1, 'type': '/type/loan', 'userid': data['userid'], 'user': user_key, 'book': book_key, 'ocaid': data['identifier'], 'expiry': expiry, 'fulfilled': data['fulfilled'], 'uuid': 'loan-{0}'.format(data['id']), 'loaned_at': time.mktime(created.timetuple()), 'resource_type': data['format'], 'resource_id': data['resource_id'], 'loan_link': data['loan_link'], 'stored_at': 'ia' } return Loan(d)
def POST(self, key): """Called when the user wants to borrow the edition""" i = web.input(action='borrow', format=None, ol_host=None) if i.ol_host: ol_host = i.ol_host else: ol_host = 'openlibrary.org' edition = web.ctx.site.get(key) if not edition: raise web.notfound() # Make a call to availability v2 update the subjects according # to result if `open`, redirect to bookreader response = lending.get_availability_of_ocaid(edition.ocaid) availability = response[edition.ocaid] if response else {} if availability and availability['status'] == 'open': raise web.seeother('https://archive.org/stream/' + edition.ocaid + '?ref=ol') error_redirect = ('https://archive.org/stream/' + edition.ocaid + '?ref=ol') user = accounts.get_current_user() if user: account = OpenLibraryAccount.get_by_email(user.email) ia_itemname = account.itemname if account else None if not user or not ia_itemname: web.setcookie(config.login_cookie_name, "", expires=-1) raise web.seeother("/account/login?redirect=%s/borrow?action=%s" % (edition.url(), i.action)) action = i.action # Intercept a 'borrow' action if the user has already # borrowed the book and convert to a 'read' action. # Added so that direct bookreader links being routed through # here can use a single action of 'borrow', regardless of # whether the book has been checked out or not. if action == 'borrow' and user.has_borrowed(edition): action = 'read' if action == 'borrow': resource_type = i.format or 'bookreader' if resource_type not in ['epub', 'pdf', 'bookreader']: raise web.seeother(error_redirect) user_meets_borrow_criteria = user_can_borrow_edition( user, edition, resource_type) if user_meets_borrow_criteria: # This must be called before the loan is initiated, # otherwise the user's waitlist status will be cleared # upon loan creation track_loan = False if is_users_turn_to_borrow( user, edition) else True loan = lending.create_loan(identifier=edition.ocaid, resource_type=resource_type, user_key=ia_itemname, book_key=key) if loan: loan_link = loan['loan_link'] if resource_type == 'bookreader': if track_loan: # As of 2017-12-14, Petabox will be # responsible for tracking borrows which # are the result of waitlist redemptions, # so we don't want to track them here to # avoid double accounting. When a reader # is at the head of a waitlist and goes to # claim their loan, Petabox now checks # whether the waitlist was initiated from # OL, and if it was, petabox tracks # ol.loans.bookreader accordingly via # lending.create_loan. stats.increment('ol.loans.bookreader') raise web.seeother( make_bookreader_auth_link( loan.get_key(), edition.ocaid, '/stream/' + edition.ocaid, ol_host)) elif resource_type == 'pdf': stats.increment('ol.loans.pdf') raise web.seeother(loan_link) elif resource_type == 'epub': stats.increment('ol.loans.epub') raise web.seeother(loan_link) else: raise web.seeother(error_redirect) else: raise web.seeother(error_redirect) elif action == 'return': # Check that this user has the loan user.update_loan_status() loans = get_loans(user) # We pick the first loan that the user has for this book that is returnable. # Assumes a user can't borrow multiple formats (resource_type) of the same book. user_loan = None for loan in loans: # Handle the case of multiple edition records for the same # ocaid and the user borrowed from one and returning from another has_loan = (loan['book'] == edition.key or loan['ocaid'] == edition.ocaid) if has_loan: user_loan = loan break if not user_loan: # $$$ add error message raise web.seeother(error_redirect) user_loan.return_loan() # Show the page with "you've returned this". Use a dummy slug. # $$$ this would do better in a session variable that can be cleared # after the message is shown once raise web.seeother(edition.url()) elif action == 'read': # Look for loans for this book user.update_loan_status() loans = get_loans(user) for loan in loans: if loan['book'] == edition.key: raise web.seeother( make_bookreader_auth_link(loan['_key'], edition.ocaid, '/stream/' + edition.ocaid, ol_host)) elif action == 'join-waitinglist': return self.POST_join_waitinglist(edition, user) elif action == 'leave-waitinglist': return self.POST_leave_waitinglist(edition, user, i) # Action not recognized raise web.seeother(error_redirect)
def POST(self, key): """Called when the user wants to borrow the edition""" i = web.input(action='borrow', format=None, ol_host=None, _autoReadAloud=None, q="") ol_host = i.ol_host or 'openlibrary.org' action = i.action edition = web.ctx.site.get(key) if not edition: raise web.notfound() archive_url = get_bookreader_stream_url(edition.ocaid) + '?ref=ol' if i._autoReadAloud is not None: archive_url += '&_autoReadAloud=show' if i.q: _q = urllib.parse.quote(i.q, safe='') archive_url += "#page/-/mode/2up/search/%s" % _q # Make a call to availability v2 update the subjects according # to result if `open`, redirect to bookreader response = lending.get_availability_of_ocaid(edition.ocaid) availability = response[edition.ocaid] if response else {} if availability and availability['status'] == 'open': raise web.seeother(archive_url) error_redirect = (archive_url) user = accounts.get_current_user() if user: account = OpenLibraryAccount.get_by_email(user.email) ia_itemname = account.itemname if account else None s3_keys = web.ctx.site.store.get(account._key).get('s3_keys') if not user or not ia_itemname or not s3_keys: web.setcookie(config.login_cookie_name, "", expires=-1) redirect_url = "/account/login?redirect=%s/borrow?action=%s" % ( edition.url(), action) if i._autoReadAloud is not None: redirect_url += '&_autoReadAloud=' + i._autoReadAloud raise web.seeother(redirect_url) if action == 'return': lending.s3_loan_api(edition.ocaid, s3_keys, action='return_loan') stats.increment('ol.loans.return') raise web.seeother(edition.url()) elif action == 'join-waitinglist': lending.s3_loan_api(edition.ocaid, s3_keys, action='join_waitlist') stats.increment('ol.loans.joinWaitlist') raise web.redirect(edition.url()) elif action == 'leave-waitinglist': lending.s3_loan_api(edition.ocaid, s3_keys, action='leave_waitlist') stats.increment('ol.loans.leaveWaitlist') raise web.redirect(edition.url()) # Intercept a 'borrow' action if the user has already # borrowed the book and convert to a 'read' action. # Added so that direct bookreader links being routed through # here can use a single action of 'borrow', regardless of # whether the book has been checked out or not. elif user.has_borrowed(edition): action = 'read' elif action in ('borrow', 'browse'): borrow_access = user_can_borrow_edition(user, edition) if not (s3_keys or borrow_access): raise web.seeother(error_redirect) lending.s3_loan_api(edition.ocaid, s3_keys, action='%s_book' % borrow_access) stats.increment('ol.loans.bookreader') stats.increment('ol.loans.%s' % borrow_access) action = 'read' if action == 'read': bookPath = '/stream/' + edition.ocaid if i._autoReadAloud is not None: bookPath += '?_autoReadAloud=show' # Look for loans for this book user.update_loan_status() loans = get_loans(user) for loan in loans: if loan['book'] == edition.key: raise web.seeother(make_bookreader_auth_link( loan['_key'], edition.ocaid, bookPath, ol_host, ia_userid=ia_itemname )) # Action not recognized raise web.seeother(error_redirect)
def POST(self, key): """Called when the user wants to borrow the edition""" i = web.input(action='borrow', format=None, ol_host=None) if i.ol_host: ol_host = i.ol_host else: ol_host = 'openlibrary.org' edition = web.ctx.site.get(key) if not edition: raise web.notfound() # Make a call to availability v2 update the subjects according # to result if `open`, redirect to bookreader response = lending.get_availability_of_ocaid(edition.ocaid) availability = response[edition.ocaid] if response else {} if availability and availability['status'] == 'open': raise web.seeother('https://archive.org/stream/' + edition.ocaid + '?ref=ol') error_redirect = ('https://archive.org/stream/' + edition.ocaid + '?ref=ol') user = accounts.get_current_user() if user: account = OpenLibraryAccount.get_by_email(user.email) ia_itemname = account.itemname if account else None if not user or not ia_itemname: web.setcookie(config.login_cookie_name, "", expires=-1) raise web.seeother("/account/login?redirect=%s/borrow?action=%s" % ( edition.url(), i.action)) action = i.action # Intercept a 'borrow' action if the user has already # borrowed the book and convert to a 'read' action. # Added so that direct bookreader links being routed through # here can use a single action of 'borrow', regardless of # whether the book has been checked out or not. if action == 'borrow' and user.has_borrowed(edition): action = 'read' if action == 'borrow': resource_type = i.format or 'bookreader' if resource_type not in ['epub', 'pdf', 'bookreader']: raise web.seeother(error_redirect) user_meets_borrow_criteria = user_can_borrow_edition(user, edition, resource_type) if user_meets_borrow_criteria: # This must be called before the loan is initiated, # otherwise the user's waitlist status will be cleared # upon loan creation track_loan = False if is_users_turn_to_borrow(user, edition) else True loan = lending.create_loan( identifier=edition.ocaid, resource_type=resource_type, user_key=ia_itemname, book_key=key) if loan: loan_link = loan['loan_link'] if resource_type == 'bookreader': if track_loan: # As of 2017-12-14, Petabox will be # responsible for tracking borrows which # are the result of waitlist redemptions, # so we don't want to track them here to # avoid double accounting. When a reader # is at the head of a waitlist and goes to # claim their loan, Petabox now checks # whether the waitlist was initiated from # OL, and if it was, petabox tracks # ol.loans.bookreader accordingly via # lending.create_loan. stats.increment('ol.loans.bookreader') raise web.seeother(make_bookreader_auth_link( loan.get_key(), edition.ocaid, '/stream/' + edition.ocaid, ol_host, ia_userid=ia_itemname)) elif resource_type == 'pdf': stats.increment('ol.loans.pdf') raise web.seeother(loan_link) elif resource_type == 'epub': stats.increment('ol.loans.epub') raise web.seeother(loan_link) else: raise web.seeother(error_redirect) else: raise web.seeother(error_redirect) elif action == 'return': # Check that this user has the loan user.update_loan_status() loans = get_loans(user) # We pick the first loan that the user has for this book that is returnable. # Assumes a user can't borrow multiple formats (resource_type) of the same book. user_loan = None for loan in loans: # Handle the case of multiple edition records for the same # ocaid and the user borrowed from one and returning from another has_loan = (loan['book'] == edition.key or loan['ocaid'] == edition.ocaid) if has_loan: user_loan = loan break if not user_loan: # $$$ add error message raise web.seeother(error_redirect) user_loan.return_loan() # Show the page with "you've returned this". Use a dummy slug. # $$$ this would do better in a session variable that can be cleared # after the message is shown once raise web.seeother(edition.url()) elif action == 'read': # Look for loans for this book user.update_loan_status() loans = get_loans(user) for loan in loans: if loan['book'] == edition.key: raise web.seeother(make_bookreader_auth_link( loan['_key'], edition.ocaid, '/stream/' + edition.ocaid, ol_host, ia_userid=ia_itemname )) elif action == 'join-waitinglist': return self.POST_join_waitinglist(edition, user) elif action == 'leave-waitinglist': return self.POST_leave_waitinglist(edition, user, i) # Action not recognized raise web.seeother(error_redirect)