forked from public/pyceo
parent
db90d4ce00
commit
4b0db35899
@ -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 |
||||
from ceo import members |
||||
from ceo.urwid import search |
||||
from ceo.urwid.widgets import * |
||||
import os |
||||
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): |
||||
""" |
||||
Create the main menu for the library system. |
||||
""" |
||||
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 |
||||
os.system("librarian") |
||||
ui.stop() |
||||
ui.start() |
||||
|
@ -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