Correct book counting.
[mspang/pyceo.git] / ceo / urwid / library.py
1 import urwid
2 from ceo import members
3 from ceo.urwid import search
4 from ceo.urwid.widgets import *
5 from ceo.urwid.window import *
6 from sqlobject.sqlbuilder import *
7 from datetime import datetime, timedelta
8 from ceo.pymazon import PyMazon
9 from ceo.pymazon import PyMazonError
10 from ceo import conf
11
12 from ceo import terms
13
14 import ceo.library as lib
15
16 CONFIG_FILE = "/etc/csc/library.cf"
17
18 cfg = {}
19
20 def configure():
21     """
22     Load configuration
23     """
24     cfg_fields = [ "aws_account_key" ]
25     temp_cfg = conf.read(CONFIG_FILE)
26     conf.check_string_fields(CONFIG_FILE, cfg_fields, temp_cfg)
27     cfg.update(temp_cfg)
28
29 def library(data):
30     """
31     Create the main menu for the library system.
32     """
33     menu = make_menu([
34         ("Checkout Book", checkout_book, None),
35         ("Return Book", return_book, None),
36         ("Search Books", search_books, None),
37         ("Add Book", add_book, None),
38         ("Back", raise_back, None),
39     ])
40     push_window(menu, "Library")
41
42 def search_books(data):
43     """
44     Define menus for searching books.
45     """
46     menu = make_menu([
47         ("Overdue Books", overdue_books, None),
48         ("Signed Out Books", outbooks_search, None),
49     ])
50     push_window(menu, "Book Search")
51
52 def add_book(data):
53     """
54     Add book to library.  Also stab Sapphyre.
55     """
56     push_wizard("Add Book", [BookAddPage])
57
58 def overdue_books(data):
59     """
60     Display a list of all books that are overdue.
61     """
62     oldest = datetime.today() - timedelta(weeks=2)
63     overdue = lib.Signout.select(lib.Signout.q.outdate<oldest)
64
65     widgets = []
66
67     for s in overdue:
68         widgets.append(urwid.AttrWrap(ButtonText(None, s.book, str(s.book)),
69                                       None, 'selected'))
70         widgets.append(urwid.Divider())
71         
72     push_window(urwid.ListBox(widgets))
73
74     None
75
76 def outbooks_search(data):
77     """
78     Display a list of all books that are signed out.
79     """
80     overdue = lib.Signout.select(lib.Signout.q.indate==None)
81
82     widgets = []
83
84     for s in overdue:
85         widgets.append(urwid.AttrWrap(ButtonText(None, s.book, str(s.book)),
86                                       None, 'selected'))
87         widgets.append(urwid.Divider())
88         
89     push_window(urwid.ListBox(widgets))
90
91     None
92
93
94 def checkout_book(data):
95     """
96     Display the book checkout wizard.
97     """
98     push_wizard("Checkout", [CheckoutPage, BookSearchPage, ConfirmPage])
99
100 def return_book(data):
101     """
102     Display the book return wizard.
103     """
104     push_wizard("Checkout", [CheckinPage, ConfirmPage])
105
106 class BookAddPage(WizardPanel):
107     """
108     Thingy for going on screen to add books.
109     """
110     def init_widgets(self):
111         """
112         Make some widgets.
113         """
114         self.isbn = SingleEdit("ISBN: ")
115         
116         self.widgets = [
117             urwid.Text("Adding New Book"),
118             urwid.Divider(),
119             self.isbn
120         ]
121
122     def check(self):
123         """
124         Do black magic.
125         """
126         configure()
127         isbn = self.isbn.get_edit_text()
128
129         try:
130             pymazon = PyMazon(cfg["aws_account_key"])
131             book = pymazon.lookup(isbn)
132
133             currents = lib.Book.select(lib.Book.q.isbn==isbn)
134             if currents.count() == 0:
135                 lib.Book(isbn=isbn, title=book.title,
136                          year=book.year, publisher=book.publisher)
137             else:
138                 sys.stderr.write("Fuck you.\n")
139                 set_status("Book already exists, fucker.")
140                 
141         except PyMazonError, e:
142             sys.stderr.write("Book not added: " + e.message + "\n")
143             set_status("Amazon thinks this is not a book.  Take it up with them.")
144         
145
146 class BookSearchPage(WizardPanel):
147     """
148     The page used when searching for books.
149     """
150     def init_widgets(self):
151         """
152         Initialize the widgets and state variables.
153         """
154         self.search = None
155         self.state["book"] = None
156         self.isbn = SingleEdit("ISBN: ")
157         self.title = SingleEdit("Title: ")
158
159         self.widgets = [
160             urwid.Text("Book Search"),
161             urwid.Text("(Only one field required.)"),
162             urwid.Divider(),
163             self.isbn,
164             self.title
165         ]
166
167     def check(self):
168         """
169         Validate input and update state.
170         """
171         if self.state["book"] is None:
172             push_window(SearchPage(self.isbn.get_edit_text(),
173                                    self.title.get_edit_text(),
174                                    None,
175                                    self.state))
176             return True
177         else:
178             return False
179         
180
181 class CheckoutPage(WizardPanel):
182     """
183     The initial page when checking out a book.
184     """
185     def init_widgets(self):
186         """
187         Initialize widgets and set up state.
188
189         user -> the username to sign the book to
190         task -> used for the confirmation dialog
191         """
192         self.state["user"] = "ERROR"
193         self.state["task"] = "sign_out"
194         self.user = LdapWordEdit(csclub_uri, csclub_base, 'uid', "Username: ")
195         
196         self.widgets = [
197             urwid.Text("Book Checkout"),
198             urwid.Divider(),
199             self.user,
200         ]
201
202     def check(self):
203         self.state['user'] = self.user.get_edit_text()
204         if not members.registered(self.state['user'], terms.current()):
205             set_status("User not registered for this term!")
206             return True
207         return False
208
209 class ConfirmPage(WizardPanel):
210     """
211     The confirmation screen when checking-in and checking-out
212     a book.
213     """
214     def init_widgets(self):
215         """
216         Initialize widgets and state.
217
218         task -> used to deterimine the action
219         """
220         self.user = urwid.Text("Username: ")
221         self.book = urwid.Text("Book: ")
222
223         title = ""
224         if self.state["task"] and self.state["task"]=="sign_in":
225             title = "Checkin"
226         else:
227             title = "Checkout"
228
229         self.widgets = [
230             urwid.Text("Confirm " + title),
231             urwid.Divider(),
232             self.user,
233             self.book
234         ]
235
236     def activate(self):
237         """
238         Ensures that correct data is displayed.
239         """
240         self.user.set_text("Username: " + self.state["user"])
241         if self.state["book"]:
242             self.book.set_text("Book: " + self.state["book"].title)
243
244     def check(self):
245         """
246         Generally used for validation, but in this case it does
247         the actual book check-out.
248         """
249         #TODO: Validate user at some point (preferrably user entry screen)
250         if self.state["task"] and self.state["task"]=="sign_in":
251             self.state["book"].sign_in(self.state["user"])
252         else:
253             self.state["book"].sign_out(self.state["user"])
254         pop_window()
255
256         
257 class SearchPage(urwid.WidgetWrap):
258     """
259     Displays search results.  Can search on isbn,
260     title, or username (for books that are currently
261     out).
262     """
263     def __init__(self, isbn, title, user, state):
264         """
265         This does the actual search, and sets up the screen
266         when it's done.
267
268         title -> search by (partial) title
269         isbn -> search by (partial) isbn
270         user -> search by username (for checked-out books)
271         """
272         self.state = state
273         books = []
274         widgets = []
275         if not title is None and not title=="":
276             books = lib.Book.select(LIKE(lib.Book.q.title, "%" + title + "%"))
277         elif not isbn is None and not isbn=="":
278             books = lib.Book.select(lib.Book.q.isbn==isbn)
279         elif not user is None and not user=="":
280             st = lib.Signout.select(AND(lib.Signout.q.username==user, lib.Signout.q.indate==None))
281             for s in st:
282                 books.append(s.book)
283
284         for b in books:
285             widgets.append(urwid.AttrWrap(ButtonText(self.select, b, str(b)),
286                                           None, 'selected'))
287             widgets.append(urwid.Divider())
288
289         urwid.WidgetWrap.__init__(self, urwid.ListBox(widgets))
290
291     def select(self, book):
292         """
293         Marks a book for check-in or check-out.
294         """
295         self.state["book"] = book
296         pop_window()
297
298 class CheckinPage(WizardPanel):
299     """
300     The initial page to start the check-in widget.
301     """
302     def init_widgets(self):
303         """
304         Throw some widgets on the screen and set up
305         some state.
306
307         book -> The book to check out.
308         user -> Stupid people like books.
309         task -> What are we doing?  (For confirm screen.)
310         """
311         self.state["book"] = None
312         self.state["user"] = "ERROR"
313         self.state["task"] = "sign_in"
314         self.user = LdapWordEdit(csclub_uri, csclub_base, 'uid', "Username: ")
315         
316         self.widgets = [
317             urwid.Text("Book Checkin"),
318             urwid.Divider(),
319             self.user,
320         ]
321
322     def check(self):
323         """
324         Pushes the search window.
325
326         Should validate usernames.
327         """
328         if self.state["book"] is None:
329             push_window(SearchPage(None,
330                                    None,
331                                    self.user.get_edit_text(),
332                                    self.state))
333             return True
334         else:
335             self.state["user"] = self.user.get_edit_text()
336             return False