4d6118fab4da7e8406da4d07ca1dc021dfe6c709
[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(AND(lib.Signout.q.outdate<oldest, lib.Signout.q.indate==None))
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                 pop_window()
138             else:
139                 set_status("Book already exists, fucker.")
140         except PyMazonError, e:
141             set_status("Amazon thinks this is not a book.  Take it up with them.")
142         return False
143         
144
145 class BookSearchPage(WizardPanel):
146     """
147     The page used when searching for books.
148     """
149     def init_widgets(self):
150         """
151         Initialize the widgets and state variables.
152         """
153         self.search = None
154         self.state["book"] = None
155         self.isbn = SingleEdit("ISBN: ")
156         self.title = SingleEdit("Title: ")
157
158         self.widgets = [
159             urwid.Text("Book Search"),
160             urwid.Text("(Only one field required.)"),
161             urwid.Divider(),
162             self.isbn,
163             self.title
164         ]
165
166     def check(self):
167         """
168         Validate input and update state.
169         """
170         if self.state["book"] is None:
171             push_window(SearchPage(self.isbn.get_edit_text(),
172                                    self.title.get_edit_text(),
173                                    None,
174                                    self.state))
175             return True
176         else:
177             return False
178         
179
180 class CheckoutPage(WizardPanel):
181     """
182     The initial page when checking out a book.
183     """
184     def init_widgets(self):
185         """
186         Initialize widgets and set up state.
187
188         user -> the username to sign the book to
189         task -> used for the confirmation dialog
190         """
191         self.state["user"] = "ERROR"
192         self.state["task"] = "sign_out"
193         self.user = LdapWordEdit(csclub_uri, csclub_base, 'uid', "Username: ")
194         
195         self.widgets = [
196             urwid.Text("Book Checkout"),
197             urwid.Divider(),
198             self.user,
199         ]
200
201     def check(self):
202         self.state['user'] = self.user.get_edit_text()
203         if not members.registered(self.state['user'], terms.current()):
204             set_status("User not registered for this term!")
205             return True
206         return False
207
208 class ConfirmPage(WizardPanel):
209     """
210     The confirmation screen when checking-in and checking-out
211     a book.
212     """
213     def init_widgets(self):
214         """
215         Initialize widgets and state.
216
217         task -> used to deterimine the action
218         """
219         self.user = urwid.Text("Username: ")
220         self.book = urwid.Text("Book: ")
221
222         title = ""
223         if self.state["task"] and self.state["task"]=="sign_in":
224             title = "Checkin"
225         else:
226             title = "Checkout"
227
228         self.widgets = [
229             urwid.Text("Confirm " + title),
230             urwid.Divider(),
231             self.user,
232             self.book
233         ]
234
235     def activate(self):
236         """
237         Ensures that correct data is displayed.
238         """
239         self.user.set_text("Username: " + self.state["user"])
240         if self.state["book"]:
241             self.book.set_text("Book: " + self.state["book"].title)
242
243     def check(self):
244         """
245         Generally used for validation, but in this case it does
246         the actual book check-out.
247         """
248         #TODO: Validate user at some point (preferrably user entry screen)
249         if self.state["task"] and self.state["task"]=="sign_in":
250             self.state["book"].sign_in(self.state["user"])
251         else:
252             self.state["book"].sign_out(self.state["user"])
253         pop_window()
254
255         
256 class SearchPage(urwid.WidgetWrap):
257     """
258     Displays search results.  Can search on isbn,
259     title, or username (for books that are currently
260     out).
261     """
262     def __init__(self, isbn, title, user, state):
263         """
264         This does the actual search, and sets up the screen
265         when it's done.
266
267         title -> search by (partial) title
268         isbn -> search by (partial) isbn
269         user -> search by username (for checked-out books)
270         """
271         self.state = state
272         books = []
273         widgets = []
274         if not title is None and not title=="":
275             books = lib.Book.select(LIKE(lib.Book.q.title, "%" + title + "%"))
276         elif not isbn is None and not isbn=="":
277             books = lib.Book.select(lib.Book.q.isbn==isbn)
278         elif (not (user is None)) and (not (user=="")):
279             st = lib.Signout.select(AND(lib.Signout.q.username==user, lib.Signout.q.indate==None))
280             for s in st:
281                 books.append(s.book)
282         else:
283             st = lib.Signout.select(lib.Signout.q.indate==None)
284             for s in st:
285                 books.append(s.book)
286
287         for b in books:
288             widgets.append(urwid.AttrWrap(ButtonText(self.select, b, str(b)),
289                                           None, 'selected'))
290             widgets.append(urwid.Divider())
291
292         urwid.WidgetWrap.__init__(self, urwid.ListBox(widgets))
293
294     def select(self, book):
295         """
296         Marks a book for check-in or check-out.
297         """
298         self.state["book"] = book
299         pop_window()
300
301 class CheckinPage(WizardPanel):
302     """
303     The initial page to start the check-in widget.
304     """
305     def init_widgets(self):
306         """
307         Throw some widgets on the screen and set up
308         some state.
309
310         book -> The book to check out.
311         user -> Stupid people like books.
312         task -> What are we doing?  (For confirm screen.)
313         """
314         self.state["book"] = None
315         self.state["user"] = "ERROR"
316         self.state["task"] = "sign_in"
317         self.user = LdapWordEdit(csclub_uri, csclub_base, 'uid', "Username: ")
318         
319         self.widgets = [
320             urwid.Text("Book Checkin"),
321             urwid.Divider(),
322             self.user,
323         ]
324
325     def check(self):
326         """
327         Pushes the search window.
328
329         Should validate usernames.
330         """
331         if self.state["book"] is None:
332             push_window(SearchPage(None,
333                                    None,
334                                    self.user.get_edit_text(),
335                                    self.state))
336             return True
337         else:
338             self.state["user"] = self.user.get_edit_text()
339             return False