Library GUI is coming, but awkwardsadface
This commit is contained in:
parent
9ac0c98ea6
commit
32004be45f
|
@ -11,6 +11,12 @@ import re
|
|||
|
||||
LIBRARY_DB = "./csc_library.db"
|
||||
|
||||
def format_maybe(v):
|
||||
if v is None:
|
||||
return "unknown"
|
||||
else:
|
||||
return str(v)
|
||||
|
||||
class Book:
|
||||
def __init__(self, author, title, year):
|
||||
"""Any of these may be None to indicate 'unknown'."""
|
||||
|
@ -30,6 +36,13 @@ class Book:
|
|||
raise Exception("Book was not signed out, no need to sign it in")
|
||||
|
||||
def __str__(self):
|
||||
author = self.author
|
||||
book = "%s [%s]\nBy: %s" % (format_maybe(self.title), format_maybe(self.year), format_maybe(self.author))
|
||||
if self.signout:
|
||||
book += "\n Signed out by %s on %s" % (self.signout.name, time.ctime(self.signout.date))
|
||||
return book
|
||||
|
||||
def __repr__(self):
|
||||
return "Book(author=%r, title=%r, year=%r, signout=%r)" % (self.author, self.title, self.year, self.signout)
|
||||
|
||||
class Signout:
|
||||
|
@ -53,12 +66,12 @@ def reset():
|
|||
|
||||
|
||||
def add(author, title, year):
|
||||
db = shelve.open(LIBRARY_DB,'w') #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)
|
||||
db[str(i)] = Book(author, title, year)
|
||||
db.close()
|
||||
|
||||
def search(author=None, title=None, year=None):
|
||||
def search(author=None, title=None, year=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)
|
||||
|
@ -67,17 +80,22 @@ def search(author=None, title=None, year=None):
|
|||
this is extraordinarily inefficient, but whatever (I don't think that without having an indexer run inthe background we can improve this any?)
|
||||
returns: a list of Book objects
|
||||
"""
|
||||
db = shelve.open(LIBRARY_DB, 'w', writeback=True) #open it for writing so that changes to books get saved
|
||||
db = shelve.open(LIBRARY_DB, 'c', writeback=True) #open it for writing so that changes to books get saved
|
||||
all = db.values() #this should pull out the ID numbers somehow too.. bah
|
||||
if author is not None:
|
||||
all = [book for book in all if book.author and re.match(author, book.author)] #should factor this out
|
||||
all = [book for book in all if book.author and re.search(author, book.author)] #should factor this out
|
||||
if title is not None:
|
||||
all = [book for book in all if book.title and re.match(title, book.title)] #should factor this out
|
||||
all = [book for book in all if book.title and re.search(title, book.title)] #should factor this out
|
||||
if year is not None:
|
||||
if type(year) == int:
|
||||
year = [year]
|
||||
#now assume year is a list
|
||||
all = [book for book in all if book.year and book.year in year]
|
||||
if signedout is not None:
|
||||
if signedout:
|
||||
all = [book for book in all if book.signout is not None]
|
||||
else:
|
||||
all = [book for book in all if book.signout is None] #aaah copypaste
|
||||
db.close()
|
||||
return all
|
||||
|
||||
|
|
|
@ -4,11 +4,13 @@ from ceo.urwid import search
|
|||
from ceo.urwid.widgets import *
|
||||
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),
|
||||
("Search Books", search_books, None),
|
||||
("List Books", search_books, None),
|
||||
("Add Book", add_book, None),
|
||||
("Remove Book", remove_book, None),
|
||||
("Back", raise_back, None),
|
||||
|
@ -16,16 +18,201 @@ def library(data):
|
|||
push_window(menu, "Library")
|
||||
|
||||
def checkout_book(data):
|
||||
"should only search signed in books"
|
||||
pass
|
||||
|
||||
def return_book(data):
|
||||
"should bring up a searchbox of all the guys first"
|
||||
pass
|
||||
|
||||
def search_books(data):
|
||||
pass
|
||||
push_wizard("Search Books", [
|
||||
SearchPage,
|
||||
])
|
||||
|
||||
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")
|
||||
|
||||
def view_books(books):
|
||||
#XXX should not use a hardcoded 20 in there, should grab the value from the width of the widget
|
||||
widgets = []
|
||||
for b in books:
|
||||
widgets.append(urwid.AttrWrap(ButtonText(view_book, b, str(b)), None, 'selected'))
|
||||
widgets.append(urwid.Divider())
|
||||
push_window(urwid.ListBox(widgets))
|
||||
|
||||
def add_book(data):
|
||||
pass
|
||||
push_wizard("Add Book", [AddBookPage])
|
||||
|
||||
def remove_book(data):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def parse_commaranges(s):
|
||||
"""parse a string into a list of numbers"""
|
||||
def numbers(section):
|
||||
if "-" in section:
|
||||
range_ = section.split("-")
|
||||
assert len(range_) == 2
|
||||
start = int(range_[0])
|
||||
end = int(range_[1])
|
||||
return range(start, end+1) #+1 to be inclusive of end
|
||||
else:
|
||||
return [int(section)]
|
||||
|
||||
l = []
|
||||
for y in s.split(","):
|
||||
l.append(numbers(y))
|
||||
return l
|
||||
|
||||
|
||||
class AddBookPage(WizardPanel):
|
||||
def init_widgets(self):
|
||||
self.author = SingleEdit("Author: ")
|
||||
self.title = SingleEdit("Title: ")
|
||||
self.year = SingleIntEdit("Year(s): ")
|
||||
self.widgets = [
|
||||
urwid.Text("Add Book"),
|
||||
urwid.Divider(),
|
||||
self.author,
|
||||
self.title,
|
||||
self.year,
|
||||
]
|
||||
|
||||
def check(self):
|
||||
author = self.author.get_edit_text()
|
||||
if author == "":
|
||||
author = None #null it so that searching ignores
|
||||
title = self.title.get_edit_text()
|
||||
if title == "":
|
||||
title = None
|
||||
try:
|
||||
year = self.year.get_edit_text()
|
||||
if year == "":
|
||||
year = None
|
||||
else:
|
||||
year = int(year)
|
||||
except:
|
||||
self.focus_widget(self.year)
|
||||
set_status("Invalid year")
|
||||
return True
|
||||
lib.add(author, title, year)
|
||||
raise_back()
|
||||
|
||||
|
||||
|
||||
class SearchPage(WizardPanel):
|
||||
def init_widgets(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"),
|
||||
urwid.Divider(),
|
||||
self.author,
|
||||
self.title,
|
||||
self.year,
|
||||
urwid.Divider(),
|
||||
urwid.Text("Author/Title are regexes.\nYear is a comma-separated list or a hyphen-separated range")
|
||||
]
|
||||
def check(self):
|
||||
author = self.author.get_edit_text()
|
||||
if author == "":
|
||||
author = None #null it so that searching ignores
|
||||
title = self.title.get_edit_text()
|
||||
if title == "":
|
||||
title = None
|
||||
try:
|
||||
years = self.year.get_edit_text()
|
||||
if years == "":
|
||||
years = None
|
||||
else:
|
||||
#try to parse the year field
|
||||
years = parse_commaranges( year )
|
||||
except:
|
||||
self.focus_widget(self.year)
|
||||
set_status("Invalid year")
|
||||
return True
|
||||
signedout = self.signedout.get_state()
|
||||
view_books(lib.search(author, title, years, 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
|
||||
self.user = SingleEdit("Checkoutee: ")
|
||||
self.widgets = [
|
||||
urwid.Text("Checkout A Book"),
|
||||
urwid.Divider(),
|
||||
self.book,
|
||||
self.user,
|
||||
]
|
||||
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
|
||||
push_window(ConfirmDialog(msg))
|
||||
|
||||
class InputDialog(urwid.WidgetWrap):
|
||||
def __init__(self, msg=None):
|
||||
msg = urwid.Text(msg)
|
||||
self.input = SingleEdit("")
|
||||
ok = urwid.Button("OK", self.ok)
|
||||
cancel = urwid.Button("Cancel", self.cancel)
|
||||
buttons = urwid.Columns([ok, cancel])
|
||||
display = urwid.Pile([msg, self.input, buttons])
|
||||
urwid.WidgetWrap.__init__(self, display)
|
||||
def ok():
|
||||
self.result = self.input.get_edit_text()
|
||||
raise Abort() #break out of the inner event loop
|
||||
def cancel():
|
||||
self.result = None
|
||||
raise Abort()
|
||||
|
||||
def urwid_input(msg):
|
||||
#this should be in widgets.py
|
||||
dialog = InputDialog(msg)
|
||||
push_window(dialog)
|
||||
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")
|
||||
else:
|
||||
book.sign_out(username)
|
||||
|
||||
def do_delete(book):
|
||||
if Confirm("Do you wish to delete %r?" % book):
|
||||
lib.delete(book)
|
||||
|
||||
class BookPage(urwid.WidgetWrap):
|
||||
def __init__(self, book):
|
||||
self._book = book
|
||||
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")
|
||||
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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue