|
1 # coding: utf-8 |
|
2 |
|
3 from svv import database as db |
|
4 from svv.controllers import PageHandler |
|
5 from svv.html import tags as html |
|
6 from svv.forms import BaseForm, FormError |
|
7 |
|
8 import logging |
|
9 |
|
10 log = logging.getLogger('svv.items') |
|
11 |
|
12 class Item (object) : |
|
13 """ |
|
14 Data-mapping for the items table |
|
15 """ |
|
16 |
|
17 db.mapper(Item, db.items, properties=dict( |
|
18 # forward ref to parent |
|
19 parent = db.relation(Item, remote_side=db.items.c.id, |
|
20 backref = db.backref('children', remote_side=db.items.c.parent_id) |
|
21 ), |
|
22 )) |
|
23 |
|
24 |
|
25 class ItemForm (BaseForm) : |
|
26 """ |
|
27 Form for editing item |
|
28 """ |
|
29 |
|
30 def __init__ (self, session) : |
|
31 """ |
|
32 Use given database session for rendering form. |
|
33 """ |
|
34 |
|
35 super(ItemForm, self).__init__() |
|
36 |
|
37 self.session = session |
|
38 |
|
39 def defaults (self) : |
|
40 self.item_id = None |
|
41 self.item_name = None |
|
42 self.item_detail = None |
|
43 self.item_quantity = None |
|
44 |
|
45 self.item_parent_id = None |
|
46 |
|
47 def load (self, item) : |
|
48 """ |
|
49 Load info from existing item for editing |
|
50 """ |
|
51 |
|
52 self.item_id = item.id |
|
53 self.item_name = item.name |
|
54 self.item_detail = item.detail |
|
55 self.item_quantity = item.quantity |
|
56 |
|
57 self.item_parent_id = item.parent.id if item.parent else None |
|
58 |
|
59 def render_parent_select (self, name, parent_id) : |
|
60 """ |
|
61 Render <select> for parent |
|
62 """ |
|
63 |
|
64 # existing items suitable as parents |
|
65 # XXX: currently only top-level items, as a heuristic |
|
66 parent_items = self.session.query(Item.id, Item.name).filter(Item.parent == None).all() |
|
67 |
|
68 return ( |
|
69 # <select> with options and selected= |
|
70 self.render_select_input(name, [(0, u"---")] + parent_items, parent_id) |
|
71 ) |
|
72 |
|
73 def render (self, action, legend=u"Laitejuttu", return_url=None, delete_action=None) : |
|
74 """ |
|
75 Render <form> HTML |
|
76 |
|
77 return_url - URL for reset button |
|
78 delete_action - (optional) URL to delete-item option |
|
79 """ |
|
80 |
|
81 return ( |
|
82 html.form(action=action, method='POST')( |
|
83 html.fieldset( |
|
84 html.legend(legend), |
|
85 |
|
86 html.ol( |
|
87 self.render_form_field('item_parent', u"Case", u"Missä laite sijaitsee", ( |
|
88 self.render_parent_select('item_parent', self.item_parent_id) |
|
89 )), |
|
90 |
|
91 self.render_form_field('item_name', u"Nimi", u"Lyhyt nimi", ( |
|
92 self.render_text_input('item_name', self.item_name) |
|
93 )), |
|
94 |
|
95 self.render_form_field('item_detail', u"Kuvaus", u"Tarkempi kuvaus", ( |
|
96 self.render_text_input('item_detail', self.item_detail, multiline=True) |
|
97 )), |
|
98 |
|
99 self.render_form_field('item_quantity', u"Kappalemäärä", u"Jos on esim. useampi piuha/adapter, kirjaa niitten määrä; muuten jätä tyhjäksi", ( |
|
100 self.render_text_input('item_quantity', self.item_quantity) |
|
101 )), |
|
102 |
|
103 html.li( |
|
104 self.render_submit_button(u"Tallenna"), |
|
105 self.render_reset_button(u"Palaa inventaariin", return_url) if return_url else None, |
|
106 ), |
|
107 ), |
|
108 ), |
|
109 ), |
|
110 |
|
111 html.form(action=delete_action, method='POST')( |
|
112 html.fieldset( |
|
113 html.legend(u"Poistaminen"), |
|
114 |
|
115 html.input(type='hidden', name='items', value=self.item_id), |
|
116 |
|
117 html.ol( |
|
118 html.li( |
|
119 html.input(type='submit', name='delete', value=u"Poista"), |
|
120 ) |
|
121 ) |
|
122 ), |
|
123 ) if delete_action else None |
|
124 ) |
|
125 |
|
126 def process (self, data) : |
|
127 """ |
|
128 Process incoming POST data |
|
129 """ |
|
130 |
|
131 # bind |
|
132 self.data = data |
|
133 |
|
134 self.do_delete = self.process_action_field('delete') |
|
135 |
|
136 if not self.do_delete : |
|
137 try : |
|
138 # XXX: self.process_id_field? |
|
139 self.item_parent_id = self.process_integer_field('item_parent') |
|
140 |
|
141 except FormError, e : |
|
142 self.fail_field(e, 'item_parent') |
|
143 |
|
144 try : |
|
145 self.item_name = self.process_text_field('item_name', required=True) |
|
146 |
|
147 except FormError, e : |
|
148 self.fail_field(e, 'item_name') |
|
149 |
|
150 try : |
|
151 self.item_detail = self.process_text_field('item_detail') |
|
152 |
|
153 except FormError, e : |
|
154 self.fail_field(e, 'item_detail') |
|
155 |
|
156 try : |
|
157 self.item_quantity = self.process_integer_field('item_quantity') |
|
158 |
|
159 except FormError, e : |
|
160 self.fail_field(e, 'item_quantity') |
|
161 |
|
162 |
|
163 # ok? |
|
164 return not self.errors |
|
165 |
|
166 class DeleteItemForm (BaseForm) : |
|
167 """ |
|
168 Display a list of items to delete, and confirm |
|
169 """ |
|
170 |
|
171 def __init__ (self, session) : |
|
172 """ |
|
173 Use given database session for rendering form. |
|
174 """ |
|
175 |
|
176 super(DeleteItemForm, self).__init__() |
|
177 |
|
178 self.session = session |
|
179 |
|
180 # list of items to delete |
|
181 self.items = [] |
|
182 |
|
183 def render_items_list (self, name, items) : |
|
184 """ |
|
185 Render list of items to delete. |
|
186 """ |
|
187 |
|
188 def render_items (items) : |
|
189 return ( |
|
190 html.ul( |
|
191 render_item(item) for item in items |
|
192 ) if items else None |
|
193 ) |
|
194 |
|
195 |
|
196 def render_item (item) : |
|
197 return html.li( |
|
198 html.input(type='hidden', name='items', value=item.id), |
|
199 item.name, |
|
200 render_items(item.children), |
|
201 ) |
|
202 |
|
203 return html.div(class_='value')( |
|
204 render_items(items), |
|
205 ) |
|
206 |
|
207 def render (self, action, return_url) : |
|
208 """ |
|
209 Render form with list of target items, and a confirm button |
|
210 """ |
|
211 |
|
212 return html.form(action=action, method='POST')( |
|
213 html.fieldset( |
|
214 html.legend(u"Poistettavat laitteet"), |
|
215 |
|
216 html.ol( |
|
217 self.render_form_field('items', u"Poistettavat laitteet", u"Kaikki listatut laitteet poistetaan inventaarista", ( |
|
218 self.render_items_list('items', self.items) |
|
219 )), |
|
220 |
|
221 html.li( |
|
222 self.render_submit_button(u"Varmista", 'confirm'), |
|
223 |
|
224 self.render_reset_button(u"Peruuta", return_url), |
|
225 ), |
|
226 ) |
|
227 ) |
|
228 ) |
|
229 |
|
230 def process_items_list (self, name) : |
|
231 """ |
|
232 Uses the incoming list of id's to build the list of Item's to delete. |
|
233 """ |
|
234 |
|
235 # incoming ids |
|
236 item_ids = self.process_list_field('items', type=int) |
|
237 |
|
238 # look up |
|
239 items = self.session.query(Item).filter(Item.id.in_(item_ids)).all() |
|
240 |
|
241 # make sure they all exist |
|
242 found = [item_id for item_id in item_ids if item_id in set(item.id for item in items)] |
|
243 |
|
244 if set(found) != set(item_ids) : |
|
245 raise FormError(name, found, "Some items were not found") |
|
246 |
|
247 if not items : |
|
248 raise FormError(name, [], "No items were given") |
|
249 |
|
250 # ok |
|
251 return items |
|
252 |
|
253 def process (self, data) : |
|
254 """ |
|
255 Look up list of Item's to delete. |
|
256 |
|
257 Returns True if the delete is confirmed, False otherwise. |
|
258 """ |
|
259 |
|
260 # bind |
|
261 self.data = data |
|
262 |
|
263 # load items |
|
264 try : |
|
265 self.items = self.process_items_list('items') |
|
266 |
|
267 except FormError, e : |
|
268 self.fail_field(e, 'items') |
|
269 |
|
270 # confirm? |
|
271 confirm = self.process_action_field('confirm') |
|
272 |
|
273 return not self.errors and confirm |
|
274 |
|
275 class ItemView (PageHandler) : |
|
276 """ |
|
277 Display/edit info for a single item |
|
278 """ |
|
279 |
|
280 def update (self, item, form) : |
|
281 """ |
|
282 Update item data from form |
|
283 """ |
|
284 |
|
285 # lookup |
|
286 item.parent = self.session.query(Item).get(form.item_parent_id) |
|
287 |
|
288 # modify |
|
289 item.name = form.item_name |
|
290 item.detail = form.item_detail |
|
291 item.quantity = form.item_quantity |
|
292 |
|
293 # update |
|
294 self.session.commit() |
|
295 |
|
296 def process (self, id) : |
|
297 """ |
|
298 Update item data if POST'd |
|
299 """ |
|
300 |
|
301 # db |
|
302 self.session = self.app.session() |
|
303 |
|
304 # item in concern |
|
305 self.item = self.session.query(Item).get(id) |
|
306 |
|
307 # form |
|
308 self.form = ItemForm(self.session) |
|
309 self.form.load(self.item) |
|
310 |
|
311 # process? |
|
312 if self.POST : |
|
313 if self.form.process(self.POST) : |
|
314 # update |
|
315 self.update(self.item, self.form) |
|
316 |
|
317 # ok, done with item, redirect to full list view... |
|
318 return self.redirect_for(InventoryView, fragment=('item-%d' % self.item.id)) |
|
319 |
|
320 else : |
|
321 # re-render form |
|
322 return |
|
323 |
|
324 def render_content (self, id) : |
|
325 """ |
|
326 View item's info |
|
327 """ |
|
328 |
|
329 return ( |
|
330 html.h1(u"Laite #%d" % self.item.id), |
|
331 |
|
332 self.form.render( |
|
333 action = self.url_for(ItemView, id=id), |
|
334 return_url = self.url_for(InventoryView), |
|
335 delete_action = self.url_for(DeleteItemView), |
|
336 ), |
|
337 ) |
|
338 |
|
339 class NewItemView (PageHandler) : |
|
340 """ |
|
341 Create new item |
|
342 """ |
|
343 |
|
344 def create (self, form) : |
|
345 """ |
|
346 Create and return new item from ItemForm data. |
|
347 """ |
|
348 |
|
349 item = Item() |
|
350 |
|
351 # validate and lookup parent |
|
352 item.parent = self.session.query(Item).get(form.item_parent_id) |
|
353 |
|
354 # store |
|
355 item.name = form.item_name |
|
356 item.detail = form.item_detail |
|
357 item.quantity = form.item_quantity |
|
358 |
|
359 # insert |
|
360 self.session.add(item) |
|
361 self.session.commit() |
|
362 |
|
363 # ok |
|
364 return item |
|
365 |
|
366 def process (self) : |
|
367 # db |
|
368 self.session = self.app.session() |
|
369 |
|
370 self.form = ItemForm(self.session) |
|
371 |
|
372 # load POST |
|
373 if self.POST : |
|
374 if self.form.process(self.POST) : |
|
375 # create |
|
376 item = self.create(self.form) |
|
377 |
|
378 # redirect to full list view... |
|
379 return self.redirect_for(InventoryView, fragment=('item-%d' % item.id)) |
|
380 |
|
381 else : |
|
382 # fail, just render... |
|
383 return |
|
384 |
|
385 else : |
|
386 # render blank form |
|
387 self.form.defaults() |
|
388 |
|
389 def render_content (self) : |
|
390 """ |
|
391 Render the proecss()'d form |
|
392 """ |
|
393 |
|
394 return ( |
|
395 self.form.render(action=self.url_for(NewItemView), legend=u"Lisää uusi", delete=True) |
|
396 ) |
|
397 |
|
398 class DeleteItemView (PageHandler) : |
|
399 """ |
|
400 Confirm deletion of items. |
|
401 """ |
|
402 |
|
403 def delete (self, form) : |
|
404 """ |
|
405 Delete items from form |
|
406 """ |
|
407 |
|
408 # list of root Item's to delete, along with children |
|
409 items = form.items |
|
410 |
|
411 for item in items : |
|
412 self.session.delete(item) |
|
413 |
|
414 # ok |
|
415 self.session.commit() |
|
416 |
|
417 def process (self) : |
|
418 # db |
|
419 self.session = self.app.session() |
|
420 |
|
421 # form |
|
422 self.form = DeleteItemForm(self.session) |
|
423 |
|
424 # process |
|
425 if self.form.process(self.POST) : |
|
426 # delete |
|
427 self.delete(self.form) |
|
428 |
|
429 # redirect back |
|
430 return self.redirect_for(InventoryView) |
|
431 |
|
432 else : |
|
433 # render |
|
434 pass |
|
435 |
|
436 def render_content (self) : |
|
437 return ( |
|
438 self.form.render(action=self.url_for(DeleteItemView), return_url=self.url_for(InventoryView)) |
|
439 ) |
|
440 |
|
441 class InventoryView (PageHandler) : |
|
442 """ |
|
443 Display overview of all items |
|
444 """ |
|
445 |
|
446 def process (self) : |
|
447 # db |
|
448 self.session = self.app.session() |
|
449 |
|
450 def render_item_table (self) : |
|
451 """ |
|
452 Render HTML for full <table> of all items, sorted heirarchially (by parent) |
|
453 """ |
|
454 |
|
455 # listing of inventory items |
|
456 items = self.session.query(Item).order_by(Item.parent).all() |
|
457 |
|
458 return html.table( |
|
459 html.caption("Kalustolistaus"), |
|
460 |
|
461 html.thead( |
|
462 html.tr( |
|
463 html.th(title) for title in ( |
|
464 u"#ID", |
|
465 u"Case", |
|
466 u"Nimi", |
|
467 u"Kuvaus", |
|
468 u"Määrä", |
|
469 ) |
|
470 ), |
|
471 ), |
|
472 |
|
473 html.tbody( |
|
474 html.tr(id=('item-%d' % item.id))( |
|
475 html.td( |
|
476 html.a(href=self.url_for(ItemView, id=item.id))( |
|
477 u'#%d' % item.id |
|
478 ) |
|
479 ), |
|
480 |
|
481 html.td( |
|
482 html.a(href=self.url_for(ItemView, id=item.parent.id))( |
|
483 item.parent.name |
|
484 ) if item.parent else None |
|
485 ), |
|
486 |
|
487 html.td( |
|
488 html.a(href=self.url_for(ItemView, id=item.id))( |
|
489 item.name |
|
490 ) |
|
491 ), |
|
492 |
|
493 html.td( |
|
494 item.detail |
|
495 ), |
|
496 |
|
497 html.td( |
|
498 "%d kpl" % item.quantity if item.quantity else None |
|
499 ), |
|
500 ) for item in items |
|
501 ) |
|
502 ) |
|
503 |
|
504 def render_item_form (self) : |
|
505 """ |
|
506 Render ItemForm for creating a new item |
|
507 """ |
|
508 |
|
509 form = ItemForm(self.session) |
|
510 form.defaults() |
|
511 |
|
512 return form.render(action=self.url_for(NewItemView), legend=u"Lisää uusi") |
|
513 |
|
514 def render_content (self) : |
|
515 """ |
|
516 Full listing of all inventory |
|
517 """ |
|
518 |
|
519 return ( |
|
520 html.h1(u"Inventaari"), |
|
521 |
|
522 self.render_item_table(), |
|
523 |
|
524 self.render_item_form(), |
|
525 ) |
|
526 |