svv/items.py
changeset 48 06fa83c8c0bb
child 53 06dad873204d
equal deleted inserted replaced
47:d79a560af791 48:06fa83c8c0bb
       
     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