forked from public/pyceo
Replace the stub of the library with a system call to "librarian"
This commit is contained in:
parent
db90d4ce00
commit
4b0db35899
122
ceo/library.py
122
ceo/library.py
|
@ -1,122 +0,0 @@
|
||||||
from sqlobject import *
|
|
||||||
from sqlobject.sqlbuilder import *
|
|
||||||
from ceo import conf
|
|
||||||
from ceo import members
|
|
||||||
from ceo import terms
|
|
||||||
import time
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
CONFIG_FILE = "/etc/csc/library.cf"
|
|
||||||
|
|
||||||
cfg = {}
|
|
||||||
|
|
||||||
def configure():
|
|
||||||
"""
|
|
||||||
Load configuration
|
|
||||||
"""
|
|
||||||
cfg_fields = [ "library_connect_string" ]
|
|
||||||
|
|
||||||
temp_cfg = conf.read(CONFIG_FILE)
|
|
||||||
conf.check_string_fields(CONFIG_FILE, cfg_fields, temp_cfg)
|
|
||||||
cfg.update(temp_cfg)
|
|
||||||
|
|
||||||
sqlhub.processConnection = connectionForURI(cfg["library_connect_string"])
|
|
||||||
|
|
||||||
class Book(SQLObject):
|
|
||||||
"""
|
|
||||||
A book. This does all the stuff we could
|
|
||||||
ever want to do with a book.
|
|
||||||
"""
|
|
||||||
isbn = StringCol()
|
|
||||||
title = StringCol()
|
|
||||||
year = StringCol()
|
|
||||||
publisher = StringCol()
|
|
||||||
authors = SQLRelatedJoin("Author")
|
|
||||||
signouts = SQLMultipleJoin("Signout")
|
|
||||||
|
|
||||||
def sign_out(self, u):
|
|
||||||
"""
|
|
||||||
Call this with a username to sign out
|
|
||||||
a book.
|
|
||||||
"""
|
|
||||||
if members.registered(u, terms.current()):
|
|
||||||
s = Signout(username=u, book=self,
|
|
||||||
outdate=datetime.today(), indate=None)
|
|
||||||
|
|
||||||
def sign_in(self, u):
|
|
||||||
"""
|
|
||||||
Call this to check a book back in to
|
|
||||||
the library. Username is used to
|
|
||||||
disambiguate in case more than one
|
|
||||||
copy of this book has been signed out.
|
|
||||||
"""
|
|
||||||
s = self.signouts.filter(AND(Signout.q.indate==None, Signout.q.username==u))
|
|
||||||
if s.count() > 0:
|
|
||||||
list(s.orderBy(Signout.q.outdate).limit(1))[0].sign_in()
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
raise Exception("PEBKAC: Book not signed out!")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""
|
|
||||||
Magic drugs to make books display
|
|
||||||
nicely.
|
|
||||||
"""
|
|
||||||
book = "%s [%s]" % (self.title, self.year)
|
|
||||||
book += "\nBy: "
|
|
||||||
for a in self.authors:
|
|
||||||
book += a.name
|
|
||||||
book += ", "
|
|
||||||
|
|
||||||
if self.authors.count() < 1:
|
|
||||||
book += "(unknown)"
|
|
||||||
|
|
||||||
book = book.strip(", ")
|
|
||||||
|
|
||||||
signouts = self.signouts.filter(Signout.q.indate==None)
|
|
||||||
if signouts.count() > 0:
|
|
||||||
book += "\nSigned Out: "
|
|
||||||
for s in signouts:
|
|
||||||
book += s.username + " (" + str(s.due_date) + "), "
|
|
||||||
|
|
||||||
book = book.strip(", ")
|
|
||||||
|
|
||||||
return book
|
|
||||||
|
|
||||||
|
|
||||||
class Author(SQLObject):
|
|
||||||
"""
|
|
||||||
An author can author many books, and a book
|
|
||||||
can have many authors. This lets us map
|
|
||||||
both ways.
|
|
||||||
"""
|
|
||||||
name = StringCol()
|
|
||||||
books = RelatedJoin("Book")
|
|
||||||
|
|
||||||
class Signout(SQLObject):
|
|
||||||
"""
|
|
||||||
An instance of a signout associates usernames,
|
|
||||||
books, signout dates, and return dates to mark
|
|
||||||
that a book has been signed out by a particular
|
|
||||||
user.
|
|
||||||
"""
|
|
||||||
username = StringCol()
|
|
||||||
book = ForeignKey("Book")
|
|
||||||
outdate = DateCol()
|
|
||||||
indate = DateCol()
|
|
||||||
|
|
||||||
def sign_in(self):
|
|
||||||
"""
|
|
||||||
Terminate the signout (return the book).
|
|
||||||
"""
|
|
||||||
self.indate = datetime.today()
|
|
||||||
|
|
||||||
def _get_due_date(self):
|
|
||||||
"""
|
|
||||||
Compute the due date of the book based on the sign-out
|
|
||||||
date.
|
|
||||||
"""
|
|
||||||
return self.outdate + timedelta(weeks=2)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print "This functionality isn't implemented yet."
|
|
|
@ -1,343 +1,8 @@
|
||||||
import urwid
|
import os
|
||||||
from ceo import members
|
|
||||||
from ceo.urwid import search
|
|
||||||
from ceo.urwid.widgets import *
|
|
||||||
from ceo.urwid.window import *
|
from ceo.urwid.window import *
|
||||||
from sqlobject.sqlbuilder import *
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from ceo.pymazon import PyMazon
|
|
||||||
from ceo.pymazon import PyMazonError
|
|
||||||
from ceo import conf
|
|
||||||
|
|
||||||
from ceo import terms
|
|
||||||
|
|
||||||
import ceo.library as lib
|
|
||||||
|
|
||||||
CONFIG_FILE = "/etc/csc/library.cf"
|
|
||||||
|
|
||||||
cfg = {}
|
|
||||||
|
|
||||||
def configure():
|
|
||||||
"""
|
|
||||||
Load configuration
|
|
||||||
"""
|
|
||||||
cfg_fields = [ "aws_account_key", "aws_secret_key" ]
|
|
||||||
temp_cfg = conf.read(CONFIG_FILE)
|
|
||||||
conf.check_string_fields(CONFIG_FILE, cfg_fields, temp_cfg)
|
|
||||||
cfg.update(temp_cfg)
|
|
||||||
|
|
||||||
def library(data):
|
def library(data):
|
||||||
"""
|
os.system("librarian")
|
||||||
Create the main menu for the library system.
|
ui.stop()
|
||||||
"""
|
ui.start()
|
||||||
menu = make_menu([
|
|
||||||
("Checkout Book", checkout_book, None),
|
|
||||||
("Return Book", return_book, None),
|
|
||||||
("Search Books", search_books, None),
|
|
||||||
("Add Book", add_book, None),
|
|
||||||
("Back", raise_back, None),
|
|
||||||
])
|
|
||||||
push_window(menu, "Library")
|
|
||||||
|
|
||||||
def search_books(data):
|
|
||||||
"""
|
|
||||||
Define menus for searching books.
|
|
||||||
"""
|
|
||||||
menu = make_menu([
|
|
||||||
("Overdue Books", overdue_books, None),
|
|
||||||
("Signed Out Books", outbooks_search, None),
|
|
||||||
])
|
|
||||||
push_window(menu, "Book Search")
|
|
||||||
|
|
||||||
def add_book(data):
|
|
||||||
"""
|
|
||||||
Add book to library. Also stab Sapphyre.
|
|
||||||
"""
|
|
||||||
push_wizard("Add Book", [BookAddPage])
|
|
||||||
|
|
||||||
def overdue_books(data):
|
|
||||||
"""
|
|
||||||
Display a list of all books that are overdue.
|
|
||||||
"""
|
|
||||||
oldest = datetime.today() - timedelta(weeks=2)
|
|
||||||
overdue = lib.Signout.select(AND(lib.Signout.q.outdate<oldest, lib.Signout.q.indate==None))
|
|
||||||
|
|
||||||
widgets = []
|
|
||||||
|
|
||||||
for s in overdue:
|
|
||||||
widgets.append(urwid.AttrWrap(ButtonText(None, s.book, str(s.book)),
|
|
||||||
None, 'selected'))
|
|
||||||
widgets.append(urwid.Divider())
|
|
||||||
|
|
||||||
push_window(urwid.ListBox(widgets))
|
|
||||||
|
|
||||||
None
|
|
||||||
|
|
||||||
def outbooks_search(data):
|
|
||||||
"""
|
|
||||||
Display a list of all books that are signed out.
|
|
||||||
"""
|
|
||||||
overdue = lib.Signout.select(lib.Signout.q.indate==None)
|
|
||||||
|
|
||||||
widgets = []
|
|
||||||
|
|
||||||
for s in overdue:
|
|
||||||
widgets.append(urwid.AttrWrap(ButtonText(None, s.book, str(s.book)),
|
|
||||||
None, 'selected'))
|
|
||||||
widgets.append(urwid.Divider())
|
|
||||||
|
|
||||||
push_window(urwid.ListBox(widgets))
|
|
||||||
|
|
||||||
None
|
|
||||||
|
|
||||||
|
|
||||||
def checkout_book(data):
|
|
||||||
"""
|
|
||||||
Display the book checkout wizard.
|
|
||||||
"""
|
|
||||||
push_wizard("Checkout", [CheckoutPage, BookSearchPage, ConfirmPage])
|
|
||||||
|
|
||||||
def return_book(data):
|
|
||||||
"""
|
|
||||||
Display the book return wizard.
|
|
||||||
"""
|
|
||||||
push_wizard("Checkout", [CheckinPage, ConfirmPage])
|
|
||||||
|
|
||||||
class BookAddPage(WizardPanel):
|
|
||||||
"""
|
|
||||||
Thingy for going on screen to add books.
|
|
||||||
"""
|
|
||||||
def init_widgets(self):
|
|
||||||
"""
|
|
||||||
Make some widgets.
|
|
||||||
"""
|
|
||||||
self.isbn = SingleEdit("ISBN: ")
|
|
||||||
|
|
||||||
self.widgets = [
|
|
||||||
urwid.Text("Adding New Book"),
|
|
||||||
urwid.Divider(),
|
|
||||||
self.isbn
|
|
||||||
]
|
|
||||||
|
|
||||||
def check(self):
|
|
||||||
"""
|
|
||||||
Do black magic.
|
|
||||||
"""
|
|
||||||
configure()
|
|
||||||
isbn = self.isbn.get_edit_text()
|
|
||||||
|
|
||||||
try:
|
|
||||||
pymazon = PyMazon(cfg["aws_account_key"], cfg["aws_secret_key"])
|
|
||||||
book = pymazon.lookup(isbn)
|
|
||||||
|
|
||||||
currents = lib.Book.select(lib.Book.q.isbn==isbn)
|
|
||||||
if currents.count() == 0:
|
|
||||||
lib.Book(isbn=isbn, title=book.title,
|
|
||||||
year=book.year, publisher=book.publisher)
|
|
||||||
pop_window()
|
|
||||||
else:
|
|
||||||
set_status("Book already exists, fucker.")
|
|
||||||
except PyMazonError, e:
|
|
||||||
set_status("Amazon thinks this is not a book. Take it up with them.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class BookSearchPage(WizardPanel):
|
|
||||||
"""
|
|
||||||
The page used when searching for books.
|
|
||||||
"""
|
|
||||||
def init_widgets(self):
|
|
||||||
"""
|
|
||||||
Initialize the widgets and state variables.
|
|
||||||
"""
|
|
||||||
self.search = None
|
|
||||||
self.state["book"] = None
|
|
||||||
self.isbn = SingleEdit("ISBN: ")
|
|
||||||
self.title = SingleEdit("Title: ")
|
|
||||||
|
|
||||||
self.widgets = [
|
|
||||||
urwid.Text("Book Search"),
|
|
||||||
urwid.Text("(Only one field required.)"),
|
|
||||||
urwid.Divider(),
|
|
||||||
self.isbn,
|
|
||||||
self.title
|
|
||||||
]
|
|
||||||
|
|
||||||
def check(self):
|
|
||||||
"""
|
|
||||||
Validate input and update state.
|
|
||||||
"""
|
|
||||||
if self.state["book"] is None:
|
|
||||||
push_window(SearchPage(self.isbn.get_edit_text(),
|
|
||||||
self.title.get_edit_text(),
|
|
||||||
None,
|
|
||||||
self.state))
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class CheckoutPage(WizardPanel):
|
|
||||||
"""
|
|
||||||
The initial page when checking out a book.
|
|
||||||
"""
|
|
||||||
def init_widgets(self):
|
|
||||||
"""
|
|
||||||
Initialize widgets and set up state.
|
|
||||||
|
|
||||||
user -> the username to sign the book to
|
|
||||||
task -> used for the confirmation dialog
|
|
||||||
"""
|
|
||||||
self.state["user"] = "ERROR"
|
|
||||||
self.state["task"] = "sign_out"
|
|
||||||
self.user = LdapWordEdit(csclub_uri, csclub_base, 'uid', "Username: ")
|
|
||||||
|
|
||||||
self.widgets = [
|
|
||||||
urwid.Text("Book Checkout"),
|
|
||||||
urwid.Divider(),
|
|
||||||
self.user,
|
|
||||||
]
|
|
||||||
|
|
||||||
def check(self):
|
|
||||||
self.state['user'] = self.user.get_edit_text()
|
|
||||||
if not members.registered(self.state['user'], terms.current()):
|
|
||||||
set_status("User not registered for this term!")
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
class ConfirmPage(WizardPanel):
|
|
||||||
"""
|
|
||||||
The confirmation screen when checking-in and checking-out
|
|
||||||
a book.
|
|
||||||
"""
|
|
||||||
def init_widgets(self):
|
|
||||||
"""
|
|
||||||
Initialize widgets and state.
|
|
||||||
|
|
||||||
task -> used to deterimine the action
|
|
||||||
"""
|
|
||||||
self.user = urwid.Text("Username: ")
|
|
||||||
self.book = urwid.Text("Book: ")
|
|
||||||
|
|
||||||
title = ""
|
|
||||||
if self.state["task"] and self.state["task"]=="sign_in":
|
|
||||||
title = "Checkin"
|
|
||||||
else:
|
|
||||||
title = "Checkout"
|
|
||||||
|
|
||||||
self.widgets = [
|
|
||||||
urwid.Text("Confirm " + title),
|
|
||||||
urwid.Divider(),
|
|
||||||
self.user,
|
|
||||||
self.book
|
|
||||||
]
|
|
||||||
|
|
||||||
def activate(self):
|
|
||||||
"""
|
|
||||||
Ensures that correct data is displayed.
|
|
||||||
"""
|
|
||||||
self.user.set_text("Username: " + self.state["user"])
|
|
||||||
if self.state["book"]:
|
|
||||||
self.book.set_text("Book: " + self.state["book"].title)
|
|
||||||
|
|
||||||
def check(self):
|
|
||||||
"""
|
|
||||||
Generally used for validation, but in this case it does
|
|
||||||
the actual book check-out.
|
|
||||||
"""
|
|
||||||
#TODO: Validate user at some point (preferrably user entry screen)
|
|
||||||
if self.state["task"] and self.state["task"]=="sign_in":
|
|
||||||
self.state["book"].sign_in(self.state["user"])
|
|
||||||
else:
|
|
||||||
self.state["book"].sign_out(self.state["user"])
|
|
||||||
pop_window()
|
|
||||||
|
|
||||||
|
|
||||||
class SearchPage(urwid.WidgetWrap):
|
|
||||||
"""
|
|
||||||
Displays search results. Can search on isbn,
|
|
||||||
title, or username (for books that are currently
|
|
||||||
out).
|
|
||||||
"""
|
|
||||||
def __init__(self, isbn, title, user, state):
|
|
||||||
"""
|
|
||||||
This does the actual search, and sets up the screen
|
|
||||||
when it's done.
|
|
||||||
|
|
||||||
title -> search by (partial) title
|
|
||||||
isbn -> search by (partial) isbn
|
|
||||||
user -> search by username (for checked-out books)
|
|
||||||
"""
|
|
||||||
self.state = state
|
|
||||||
books = []
|
|
||||||
widgets = []
|
|
||||||
if not title is None and not title=="":
|
|
||||||
books = lib.Book.select(LIKE(lib.Book.q.title, "%" + title + "%"))
|
|
||||||
elif not isbn is None and not isbn=="":
|
|
||||||
books = lib.Book.select(lib.Book.q.isbn==isbn)
|
|
||||||
elif (not (user is None)) and (not (user=="")):
|
|
||||||
st = lib.Signout.select(AND(lib.Signout.q.username==user, lib.Signout.q.indate==None))
|
|
||||||
for s in st:
|
|
||||||
books.append(s.book)
|
|
||||||
else:
|
|
||||||
st = lib.Signout.select(lib.Signout.q.indate==None)
|
|
||||||
for s in st:
|
|
||||||
books.append(s.book)
|
|
||||||
|
|
||||||
if ((type(books) != type([])) and books.count() == 0) or books == []:
|
|
||||||
widgets.append(urwid.Text("No results. Hit ESC to return to search page."))
|
|
||||||
widgets.append(urwid.Divider())
|
|
||||||
|
|
||||||
for b in books:
|
|
||||||
widgets.append(urwid.AttrWrap(ButtonText(self.select, b, str(b)),
|
|
||||||
None, 'selected'))
|
|
||||||
widgets.append(urwid.Divider())
|
|
||||||
|
|
||||||
urwid.WidgetWrap.__init__(self, urwid.ListBox(widgets))
|
|
||||||
|
|
||||||
def select(self, book):
|
|
||||||
"""
|
|
||||||
Marks a book for check-in or check-out.
|
|
||||||
"""
|
|
||||||
self.state["book"] = book
|
|
||||||
pop_window()
|
|
||||||
|
|
||||||
class CheckinPage(WizardPanel):
|
|
||||||
"""
|
|
||||||
The initial page to start the check-in widget.
|
|
||||||
"""
|
|
||||||
def init_widgets(self):
|
|
||||||
"""
|
|
||||||
Throw some widgets on the screen and set up
|
|
||||||
some state.
|
|
||||||
|
|
||||||
book -> The book to check out.
|
|
||||||
user -> Stupid people like books.
|
|
||||||
task -> What are we doing? (For confirm screen.)
|
|
||||||
"""
|
|
||||||
self.state["book"] = None
|
|
||||||
self.state["user"] = "ERROR"
|
|
||||||
self.state["task"] = "sign_in"
|
|
||||||
self.user = LdapWordEdit(csclub_uri, csclub_base, 'uid', "Username: ")
|
|
||||||
|
|
||||||
self.widgets = [
|
|
||||||
urwid.Text("Book Checkin"),
|
|
||||||
urwid.Divider(),
|
|
||||||
self.user,
|
|
||||||
]
|
|
||||||
|
|
||||||
def check(self):
|
|
||||||
"""
|
|
||||||
Pushes the search window.
|
|
||||||
|
|
||||||
Should validate usernames.
|
|
||||||
"""
|
|
||||||
if self.state["book"] is None:
|
|
||||||
push_window(SearchPage(None,
|
|
||||||
None,
|
|
||||||
self.user.get_edit_text(),
|
|
||||||
self.state))
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
self.state["user"] = self.user.get_edit_text()
|
|
||||||
return False
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
# /etc/csc/library.cf: Library Config
|
|
||||||
|
|
||||||
library_connect_string = "postgres://librarian:PWPWPWPWPWPWPWPWPWPW@127.0.0.1/library"
|
|
||||||
aws_account_key = "KEYKEYKEYKEYKEYKEYKY"
|
|
||||||
aws_secret_key = "KEYKEYKEYKEYKEYKEYKY"
|
|
Loading…
Reference in New Issue