terom@48: # coding: utf-8 terom@48: terom@48: from svv import database as db terom@48: from svv.controllers import PageHandler terom@48: from svv.html import tags as html terom@48: from svv.forms import BaseForm, FormError terom@48: terom@48: import logging terom@48: terom@48: log = logging.getLogger('svv.items') terom@48: terom@48: class Item (object) : terom@48: """ terom@48: Data-mapping for the items table terom@48: """ terom@48: terom@48: db.mapper(Item, db.items, properties=dict( terom@48: # forward ref to parent terom@48: parent = db.relation(Item, remote_side=db.items.c.id, terom@48: backref = db.backref('children', remote_side=db.items.c.parent_id) terom@48: ), terom@48: )) terom@48: terom@48: terom@48: class ItemForm (BaseForm) : terom@48: """ terom@48: Form for editing item terom@48: """ terom@48: terom@48: def __init__ (self, session) : terom@48: """ terom@48: Use given database session for rendering form. terom@48: """ terom@48: terom@48: super(ItemForm, self).__init__() terom@48: terom@48: self.session = session terom@48: terom@48: def defaults (self) : terom@48: self.item_id = None terom@48: self.item_name = None terom@48: self.item_detail = None terom@48: self.item_quantity = None terom@48: terom@48: self.item_parent_id = None terom@48: terom@48: def load (self, item) : terom@48: """ terom@48: Load info from existing item for editing terom@48: """ terom@48: terom@48: self.item_id = item.id terom@48: self.item_name = item.name terom@48: self.item_detail = item.detail terom@48: self.item_quantity = item.quantity terom@48: terom@48: self.item_parent_id = item.parent.id if item.parent else None terom@48: terom@48: def render_parent_select (self, name, parent_id) : terom@48: """ terom@48: Render with options and selected= terom@48: self.render_select_input(name, [(0, u"---")] + parent_items, parent_id) terom@48: ) terom@48: terom@53: def render (self, action, legend=u"Laitetiedot", return_url=None) : terom@48: """ terom@48: Render
HTML terom@48: terom@48: return_url - URL for reset button terom@48: """ terom@48: terom@53: return html.form(action=action, method='POST')( terom@53: html.fieldset( terom@53: html.legend(legend), terom@48: terom@53: html.ol( terom@53: self.render_form_field('item_parent', u"Case", u"Missä laite sijaitsee", ( terom@53: self.render_parent_select('item_parent', self.item_parent_id) terom@53: )), terom@48: terom@53: self.render_form_field('item_name', u"Nimi", u"Lyhyt nimi", ( terom@53: self.render_text_input('item_name', self.item_name) terom@53: )), terom@53: terom@53: self.render_form_field('item_detail', u"Kuvaus", u"Tarkempi kuvaus", ( terom@53: self.render_text_input('item_detail', self.item_detail, multiline=True) terom@53: )), terom@53: terom@53: 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", ( terom@53: self.render_text_input('item_quantity', self.item_quantity) terom@53: )), terom@53: terom@53: html.li( terom@53: self.render_submit_button(u"Tallenna"), terom@53: self.render_reset_button(u"Palaa inventaariin", return_url) if return_url else None, terom@48: ), terom@48: ), terom@48: ), terom@48: ) terom@48: terom@48: def process (self, data) : terom@48: """ terom@48: Process incoming POST data terom@48: """ terom@48: terom@48: # bind terom@48: self.data = data terom@48: terom@48: self.do_delete = self.process_action_field('delete') terom@48: terom@48: if not self.do_delete : terom@48: try : terom@48: # XXX: self.process_id_field? terom@48: self.item_parent_id = self.process_integer_field('item_parent') terom@48: terom@48: except FormError, e : terom@48: self.fail_field(e, 'item_parent') terom@48: terom@48: try : terom@48: self.item_name = self.process_text_field('item_name', required=True) terom@48: terom@48: except FormError, e : terom@48: self.fail_field(e, 'item_name') terom@48: terom@48: try : terom@48: self.item_detail = self.process_text_field('item_detail') terom@48: terom@48: except FormError, e : terom@48: self.fail_field(e, 'item_detail') terom@48: terom@48: try : terom@48: self.item_quantity = self.process_integer_field('item_quantity') terom@48: terom@48: except FormError, e : terom@48: self.fail_field(e, 'item_quantity') terom@48: terom@48: terom@48: # ok? terom@48: return not self.errors terom@48: terom@48: class DeleteItemForm (BaseForm) : terom@48: """ terom@48: Display a list of items to delete, and confirm terom@48: """ terom@48: terom@48: def __init__ (self, session) : terom@48: """ terom@48: Use given database session for rendering form. terom@48: """ terom@48: terom@48: super(DeleteItemForm, self).__init__() terom@48: terom@48: self.session = session terom@48: terom@48: # list of items to delete terom@48: self.items = [] terom@48: terom@53: def load (self, items) : terom@48: """ terom@53: Init set of items to delete terom@48: """ terom@48: terom@53: self.items = items terom@48: terom@53: def render_items_list (self, name, items, visible=None) : terom@53: """ terom@53: Render list of items to delete. terom@53: terom@53: visible - display list of items in addition the rendering the 's terom@53: """ terom@53: terom@53: def render_items (items, visible) : terom@53: if not items : terom@53: # blank terom@53: return None terom@53: terom@53: elif visible : terom@53: # nested list terom@53: return html.ul( terom@53: render_item(item, True) for item in items terom@53: ) terom@53: terom@53: else : terom@53: # just the controls terom@53: return [render_item(item, False) for item in items] terom@53: terom@53: def render_item (item, visible) : terom@53: # the form field terom@53: field = html.input(type='hidden', name='items', value=item.id), terom@53: terom@53: if visible : terom@53: return html.li( terom@53: field, terom@53: terom@53: item.name if visible else None, terom@53: terom@53: render_items(item.children, visible), terom@53: ) terom@53: else : terom@53: # just the input terom@53: return field terom@53: terom@53: if visible : terom@53: # div with nested
  • s terom@53: return html.div(class_='value')( terom@53: render_items(items, True), terom@48: ) terom@53: else : terom@53: # just the s terom@53: return render_items(items, False) terom@48: terom@53: def render (self, legend=None, delete_action=None, confirm_action=None, return_url=None, item_listing=None) : terom@48: """ terom@48: Render form with list of target items, and a confirm button terom@53: terom@53: legend - form title terom@53: delete_action - URL for initial confirm stage submit terom@53: confirm_action - URL for final delete stage submit terom@53: reutrn_url - URL for cancel button terom@53: item_listing - display recursive item listing for confirmation terom@53: terom@53: Supply either delete_action or confirm_action, but not both terom@48: """ terom@48: terom@53: action = delete_action or confirm_action terom@53: terom@48: return html.form(action=action, method='POST')( terom@48: html.fieldset( terom@53: html.legend(legend), terom@48: terom@48: html.ol( terom@53: ( terom@53: # full UI field terom@53: (self.render_form_field('items', u"Poistettavat laitteet", u"Kaikki listatut laitteet poistetaan inventaarista", ( terom@53: self.render_items_list('items', self.items, visible=True) terom@53: ))) terom@53: terom@53: if item_listing else terom@53: terom@53: # raw field with just the s terom@53: (self.render_items_list('items', self.items, visible=False)) terom@53: ), terom@48: terom@48: html.li( terom@53: self.render_submit_button(u"Poista") if delete_action else None, terom@53: self.render_submit_button(u"Varmista", 'confirm') if confirm_action else None, terom@48: terom@53: self.render_reset_button(u"Peruuta", return_url) if return_url else None, terom@48: ), terom@48: ) terom@48: ) terom@48: ) terom@48: terom@48: def process_items_list (self, name) : terom@48: """ terom@48: Uses the incoming list of id's to build the list of Item's to delete. terom@48: """ terom@48: terom@48: # incoming ids terom@48: item_ids = self.process_list_field('items', type=int) terom@48: terom@48: # look up terom@48: items = self.session.query(Item).filter(Item.id.in_(item_ids)).all() terom@48: terom@48: # make sure they all exist terom@48: found = [item_id for item_id in item_ids if item_id in set(item.id for item in items)] terom@48: terom@48: if set(found) != set(item_ids) : terom@48: raise FormError(name, found, "Some items were not found") terom@48: terom@48: if not items : terom@48: raise FormError(name, [], "No items were given") terom@48: terom@48: # ok terom@48: return items terom@48: terom@48: def process (self, data) : terom@48: """ terom@48: Look up list of Item's to delete. terom@48: terom@48: Returns True if the delete is confirmed, False otherwise. terom@48: """ terom@48: terom@48: # bind terom@48: self.data = data terom@48: terom@48: # load items terom@48: try : terom@48: self.items = self.process_items_list('items') terom@48: terom@48: except FormError, e : terom@48: self.fail_field(e, 'items') terom@48: terom@48: # confirm? terom@48: confirm = self.process_action_field('confirm') terom@48: terom@48: return not self.errors and confirm terom@48: terom@48: class ItemView (PageHandler) : terom@48: """ terom@48: Display/edit info for a single item terom@48: """ terom@48: terom@48: def update (self, item, form) : terom@48: """ terom@48: Update item data from form terom@48: """ terom@48: terom@48: # lookup terom@48: item.parent = self.session.query(Item).get(form.item_parent_id) terom@48: terom@48: # modify terom@48: item.name = form.item_name terom@48: item.detail = form.item_detail terom@48: item.quantity = form.item_quantity terom@48: terom@48: # update terom@48: self.session.commit() terom@48: terom@48: def process (self, id) : terom@48: """ terom@48: Update item data if POST'd terom@48: """ terom@48: terom@48: # db terom@48: self.session = self.app.session() terom@48: terom@48: # item in concern terom@48: self.item = self.session.query(Item).get(id) terom@48: terom@48: # form terom@48: self.form = ItemForm(self.session) terom@48: self.form.load(self.item) terom@48: terom@48: # process? terom@48: if self.POST : terom@48: if self.form.process(self.POST) : terom@48: # update terom@48: self.update(self.item, self.form) terom@48: terom@48: # ok, done with item, redirect to full list view... terom@48: return self.redirect_for(InventoryView, fragment=('item-%d' % self.item.id)) terom@48: terom@48: else : terom@48: # re-render form terom@48: return terom@48: terom@53: def render_subitems_table (self, subitems) : terom@53: """ terom@53: Render listing of child items in this item terom@53: """ terom@53: terom@53: return ItemTable(self).render(subitems, parent=self.item) terom@53: terom@53: terom@53: def render_delete_form (self) : terom@53: """ terom@53: Render delete form for this item terom@53: """ terom@53: terom@53: form = DeleteItemForm(self.session) terom@53: form.load([self.item]) terom@53: terom@53: return form.render( terom@53: legend = u"Poistaminen", terom@53: terom@53: delete_action = self.url_for(DeleteItemView), terom@53: terom@53: item_listing = False, terom@53: ) terom@53: terom@48: def render_content (self, id) : terom@48: """ terom@48: View item's info terom@48: """ terom@53: terom@53: # ourselves terom@53: item = self.item terom@48: terom@53: # items in this item terom@53: subitems = item.children terom@53: terom@48: return ( terom@53: html.h1("(#%d) %s" % (item.id, item.name)), terom@53: terom@53: html.h3( terom@53: html.raw(u"Sijaitsee %s:ssa" % ( terom@53: html.a(href=self.url_for(ItemView, id=item.parent.id))(item.parent.name) terom@53: )), terom@53: ) if item.parent else None, terom@53: terom@53: self.render_subitems_table(subitems) if subitems else None, terom@48: terom@48: self.form.render( terom@48: action = self.url_for(ItemView, id=id), terom@48: return_url = self.url_for(InventoryView), terom@48: ), terom@53: terom@53: # delete form terom@53: self.render_delete_form(), terom@48: ) terom@48: terom@48: class NewItemView (PageHandler) : terom@48: """ terom@48: Create new item terom@48: """ terom@48: terom@48: def create (self, form) : terom@48: """ terom@48: Create and return new item from ItemForm data. terom@48: """ terom@48: terom@48: item = Item() terom@48: terom@48: # validate and lookup parent terom@48: item.parent = self.session.query(Item).get(form.item_parent_id) terom@48: terom@48: # store terom@48: item.name = form.item_name terom@48: item.detail = form.item_detail terom@48: item.quantity = form.item_quantity terom@48: terom@48: # insert terom@48: self.session.add(item) terom@48: self.session.commit() terom@48: terom@48: # ok terom@48: return item terom@48: terom@48: def process (self) : terom@48: # db terom@48: self.session = self.app.session() terom@48: terom@48: self.form = ItemForm(self.session) terom@48: terom@48: # load POST terom@48: if self.POST : terom@48: if self.form.process(self.POST) : terom@48: # create terom@48: item = self.create(self.form) terom@48: terom@48: # redirect to full list view... terom@48: return self.redirect_for(InventoryView, fragment=('item-%d' % item.id)) terom@48: terom@48: else : terom@48: # fail, just render... terom@48: return terom@48: terom@48: else : terom@48: # render blank form terom@48: self.form.defaults() terom@48: terom@48: def render_content (self) : terom@48: """ terom@48: Render the proecss()'d form terom@48: """ terom@48: terom@48: return ( terom@48: self.form.render(action=self.url_for(NewItemView), legend=u"Lisää uusi", delete=True) terom@48: ) terom@48: terom@48: class DeleteItemView (PageHandler) : terom@48: """ terom@48: Confirm deletion of items. terom@48: """ terom@48: terom@48: def delete (self, form) : terom@48: """ terom@48: Delete items from form terom@48: """ terom@48: terom@48: # list of root Item's to delete, along with children terom@48: items = form.items terom@48: terom@48: for item in items : terom@48: self.session.delete(item) terom@48: terom@48: # ok terom@48: self.session.commit() terom@48: terom@48: def process (self) : terom@48: # db terom@48: self.session = self.app.session() terom@48: terom@48: # form terom@48: self.form = DeleteItemForm(self.session) terom@48: terom@48: # process terom@48: if self.form.process(self.POST) : terom@48: # delete terom@48: self.delete(self.form) terom@48: terom@48: # redirect back terom@48: return self.redirect_for(InventoryView) terom@48: terom@48: else : terom@48: # render terom@48: pass terom@48: terom@48: def render_content (self) : terom@48: return ( terom@53: self.form.render( terom@53: legend = u"Poistettavat laitteet", terom@53: terom@53: confirm_action = self.url_for(DeleteItemView), terom@53: terom@53: return_url = self.url_for(InventoryView), terom@53: terom@53: item_listing = True, terom@53: ) terom@48: ) terom@48: terom@53: class ItemTable (object) : terom@53: """ terom@53: Table of items terom@53: """ terom@53: terom@53: def __init__ (self, handler) : terom@53: """ terom@53: handler - the AppHandler we are running under, used for url_for etc. terom@53: """ terom@53: terom@53: self.handler = handler terom@53: terom@53: def url_for (self, *args, **kwargs) : terom@53: """ terom@53: Proxy to handler terom@53: """ terom@53: terom@53: return self.handler.url_for(*args, **kwargs) terom@53: terom@53: def render (self, items, parent=None) : terom@53: """ terom@53: Render table for given set of items. If parent is given, those items are assumed to be the children of that terom@53: item. terom@53: """ terom@53: terom@53: return html.table( terom@53: html.caption( terom@53: u"Kalustolistaus", terom@53: ( terom@53: html.a(href=self.url_for(ItemView, id=parent.id))( terom@53: parent.name terom@53: ) if parent else None terom@53: ), terom@53: ), terom@53: terom@53: html.thead( terom@53: html.tr( terom@53: html.th(title) for title in ( terom@53: u"#ID", terom@53: u"Case" if not parent else None, terom@53: u"Nimi", terom@53: u"Kuvaus", terom@53: u"Määrä", terom@53: ) if title terom@53: ), terom@53: ), terom@53: terom@53: html.tbody( terom@53: html.tr(id=('item-%d' % item.id))( terom@53: html.td( terom@53: html.a(href=self.url_for(ItemView, id=item.id))( terom@53: u'#%d' % item.id terom@53: ) terom@53: ), terom@53: terom@53: html.td( terom@53: html.a(href=self.url_for(ItemView, id=item.parent.id))( terom@53: item.parent.name terom@53: ) if item.parent else None terom@53: ) if not parent else None, terom@53: terom@53: html.td( terom@53: html.a(href=self.url_for(ItemView, id=item.id))( terom@53: item.name terom@53: ) terom@53: ), terom@53: terom@53: html.td( terom@53: item.detail terom@53: ), terom@53: terom@53: html.td( terom@53: "%d kpl" % item.quantity if item.quantity else None terom@53: ), terom@53: ) for item in items terom@53: ) terom@53: ) terom@53: terom@53: terom@48: class InventoryView (PageHandler) : terom@48: """ terom@48: Display overview of all items terom@48: """ terom@48: terom@48: def process (self) : terom@48: # db terom@48: self.session = self.app.session() terom@48: terom@48: def render_item_table (self) : terom@48: """ terom@48: Render HTML for full of all items, sorted heirarchially (by parent) terom@48: """ terom@48: terom@48: # listing of inventory items terom@48: items = self.session.query(Item).order_by(Item.parent).all() terom@48: terom@53: return ItemTable(self).render(items) terom@48: terom@48: def render_item_form (self) : terom@48: """ terom@48: Render ItemForm for creating a new item terom@48: """ terom@48: terom@48: form = ItemForm(self.session) terom@48: form.defaults() terom@48: terom@48: return form.render(action=self.url_for(NewItemView), legend=u"Lisää uusi") terom@48: terom@48: def render_content (self) : terom@48: """ terom@48: Full listing of all inventory terom@48: """ terom@48: terom@48: return ( terom@48: html.h1(u"Inventaari"), terom@48: terom@48: self.render_item_table(), terom@48: terom@48: self.render_item_form(), terom@48: ) terom@48: