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

Nick Guenther 15 years ago
parent 0241e3b0eb
commit 899791fb4e
  1. 50
  2. 142
  3. 11

@ -3,13 +3,18 @@
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.
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 time
import re
#LIBRARY_DB = "/users/ceo/library/books.db"
#LIBRARY_DB = "/users/office/library/books.db"
LIBRARY_DB = "./csc_library.db" #testing location
def format_maybe(v):
@ -20,17 +25,19 @@ def format_maybe(v):
return str(v)
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'."""
self.author =author
self.author = author
self.title = title
self.year = year
self.ISBN = ISBN
self.description = description
self.signout = None
def sign_out(self, username):
if self.signout is None:
self.signout = Signout(username)
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):
if self.signout is not None:
self.signout = None
@ -69,11 +76,11 @@ def reset():
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?)
i = len(db)
db[str(i)] = Book(author, title, year)
isbn = str(len(db)) #not true, but works for now
db[isbn] = Book(author, title, year, isbn)
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
author and title are regular expressions
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"""
#this code is SOOO bad, someone who has a clear head please fix this
#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 re.search(author, book.author):
b_auth = True
@ -105,17 +112,38 @@ def search(author=None, title=None, year=None, signedout=None):
b_year = True
b_year = False
if ISBN is not None:
if re.search(ISBN, book.ISBN):
b_ISBN = True
b_ISBN = False
if description is not None:
if re.search(description, book.description):
b_description = True
b_description = False
if signedout 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:
book = db[i]
yield i,book
yield book
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
def delete(book):
db = shelve.open(LIBRARY_DB, "w")
del db[book.ISBN]
#def delete(....):
# """must think about how to do this one; it'll have to be tied to the DB ID somehow"""
# pass
@ -130,4 +158,4 @@ if __name__ == '__main__':
print "Listing database"
for b in search():
print b
print b

@ -6,36 +6,37 @@ from ceo.urwid.window import *
import ceo.library as lib
def library(data):
menu = make_menu([
("Checkout Book", checkout_book, None),
("Return Book", return_book, None),
("List Books", search_books, None),
("Search Books", search_books, None),
("Add Book", add_book, None),
("Remove Book", remove_book, None),
#("Remove Book", remove_book, None),
("Back", raise_back, None),
push_window(menu, "Library")
def checkout_book(data):
"should only search signed in books"
def return_book(data):
"should bring up a searchbox of all the guys first"
def search_books(data):
push_wizard("Search Books", [
push_window(urwid.Filler(SearchPage(), valign='top'), "Search Books")
def view_book(book):
"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):
#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 = []
for b in books:
widgets.append(urwid.AttrWrap(ButtonText(view_book, b, str(b)), None, 'selected'))
@ -45,13 +46,13 @@ def view_books(books):
def add_book(data):
push_wizard("Add Book", [AddBookPage])
def remove_book(data):
#def remove_book(data):
# pass
def parse_commaranges(s):
"""parse a string into a list of numbers"""
"""Fixme: this should be in a different module"""
def numbers(section):
if "-" in section:
range_ = section.split("-")
@ -64,7 +65,7 @@ def parse_commaranges(s):
l = []
for y in s.split(","):
return l
@ -103,22 +104,35 @@ class AddBookPage(WizardPanel):
class SearchPage(WizardPanel):
def init_widgets(self):
class SearchPage(urwid.WidgetWrap):
TODO: need to be able to jump to "search" button quickly; perhaps trap a certain keypress?
def __init__(self):
self.author = SingleEdit("Author: ")
self.title = SingleEdit("Title: ")
self.year = SingleEdit("Year(s): ")
self.signedout = urwid.CheckBox("Checked Out: ")
self.widgets = [
urwid.Text("Search Library"),
self.ISBN = SingleEdit("ISBN: ")
self.description = urwid.Edit("Description: ", multiline=True)
self.signedout = urwid.CheckBox(": Checked Out")
self.ok = urwid.Button("Search", self.search)
self.back = urwid.Button("Back", raise_back)
widgets = [
#urwid.Text("Search Library"),
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()
if author == "":
author = None #null it so that searching ignores
@ -131,17 +145,22 @@ class SearchPage(WizardPanel):
years = None
#try to parse the year field
years = parse_commaranges( year )
years = parse_commaranges( years )
set_status("Invalid year")
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()
view_books(lib.search(author, title, years, signedout))
view_books(lib.search(author, title, years, ISBN, description, signedout))
class CheckoutPage(urwid.WidgetWrap):
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
@ -154,14 +173,17 @@ class CheckoutPage(urwid.WidgetWrap):
urwid.WidgetWrap.__init__(self, urwid.Pile(self.widgets))
class ConfirmDialog(urwid.WidgetWrap):
def __init__(self, msg):
raise NotImplementedError
def Confirm(msg):
#this should be in widgets.py
class InputDialog(urwid.WidgetWrap):
def __init__(self, msg=None):
msg = urwid.Text(msg)
@ -178,6 +200,7 @@ class InputDialog(urwid.WidgetWrap):
self.result = None
raise Abort()
def urwid_input(msg):
#this should be in widgets.py
dialog = InputDialog(msg)
@ -185,13 +208,6 @@ def urwid_input(msg):
event_loop(urwid.main.ui) #HACK
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")
def do_delete(book):
if Confirm("Do you wish to delete %r?" % book):
@ -203,16 +219,58 @@ class BookPage(urwid.WidgetWrap):
self.author = SingleEdit("Author: ")
self.title = SingleEdit("Title: ")
self.year = SingleIntEdit("Year: ")
#now need a checkout widget to go down here..
#and "Delete"
if book.signout is None:
self.checkout = ButtonText(do_checkout, book, "Check Out")
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,
], 15, 3, 1, 'left')
urwid.WidgetWrap.__init__(self, self.author)
self.ISBN = urwid.Text("ISBN: ")
self.description = urwid.Edit("Description: ", multiline=True)
self.checkout_label = urwid.Text("")
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,
urwid.Divider(), buttons])
urwid.WidgetWrap.__init__(self, display)
#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.ISBN.set_text("ISBN: " + self._book.ISBN)
if self._book.signout is None:
self.checkout_label.set_text("Checked In")
self.checkout_button.set_label("Check Out")
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()
def checkout(self, *sender):
username = "nguenthe"
def checkin(self, *sender):
def delete(self, *sender):

@ -2,6 +2,8 @@ import urwid, ldap
from ceo.urwid.window import raise_back, push_window
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_base = "dc=csclub,dc=uwaterloo,dc=ca"
@ -33,6 +35,15 @@ class ButtonText(urwid.Text):
return key
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):
def keypress(self, size, key):
if key == 'enter':