We've gone from not having a library, to having a basic library that almost works! There's kinks and the code could be cleaner in places, but it's a really decent start for only a day's work. yayyyy python

This commit is contained in:
Nick Guenther 2008-06-02 23:21:25 -04:00
parent 0241e3b0eb
commit 899791fb4e
3 changed files with 150 additions and 53 deletions

View File

@ -3,13 +3,18 @@
This uses shelve which is pretty simplistic, but which should be sufficient for our (extremely minimal) use of the library. This uses shelve which is pretty simplistic, but which should be sufficient for our (extremely minimal) use of the library.
There is a booklist (book=(Author, Title, Year, Signout)) where signout is None for "Checked In" or a (userid, date) to give who and when that book was signed out. There is a booklist (book=(Author, Title, Year, Signout)) where signout is None for "Checked In" or a (userid, date) to give who and when that book was signed out.
We key books by their ISBN number (this is currently only hoboily implemented; we don't use real ISBNs yet)
Future plans: use barcode scanners, index by ISBN, cross reference to library of congress
Future plans: keep a whole stack of people who have checked it out (the last few at least)
""" """
import shelve import shelve
import time import time
import re import re
#LIBRARY_DB = "/users/ceo/library/books.db" #LIBRARY_DB = "/users/office/library/books.db"
LIBRARY_DB = "./csc_library.db" #testing location LIBRARY_DB = "./csc_library.db" #testing location
def format_maybe(v): def format_maybe(v):
@ -20,17 +25,19 @@ def format_maybe(v):
return str(v) return str(v)
class Book: class Book:
def __init__(self, author, title, year): def __init__(self, author, title, year, ISBN=None, description=""):
"""Any of these may be None to indicate 'unknown'.""" """Any of these may be None to indicate 'unknown'."""
self.author =author self.author = author
self.title = title self.title = title
self.year = year self.year = year
self.ISBN = ISBN
self.description = description
self.signout = None self.signout = None
def sign_out(self, username): def sign_out(self, username):
if self.signout is None: if self.signout is None:
self.signout = Signout(username) self.signout = Signout(username)
else: else:
raise Exception("Book already signed out to %s" % self.signout.name, b) raise Exception("Book already signed out to %s" % self.signout.name, self)
def sign_in(self): def sign_in(self):
if self.signout is not None: if self.signout is not None:
self.signout = None self.signout = None
@ -69,11 +76,11 @@ def reset():
def add(author, title, year): def add(author, title, year):
db = shelve.open(LIBRARY_DB,'c') #use w here (not c) to ensure a crash if the DB file got erased (is this a good idea?) db = shelve.open(LIBRARY_DB,'c') #use w here (not c) to ensure a crash if the DB file got erased (is this a good idea?)
i = len(db) isbn = str(len(db)) #not true, but works for now
db[str(i)] = Book(author, title, year) db[isbn] = Book(author, title, year, isbn)
db.close() db.close()
def search(author=None, title=None, year=None, signedout=None): def search(author=None, title=None, year=None, ISBN=None, description=None, signedout=None):
"""search for a title """search for a title
author and title are regular expressions author and title are regular expressions
year is a single number or a list of numbers (so use range() to search the DB) year is a single number or a list of numbers (so use range() to search the DB)
@ -89,7 +96,7 @@ def search(author=None, title=None, year=None, signedout=None):
"""filter by the given params, but only apply those that are non-None""" """filter by the given params, but only apply those that are non-None"""
#this code is SOOO bad, someone who has a clear head please fix this #this code is SOOO bad, someone who has a clear head please fix this
#we need to apply: #we need to apply:
b_auth = b_title = b_year = b_signedout = True #default to true (in case of None i.e. this doesn't apply) b_auth = b_title = b_year = b_ISBN = b_description = b_signedout = True #default to true (in case of None i.e. this doesn't apply)
if author is not None: if author is not None:
if re.search(author, book.author): if re.search(author, book.author):
b_auth = True b_auth = True
@ -105,17 +112,38 @@ def search(author=None, title=None, year=None, signedout=None):
b_year = True b_year = True
else: else:
b_year = False b_year = False
if ISBN is not None:
if re.search(ISBN, book.ISBN):
b_ISBN = True
else:
b_ISBN = False
if description is not None:
if re.search(description, book.description):
b_description = True
else:
b_description = False
if signedout is not None: if signedout is not None:
b_signedout = signedout == (book.signout is not None) b_signedout = signedout == (book.signout is not None)
return b_auth and b_title and b_year and b_signedout return b_auth and b_title and b_year and b_ISBN and b_description and b_signedout
for i in db: for i in db:
book = db[i] book = db[i]
if(filter(book)): if(filter(book)):
yield i,book yield book
db.close() db.close()
def save(book):
db = shelve.open(LIBRARY_DB, "w")
assert book.ISBN is not None, "We should really handle this case better, like making an ISBN or something"
db[book.ISBN] = book
db.close()
def delete(book):
db = shelve.open(LIBRARY_DB, "w")
del db[book.ISBN]
#def delete(....): #def delete(....):
# """must think about how to do this one; it'll have to be tied to the DB ID somehow""" # """must think about how to do this one; it'll have to be tied to the DB ID somehow"""
# pass # pass

View File

@ -6,36 +6,37 @@ from ceo.urwid.window import *
import ceo.library as lib import ceo.library as lib
def library(data): def library(data):
menu = make_menu([ menu = make_menu([
("Checkout Book", checkout_book, None), ("Checkout Book", checkout_book, None),
("Return Book", return_book, None), ("Return Book", return_book, None),
("List Books", search_books, None), ("Search Books", search_books, None),
("Add Book", add_book, None), ("Add Book", add_book, None),
("Remove Book", remove_book, None), #("Remove Book", remove_book, None),
("Back", raise_back, None), ("Back", raise_back, None),
]) ])
push_window(menu, "Library") push_window(menu, "Library")
def checkout_book(data): def checkout_book(data):
"should only search signed in books" "should only search signed in books"
pass view_books(lib.search(signedout=False))
def return_book(data): def return_book(data):
"should bring up a searchbox of all the guys first" "should bring up a searchbox of all the guys first"
pass view_books(lib.search(signedout=True))
def search_books(data): def search_books(data):
push_wizard("Search Books", [ push_window(urwid.Filler(SearchPage(), valign='top'), "Search Books")
SearchPage,
])
def view_book(book): def view_book(book):
"this should develop into a full fledged useful panel for doing stuff with books. for now it's not." "this should develop into a full fledged useful panel for doing stuff with books. for now it's not."
push_window(BookPage(book), "Book detail") push_window(urwid.Filler(BookPage(book), valign='top'), "Book detail")
def view_books(books): def view_books(books):
#XXX should not use a hardcoded 20 in there, should grab the value from the width of the widget #XXX should not use a hardcoded 20 in there, should grab the value from the width of the widget
#TODO: this should take the search arguments, and stash them away, and everytime you come back to this page it should refresh itself
widgets = [] widgets = []
for b in books: for b in books:
widgets.append(urwid.AttrWrap(ButtonText(view_book, b, str(b)), None, 'selected')) widgets.append(urwid.AttrWrap(ButtonText(view_book, b, str(b)), None, 'selected'))
@ -45,13 +46,13 @@ def view_books(books):
def add_book(data): def add_book(data):
push_wizard("Add Book", [AddBookPage]) push_wizard("Add Book", [AddBookPage])
def remove_book(data): #def remove_book(data):
pass # pass
def parse_commaranges(s): def parse_commaranges(s):
"""parse a string into a list of numbers""" """parse a string into a list of numbers"""
"""Fixme: this should be in a different module"""
def numbers(section): def numbers(section):
if "-" in section: if "-" in section:
range_ = section.split("-") range_ = section.split("-")
@ -64,7 +65,7 @@ def parse_commaranges(s):
l = [] l = []
for y in s.split(","): for y in s.split(","):
l.append(numbers(y)) l.extend(numbers(y))
return l return l
@ -103,22 +104,35 @@ class AddBookPage(WizardPanel):
class SearchPage(WizardPanel): class SearchPage(urwid.WidgetWrap):
def init_widgets(self): """
TODO: need to be able to jump to "search" button quickly; perhaps trap a certain keypress?
"""
def __init__(self):
self.author = SingleEdit("Author: ") self.author = SingleEdit("Author: ")
self.title = SingleEdit("Title: ") self.title = SingleEdit("Title: ")
self.year = SingleEdit("Year(s): ") self.year = SingleEdit("Year(s): ")
self.signedout = urwid.CheckBox("Checked Out: ") self.ISBN = SingleEdit("ISBN: ")
self.widgets = [ self.description = urwid.Edit("Description: ", multiline=True)
urwid.Text("Search Library"), self.signedout = urwid.CheckBox(": Checked Out")
urwid.Divider(), self.ok = urwid.Button("Search", self.search)
self.back = urwid.Button("Back", raise_back)
widgets = [
#urwid.Text("Search Library"),
#urwid.Divider(),
self.author, self.author,
self.title, self.title,
self.year, self.year,
self.ISBN,
self.description,
self.signedout,
urwid.Divider(), urwid.Divider(),
urwid.Text("Author/Title are regexes.\nYear is a comma-separated list or a hyphen-separated range") urwid.Text("String fields are regexes.\nYear is a comma-separated list or a hyphen-separated range")
] ]
def check(self): buttons = urwid.GridFlow([self.ok, self.back], 10, 3, 1, align='right')
urwid.WidgetWrap.__init__(self, urwid.Pile([urwid.Pile(widgets), buttons]))
def search(self, *sender):
author = self.author.get_edit_text() author = self.author.get_edit_text()
if author == "": if author == "":
author = None #null it so that searching ignores author = None #null it so that searching ignores
@ -131,17 +145,22 @@ class SearchPage(WizardPanel):
years = None years = None
else: else:
#try to parse the year field #try to parse the year field
years = parse_commaranges( year ) years = parse_commaranges( years )
except: except:
raise
self.focus_widget(self.year) self.focus_widget(self.year)
set_status("Invalid year") set_status("Invalid year")
return True return True
ISBN = self.ISBN.get_edit_text()
if ISBN == "": ISBN = None
description = self.description.get_edit_text()
if description == "": description = None
signedout = self.signedout.get_state() signedout = self.signedout.get_state()
view_books(lib.search(author, title, years, signedout)) view_books(lib.search(author, title, years, ISBN, description, signedout))
#DONTUSE
class CheckoutPage(urwid.WidgetWrap): class CheckoutPage(urwid.WidgetWrap):
def __init__(self, book): def __init__(self, book):
self.book = SingleEdit("Book: ") #this needs to be a widget that when you click on it, it takes you to the search_books pane, lets you pick a book, and then drops you back here self.book = SingleEdit("Book: ") #this needs to be a widget that when you click on it, it takes you to the search_books pane, lets you pick a book, and then drops you back here
@ -154,14 +173,17 @@ class CheckoutPage(urwid.WidgetWrap):
] ]
urwid.WidgetWrap.__init__(self, urwid.Pile(self.widgets)) urwid.WidgetWrap.__init__(self, urwid.Pile(self.widgets))
#DONTUSE
class ConfirmDialog(urwid.WidgetWrap): class ConfirmDialog(urwid.WidgetWrap):
def __init__(self, msg): def __init__(self, msg):
raise NotImplementedError raise NotImplementedError
#DONTUSE
def Confirm(msg): def Confirm(msg):
#this should be in widgets.py #this should be in widgets.py
push_window(ConfirmDialog(msg)) push_window(ConfirmDialog(msg))
#DONTUSE
class InputDialog(urwid.WidgetWrap): class InputDialog(urwid.WidgetWrap):
def __init__(self, msg=None): def __init__(self, msg=None):
msg = urwid.Text(msg) msg = urwid.Text(msg)
@ -178,6 +200,7 @@ class InputDialog(urwid.WidgetWrap):
self.result = None self.result = None
raise Abort() raise Abort()
#DONTUSE
def urwid_input(msg): def urwid_input(msg):
#this should be in widgets.py #this should be in widgets.py
dialog = InputDialog(msg) dialog = InputDialog(msg)
@ -185,13 +208,6 @@ def urwid_input(msg):
event_loop(urwid.main.ui) #HACK event_loop(urwid.main.ui) #HACK
return dialog.result return dialog.result
def do_checkout(book):
"this is temporary to fil lthe gap until we see what we reall need"
username = urwid_input("Username to check out to?")
if username is None:
set_status("Checkout cancelled")
else:
book.sign_out(username)
def do_delete(book): def do_delete(book):
if Confirm("Do you wish to delete %r?" % book): if Confirm("Do you wish to delete %r?" % book):
@ -203,16 +219,58 @@ class BookPage(urwid.WidgetWrap):
self.author = SingleEdit("Author: ") self.author = SingleEdit("Author: ")
self.title = SingleEdit("Title: ") self.title = SingleEdit("Title: ")
self.year = SingleIntEdit("Year: ") self.year = SingleIntEdit("Year: ")
#now need a checkout widget to go down here.. self.ISBN = urwid.Text("ISBN: ")
#and "Delete" self.description = urwid.Edit("Description: ", multiline=True)
if book.signout is None: self.checkout_label = urwid.Text("")
self.checkout = ButtonText(do_checkout, book, "Check Out")
else:
self.checkout = ButtonText(lambda book: book.sign_in(), book, "Check In")
#self.remove = ButtonText(do_delete, book, "Delete")
display = urwid.GridFlow([self.author, self.title, self.year,
#self.checkout,
#self.remove
], 15, 3, 1, 'left')
urwid.WidgetWrap.__init__(self, self.author)
save = urwid.Button("Save", self.save)
self.checkout_button = urwid.Button("", self.checkout)
back = urwid.Button("Back", raise_back)
remove = urwid.Button("Delete", self.delete)
buttons = urwid.GridFlow([back, self.checkout_button, save, remove], 13, 2, 1, 'center')
display = urwid.Pile([self.author, self.title, self.year, self.ISBN,
self.description,
urwid.Divider(), buttons])
urwid.WidgetWrap.__init__(self, display)
self.refresh()
#all these *senders are to allow these to be used as event handlers or just on their own
def refresh(self, *sender):
"""update the widgets from the data model"""
self.author.set_edit_text(self._book.author)
self.title.set_edit_text(self._book.title)
self.year.set_edit_text(str(self._book.year))
self.ISBN.set_text("ISBN: " + self._book.ISBN)
self.description.set_edit_text(self._book.description)
if self._book.signout is None:
self.checkout_label.set_text("Checked In")
self.checkout_button.set_label("Check Out")
else:
self.checkout_label.set_text(self._book.signout)
self.checkout_button.set_label("Check In")
def save(self, *sender):
self._book.author = self.author.get_edit_text()
self._book.title = self.title.get_edit_text()
yeartmp = self.year.get_edit_text()
if yeartmp is not None: yeartmp = int(yeartmp)
self._book.year = yeartmp
#self._book.ISBN = .... #no... don't do this...
self._book.description = self.description.get_edit_text()
lib.save(self._book)
self.refresh()
def checkout(self, *sender):
username = "nguenthe"
self._book.sign_out(username)
self.save()
def checkin(self, *sender):
self._book.sign_in()
self.save()
def delete(self, *sender):
lib.delete(self._book)
raise_back()

View File

@ -2,6 +2,8 @@ import urwid, ldap
from ceo.urwid.window import raise_back, push_window from ceo.urwid.window import raise_back, push_window
import ceo.ldapi as ldapi import ceo.ldapi as ldapi
#Todo: kill ButtonText because no one uses it except one place and we can probably do that better anyway
csclub_uri = "ldap://ldap1.csclub.uwaterloo.ca/ ldap://ldap2.csclub.uwaterloo.ca" csclub_uri = "ldap://ldap1.csclub.uwaterloo.ca/ ldap://ldap2.csclub.uwaterloo.ca"
csclub_base = "dc=csclub,dc=uwaterloo,dc=ca" csclub_base = "dc=csclub,dc=uwaterloo,dc=ca"
@ -33,6 +35,15 @@ class ButtonText(urwid.Text):
else: else:
return key return key
#DONTUSE
class CaptionedText(urwid.Text):
def __init__(self, caption, *args, **kwargs):
self.caption = caption
urwid.Text.__init__(self, *args, **kwargs)
def render(self, *args, **kwargs):
self.set_text(self.caption + self.get_text()[0])
urwid.Text.render(*args, **kwargs)
class SingleEdit(urwid.Edit): class SingleEdit(urwid.Edit):
def keypress(self, size, key): def keypress(self, size, key):
if key == 'enter': if key == 'enter':