New batch system! Hopefully nothing else broke...
Attempt number 5,176 at a decent batch system, we'll see.
This commit is contained in:
		
							parent
							
								
									c4a19f279b
								
							
						
					
					
						commit
						b05f30d9fe
					
				
					 15 changed files with 1213 additions and 32 deletions
				
			
		| 
						 | 
				
			
			@ -29,6 +29,13 @@ Backoffice Web Application for Rattail
 | 
			
		|||
from ._version import __version__
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO: Ugh, hack to get batch models loaded before views can complain...
 | 
			
		||||
from rattail.db import model
 | 
			
		||||
from rattail.db.batch.vendorcatalog.model import VendorCatalog, VendorCatalogRow
 | 
			
		||||
model.VendorCatalog = VendorCatalog
 | 
			
		||||
model.VendorCatalogRow = VendorCatalogRow
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def includeme(config):
 | 
			
		||||
    config.include('tailbone.static')
 | 
			
		||||
    config.include('tailbone.subscribers')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -115,7 +115,7 @@ $(function() {
 | 
			
		|||
    /*
 | 
			
		||||
     * When filter labels are clicked, (un)check the associated checkbox.
 | 
			
		||||
     */
 | 
			
		||||
    $('div.grid-wrapper div.filter label').on('click', function() {
 | 
			
		||||
    $('body').on('click', '.grid-wrapper .filter label', function() {
 | 
			
		||||
        var checkbox = $(this).prev('input[type="checkbox"]');
 | 
			
		||||
        if (checkbox.prop('checked')) {
 | 
			
		||||
            checkbox.prop('checked', false);
 | 
			
		||||
| 
						 | 
				
			
			@ -130,7 +130,7 @@ $(function() {
 | 
			
		|||
     * element.  If all available filters have been displayed, the "add filter"
 | 
			
		||||
     * dropdown will be hidden.
 | 
			
		||||
     */
 | 
			
		||||
    $('#add-filter').on('change', function() {
 | 
			
		||||
    $('body').on('change', '#add-filter', function() {
 | 
			
		||||
        var select = $(this);
 | 
			
		||||
        var filters = select.parents('div.filters:first');
 | 
			
		||||
        var filter = filters.find('#filter-' + select.val());
 | 
			
		||||
| 
						 | 
				
			
			@ -156,7 +156,7 @@ $(function() {
 | 
			
		|||
     * When user clicks the grid filters search button, perform the search in
 | 
			
		||||
     * the background and reload the grid in-place.
 | 
			
		||||
     */
 | 
			
		||||
    $('div.filters form').submit(function() {
 | 
			
		||||
    $('body').on('submit', '.filters form', function() {
 | 
			
		||||
        var form = $(this);
 | 
			
		||||
        var wrapper = form.parents('div.grid-wrapper');
 | 
			
		||||
        var grid = wrapper.find('div.grid');
 | 
			
		||||
| 
						 | 
				
			
			@ -174,7 +174,7 @@ $(function() {
 | 
			
		|||
     * When user clicks the grid filters reset button, manually clear all
 | 
			
		||||
     * filter input elements, and submit a new search.
 | 
			
		||||
     */
 | 
			
		||||
    $('div.filters form button[type="reset"]').click(function() {
 | 
			
		||||
    $('body').on('click', '.filters form button[type="reset"]', function() {
 | 
			
		||||
        var form = $(this).parents('form');
 | 
			
		||||
        form.find('div.filter').each(function() {
 | 
			
		||||
            $(this).find('div.value input').val('');
 | 
			
		||||
| 
						 | 
				
			
			@ -183,7 +183,7 @@ $(function() {
 | 
			
		|||
        return false;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $('div.grid-wrapper').on('click', 'div.grid th.sortable a', function() {
 | 
			
		||||
    $('body').on('click', '.grid thead th.sortable a', function() {
 | 
			
		||||
        var th = $(this).parent();
 | 
			
		||||
        var wrapper = th.parents('div.grid-wrapper');
 | 
			
		||||
        var grid = wrapper.find('div.grid');
 | 
			
		||||
| 
						 | 
				
			
			@ -201,29 +201,29 @@ $(function() {
 | 
			
		|||
        return false;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $('#body').on('mouseenter', 'div.grid.hoverable table tbody tr', function() {
 | 
			
		||||
    $('body').on('mouseenter', '.grid.hoverable tbody tr', function() {
 | 
			
		||||
        $(this).addClass('hovering');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $('#body').on('mouseleave', 'div.grid.hoverable table tbody tr', function() {
 | 
			
		||||
    $('body').on('mouseleave', '.grid.hoverable tbody tr', function() {
 | 
			
		||||
        $(this).removeClass('hovering');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $('div.grid-wrapper').on('click', 'div.grid table tbody td.view', function() {
 | 
			
		||||
    $('body').on('click', '.grid tbody td.view', function() {
 | 
			
		||||
        var url = $(this).attr('url');
 | 
			
		||||
        if (url) {
 | 
			
		||||
            location.href = url;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $('div.grid-wrapper').on('click', 'div.grid table tbody td.edit', function() {
 | 
			
		||||
    $('body').on('click', '.grid tbody td.edit', function() {
 | 
			
		||||
        var url = $(this).attr('url');
 | 
			
		||||
        if (url) {
 | 
			
		||||
            location.href = url;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $('div.grid-wrapper').on('click', 'div.grid table tbody td.delete', function() {
 | 
			
		||||
    $('body').on('click', '.grid tbody td.delete', function() {
 | 
			
		||||
        var url = $(this).attr('url');
 | 
			
		||||
        if (url) {
 | 
			
		||||
            if (confirm("Do you really wish to delete this object?")) {
 | 
			
		||||
| 
						 | 
				
			
			@ -232,7 +232,8 @@ $(function() {
 | 
			
		|||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $('div.grid-wrapper').on('change', 'div.grid div.pager select#grid-page-count', function() {
 | 
			
		||||
    // $('div.grid-wrapper').on('change', 'div.grid div.pager select#grid-page-count', function() {
 | 
			
		||||
    $('body').on('change', '.grid .pager #grid-page-count', function() {
 | 
			
		||||
        var select = $(this);
 | 
			
		||||
        var wrapper = select.parents('div.grid-wrapper');
 | 
			
		||||
        var grid = wrapper.find('div.grid');
 | 
			
		||||
| 
						 | 
				
			
			@ -269,7 +270,7 @@ $(function() {
 | 
			
		|||
    /*
 | 
			
		||||
     * Add "check all" functionality to tables with checkboxes.
 | 
			
		||||
     */
 | 
			
		||||
    $('body').on('click', 'div.grid table thead th.checkbox input[type="checkbox"]', function() {
 | 
			
		||||
    $('body').on('click', '.grid thead th.checkbox input[type="checkbox"]', function() {
 | 
			
		||||
        var table = $(this).parents('table:first');
 | 
			
		||||
        var checked = $(this).prop('checked');
 | 
			
		||||
        table.find('tbody tr').each(function() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								tailbone/templates/batch/create.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tailbone/templates/batch/create.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
## -*- coding: utf-8 -*-
 | 
			
		||||
<%inherit file="/crud.mako" />
 | 
			
		||||
 | 
			
		||||
<%def name="title()">Upload ${batch_display}</%def>
 | 
			
		||||
 | 
			
		||||
<%def name="context_menu_items()">
 | 
			
		||||
  % if request.has_perm('{0}.view'.format(permission_prefix)):
 | 
			
		||||
      <li>${h.link_to("Back to {0}".format(batch_display_plural), url(route_prefix))}</li>
 | 
			
		||||
  % endif
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
${parent.body()}
 | 
			
		||||
							
								
								
									
										12
									
								
								tailbone/templates/batch/index.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tailbone/templates/batch/index.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
## -*- coding: utf-8 -*-
 | 
			
		||||
<%inherit file="/grid.mako" />
 | 
			
		||||
 | 
			
		||||
<%def name="title()">${batch_display_plural}</%def>
 | 
			
		||||
 | 
			
		||||
<%def name="context_menu_items()">
 | 
			
		||||
  % if request.has_perm('{0}.create'.format(permission_prefix)):
 | 
			
		||||
      <li>${h.link_to("Create a new {0}".format(batch_display), url('{0}.create'.format(route_prefix)))}</li>
 | 
			
		||||
  % endif
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
${parent.body()}
 | 
			
		||||
							
								
								
									
										14
									
								
								tailbone/templates/batch/rows.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								tailbone/templates/batch/rows.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
## -*- coding: utf-8 -*-
 | 
			
		||||
<div class="grid-wrapper">
 | 
			
		||||
 | 
			
		||||
  <table class="grid-header">
 | 
			
		||||
    <tr>
 | 
			
		||||
      <td rowspan="2" class="form">
 | 
			
		||||
        ${search.render()}
 | 
			
		||||
      </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
  </table>
 | 
			
		||||
 | 
			
		||||
  ${grid}
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										65
									
								
								tailbone/templates/batch/view.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								tailbone/templates/batch/view.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,65 @@
 | 
			
		|||
## -*- coding: utf-8 -*-
 | 
			
		||||
<%inherit file="/crud.mako" />
 | 
			
		||||
 | 
			
		||||
<%def name="title()">View ${batch_display}</%def>
 | 
			
		||||
 | 
			
		||||
<%def name="head_tags()">
 | 
			
		||||
    <script type="text/javascript">
 | 
			
		||||
      $(function() {
 | 
			
		||||
          $('#rows-wrapper').load('${url('{0}.rows'.format(route_prefix), uuid=batch.uuid)}', function() {
 | 
			
		||||
              // TODO: It'd be nice if we didn't have to do this here.
 | 
			
		||||
              $(this).find('button').button();
 | 
			
		||||
              $(this).find('input[type=submit]').button();
 | 
			
		||||
          });
 | 
			
		||||
      });
 | 
			
		||||
    </script>
 | 
			
		||||
    <style type="text/css">
 | 
			
		||||
      #rows-wrapper {
 | 
			
		||||
          margin-top: 10px;
 | 
			
		||||
      }
 | 
			
		||||
      .grid tr.notice.odd {
 | 
			
		||||
          background-color: #fe8;
 | 
			
		||||
      }
 | 
			
		||||
      .grid tr.notice.even {
 | 
			
		||||
          background-color: #fd6;
 | 
			
		||||
      }
 | 
			
		||||
      .grid tr.notice.hovering {
 | 
			
		||||
          background-color: #ec7;
 | 
			
		||||
      }
 | 
			
		||||
      .grid tr.warning.odd {
 | 
			
		||||
          background-color: #ebb;
 | 
			
		||||
      }
 | 
			
		||||
      .grid tr.warning.even {
 | 
			
		||||
          background-color: #fcc;
 | 
			
		||||
      }
 | 
			
		||||
      .grid tr.warning.hovering {
 | 
			
		||||
          background-color: #daa;
 | 
			
		||||
      }
 | 
			
		||||
    </style>
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
<div class="form-wrapper">
 | 
			
		||||
 | 
			
		||||
  <ul class="context-menu">
 | 
			
		||||
    <li>${h.link_to("Back to {0}".format(batch_display_plural), url(route_prefix))}</li>
 | 
			
		||||
    % if not batch.executed:
 | 
			
		||||
        % if request.has_perm('{0}.edit'.format(permission_prefix)):
 | 
			
		||||
            ## <li>${h.link_to("Edit this {0}".format(batch_display), url('{0}.edit'.format(route_prefix), uuid=batch.uuid))}</li>
 | 
			
		||||
            % if batch.refreshable:
 | 
			
		||||
                <li>${h.link_to("Refresh Data for this {0}".format(batch_display), url('{0}.refresh'.format(route_prefix), uuid=batch.uuid))}</li>
 | 
			
		||||
            % endif
 | 
			
		||||
        % endif
 | 
			
		||||
        % if request.has_perm('{0}.execute'.format(permission_prefix)):
 | 
			
		||||
            <li>${h.link_to("Execute this {0}".format(batch_display), url('{0}.execute'.format(route_prefix), uuid=batch.uuid))}</li>
 | 
			
		||||
        % endif
 | 
			
		||||
    % endif
 | 
			
		||||
    % if request.has_perm('{0}.delete'.format(permission_prefix)):
 | 
			
		||||
        <li>${h.link_to("Delete this {0}".format(batch_display), url('{0}.delete'.format(route_prefix), uuid=batch.uuid))}</li>
 | 
			
		||||
    % endif
 | 
			
		||||
  </ul>
 | 
			
		||||
 | 
			
		||||
  ${form.render()|n}
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div id="rows-wrapper"></div>
 | 
			
		||||
							
								
								
									
										3
									
								
								tailbone/templates/vendors/catalogs/create.mako
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								tailbone/templates/vendors/catalogs/create.mako
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
## -*- coding: utf-8 -*-
 | 
			
		||||
<%inherit file="/batch/create.mako" />
 | 
			
		||||
${parent.body()}
 | 
			
		||||
							
								
								
									
										3
									
								
								tailbone/templates/vendors/catalogs/index.mako
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								tailbone/templates/vendors/catalogs/index.mako
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
## -*- coding: utf-8 -*-
 | 
			
		||||
<%inherit file="/batch/index.mako" />
 | 
			
		||||
${parent.body()}
 | 
			
		||||
							
								
								
									
										3
									
								
								tailbone/templates/vendors/catalogs/view.mako
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								tailbone/templates/vendors/catalogs/view.mako
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
## -*- coding: utf-8 -*-
 | 
			
		||||
<%inherit file="/batch/view.mako" />
 | 
			
		||||
${parent.body()}
 | 
			
		||||
							
								
								
									
										849
									
								
								tailbone/views/batch.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										849
									
								
								tailbone/views/batch.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,849 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
################################################################################
 | 
			
		||||
#
 | 
			
		||||
#  Rattail -- Retail Software Framework
 | 
			
		||||
#  Copyright © 2010-2015 Lance Edgar
 | 
			
		||||
#
 | 
			
		||||
#  This file is part of Rattail.
 | 
			
		||||
#
 | 
			
		||||
#  Rattail is free software: you can redistribute it and/or modify it under the
 | 
			
		||||
#  terms of the GNU Affero General Public License as published by the Free
 | 
			
		||||
#  Software Foundation, either version 3 of the License, or (at your option)
 | 
			
		||||
#  any later version.
 | 
			
		||||
#
 | 
			
		||||
#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
 | 
			
		||||
#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | 
			
		||||
#  FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
 | 
			
		||||
#  more details.
 | 
			
		||||
#
 | 
			
		||||
#  You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
################################################################################
 | 
			
		||||
"""
 | 
			
		||||
Base views for maintaining new-style batches.
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
   This is all still very experimental.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import datetime
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import formalchemy
 | 
			
		||||
from pyramid.renderers import render_to_response
 | 
			
		||||
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
 | 
			
		||||
 | 
			
		||||
from rattail.db import model
 | 
			
		||||
from rattail.db import Session as RatSession
 | 
			
		||||
from rattail.threads import Thread
 | 
			
		||||
 | 
			
		||||
from tailbone.db import Session
 | 
			
		||||
from tailbone.views import SearchableAlchemyGridView, CrudView
 | 
			
		||||
from tailbone.forms import DateTimeFieldRenderer, UserFieldRenderer, EnumFieldRenderer
 | 
			
		||||
from tailbone.grids.search import BooleanSearchFilter, EnumSearchFilter
 | 
			
		||||
from tailbone.progress import SessionProgress
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseGrid(SearchableAlchemyGridView):
 | 
			
		||||
    """
 | 
			
		||||
    Base view for batch and batch row grid views.  You should not derive from
 | 
			
		||||
    this class, but :class:`BatchGrid` or :class:`BatchRowGrid` instead.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def config_prefix(self):
 | 
			
		||||
        """
 | 
			
		||||
        Config prefix for the grid view.  This is used to keep track of current
 | 
			
		||||
        filtering and sorting, within the user's session.  Derived classes may
 | 
			
		||||
        override this.
 | 
			
		||||
        """
 | 
			
		||||
        return self.mapped_class.__name__.lower()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def permission_prefix(self):
 | 
			
		||||
        """
 | 
			
		||||
        Permission prefix for the grid view.  This is used to automatically
 | 
			
		||||
        protect certain views common to all batches.  Derived classes can
 | 
			
		||||
        override this.
 | 
			
		||||
        """
 | 
			
		||||
        return self.route_prefix
 | 
			
		||||
 | 
			
		||||
    def join_map_extras(self):
 | 
			
		||||
        """
 | 
			
		||||
        Derived classes can override this.  The value returned will be used to
 | 
			
		||||
        supplement the default join map.
 | 
			
		||||
        """
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
    def filter_map_extras(self):
 | 
			
		||||
        """
 | 
			
		||||
        Derived classes can override this.  The value returned will be used to
 | 
			
		||||
        supplement the default filter map.
 | 
			
		||||
        """
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
    def make_filter_map(self, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Make a filter map by combining kwargs from the base class, with extras
 | 
			
		||||
        supplied by a derived class.
 | 
			
		||||
        """
 | 
			
		||||
        extras = self.filter_map_extras()
 | 
			
		||||
        exact = extras.pop('exact', None)
 | 
			
		||||
        if exact:
 | 
			
		||||
            kwargs.setdefault('exact', []).extend(exact)
 | 
			
		||||
        ilike = extras.pop('ilike', None)
 | 
			
		||||
        if ilike:
 | 
			
		||||
            kwargs.setdefault('ilike', []).extend(ilike)
 | 
			
		||||
        kwargs.update(extras)
 | 
			
		||||
        return super(BaseGrid, self).make_filter_map(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def filter_config_extras(self):
 | 
			
		||||
        """
 | 
			
		||||
        Derived classes can override this.  The value returned will be used to
 | 
			
		||||
        supplement the default filter config.
 | 
			
		||||
        """
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
    def sort_map_extras(self):
 | 
			
		||||
        """
 | 
			
		||||
        Derived classes can override this.  The value returned will be used to
 | 
			
		||||
        supplement the default sort map.
 | 
			
		||||
        """
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
    def _configure_grid(self, grid):
 | 
			
		||||
        """
 | 
			
		||||
        Internal method for configuring the grid.  This is meant only for base
 | 
			
		||||
        classes; derived classes should not need to override it.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
    def configure_grid(self, grid):
 | 
			
		||||
        """
 | 
			
		||||
        Derived classes can override this.  Customizes a grid which has already
 | 
			
		||||
        been created with defaults by the base class.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BatchGrid(BaseGrid):
 | 
			
		||||
    """
 | 
			
		||||
    Base grid view for batches, which can be filtered and sorted.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def batch_class(self):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def mapped_class(self):
 | 
			
		||||
        return self.batch_class
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def batch_display_plural(self):
 | 
			
		||||
        """
 | 
			
		||||
        Plural display text for the batch type.
 | 
			
		||||
        """
 | 
			
		||||
        return "{0}s".format(self.batch_display)
 | 
			
		||||
 | 
			
		||||
    def join_map(self):
 | 
			
		||||
        """
 | 
			
		||||
        Provides the default join map for batch grid views.  Derived classes
 | 
			
		||||
        should *not* override this, but :meth:`join_map_extras()` instead.
 | 
			
		||||
        """
 | 
			
		||||
        map_ = {
 | 
			
		||||
            'created_by':
 | 
			
		||||
                lambda q: q.join(model.User, model.User.uuid == self.batch_class.created_by_uuid),
 | 
			
		||||
            }
 | 
			
		||||
        map_.update(self.join_map_extras())
 | 
			
		||||
        return map_
 | 
			
		||||
 | 
			
		||||
    def filter_map(self):
 | 
			
		||||
        """
 | 
			
		||||
        Provides the default filter map for batch grid views.  Derived classes
 | 
			
		||||
        should *not* override this, but :meth:`filter_map_extras()` instead.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        def executed_is(q, v):
 | 
			
		||||
            if v == 'True':
 | 
			
		||||
                return q.filter(self.batch_class.executed != None)
 | 
			
		||||
            else:
 | 
			
		||||
                return q.filter(self.batch_class.executed == None)
 | 
			
		||||
 | 
			
		||||
        def executed_nt(q, v):
 | 
			
		||||
            if v == 'True':
 | 
			
		||||
                return q.filter(self.batch_class.executed == None)
 | 
			
		||||
            else:
 | 
			
		||||
                return q.filter(self.batch_class.executed != None)
 | 
			
		||||
 | 
			
		||||
        return self.make_filter_map(
 | 
			
		||||
            executed={'is': executed_is, 'nt': executed_nt})
 | 
			
		||||
 | 
			
		||||
    def filter_config(self):
 | 
			
		||||
        """
 | 
			
		||||
        Provides the default filter config for batch grid views.  Derived
 | 
			
		||||
        classes should *not* override this, but :meth:`filter_config_extras()`
 | 
			
		||||
        instead.
 | 
			
		||||
        """
 | 
			
		||||
        config = self.make_filter_config(
 | 
			
		||||
            filter_factory_executed=BooleanSearchFilter,
 | 
			
		||||
            filter_type_executed='is',
 | 
			
		||||
            executed=False,
 | 
			
		||||
            include_filter_executed=True)
 | 
			
		||||
        config.update(self.filter_config_extras())
 | 
			
		||||
        return config
 | 
			
		||||
 | 
			
		||||
    def sort_map(self):
 | 
			
		||||
        """
 | 
			
		||||
        Provides the default sort map for batch grid views.  Derived classes
 | 
			
		||||
        should *not* override this, but :meth:`sort_map_extras()` instead.
 | 
			
		||||
        """
 | 
			
		||||
        map_ = self.make_sort_map(
 | 
			
		||||
            created_by=self.sorter(model.User.username))
 | 
			
		||||
        map_.update(self.sort_map_extras())
 | 
			
		||||
        return map_
 | 
			
		||||
 | 
			
		||||
    def sort_config(self):
 | 
			
		||||
        """
 | 
			
		||||
        Provides the default sort config for batch grid views.  Derived classes
 | 
			
		||||
        may override this.
 | 
			
		||||
        """
 | 
			
		||||
        return self.make_sort_config(sort='created', dir='desc')
 | 
			
		||||
 | 
			
		||||
    def grid(self):
 | 
			
		||||
        """
 | 
			
		||||
        Creates the grid for the view.  Derived classes should *not* override
 | 
			
		||||
        this, but :meth:`configure_grid()` instead.
 | 
			
		||||
        """
 | 
			
		||||
        g = self.make_grid()
 | 
			
		||||
        g.created.set(renderer=DateTimeFieldRenderer(self.request.rattail_config))
 | 
			
		||||
        g.created_by.set(renderer=UserFieldRenderer)
 | 
			
		||||
        g.cognized.set(renderer=DateTimeFieldRenderer(self.request.rattail_config))
 | 
			
		||||
        g.cognized_by.set(renderer=UserFieldRenderer)
 | 
			
		||||
        g.executed.set(renderer=DateTimeFieldRenderer(self.request.rattail_config))
 | 
			
		||||
        g.executed_by.set(renderer=UserFieldRenderer)
 | 
			
		||||
        self._configure_grid(g)
 | 
			
		||||
        self.configure_grid(g)
 | 
			
		||||
        if self.request.has_perm('{0}.view'.format(self.permission_prefix)):
 | 
			
		||||
            g.viewable = True
 | 
			
		||||
            g.view_route_name = '{0}.view'.format(self.route_prefix)
 | 
			
		||||
        # if self.request.has_perm('{0}.edit'.format(self.permission_prefix)):
 | 
			
		||||
        #     g.editable = True
 | 
			
		||||
        #     g.edit_route_name = '{0}.edit'.format(self.route_prefix)
 | 
			
		||||
        if self.request.has_perm('{0}.delete'.format(self.permission_prefix)):
 | 
			
		||||
            g.deletable = True
 | 
			
		||||
            g.delete_route_name = '{0}.delete'.format(self.route_prefix)
 | 
			
		||||
        return g
 | 
			
		||||
 | 
			
		||||
    def _configure_grid(self, grid):
 | 
			
		||||
        grid.created_by.set(label="Created by")
 | 
			
		||||
        grid.executed_by.set(label="Executed by")
 | 
			
		||||
 | 
			
		||||
    def configure_grid(self, grid):
 | 
			
		||||
        """
 | 
			
		||||
        Derived classes can override this.  Customizes a grid which has already
 | 
			
		||||
        been created with defaults by the base class.
 | 
			
		||||
        """
 | 
			
		||||
        g = grid
 | 
			
		||||
        g.configure(
 | 
			
		||||
            include=[
 | 
			
		||||
                g.created,
 | 
			
		||||
                g.created_by,
 | 
			
		||||
                g.executed,
 | 
			
		||||
                g.executed_by,
 | 
			
		||||
                ],
 | 
			
		||||
            readonly=True)
 | 
			
		||||
 | 
			
		||||
    def render_kwargs(self):
 | 
			
		||||
        """
 | 
			
		||||
        Add some things to the template context: batch type display name, route
 | 
			
		||||
        and permission prefixes.
 | 
			
		||||
        """
 | 
			
		||||
        return {
 | 
			
		||||
            'batch_display': self.batch_display,
 | 
			
		||||
            'batch_display_plural': self.batch_display_plural,
 | 
			
		||||
            'route_prefix': self.route_prefix,
 | 
			
		||||
            'permission_prefix': self.permission_prefix,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FileBatchGrid(BatchGrid):
 | 
			
		||||
    """
 | 
			
		||||
    Base grid view for batches, which involve primarily a file upload.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def _configure_grid(self, g):
 | 
			
		||||
        super(FileBatchGrid, self)._configure_grid(g)
 | 
			
		||||
        g.created.set(label="Uploaded")
 | 
			
		||||
        g.created_by.set(label="Uploaded by")
 | 
			
		||||
 | 
			
		||||
    def configure_grid(self, grid):
 | 
			
		||||
        """
 | 
			
		||||
        Derived classes can override this.  Customizes a grid which has already
 | 
			
		||||
        been created with defaults by the base class.
 | 
			
		||||
        """
 | 
			
		||||
        g = grid
 | 
			
		||||
        g.configure(
 | 
			
		||||
            include=[
 | 
			
		||||
                g.created,
 | 
			
		||||
                g.created_by,
 | 
			
		||||
                g.filename,
 | 
			
		||||
                g.executed,
 | 
			
		||||
                g.executed_by,
 | 
			
		||||
                ],
 | 
			
		||||
            readonly=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseCrud(CrudView):
 | 
			
		||||
    """
 | 
			
		||||
    Base CRUD view for batches and batch rows.
 | 
			
		||||
    """
 | 
			
		||||
    flash = {}
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def permission_prefix(self):
 | 
			
		||||
        """
 | 
			
		||||
        Permission prefix used to generically protect certain views common to
 | 
			
		||||
        all batches.  Derived classes can override this.
 | 
			
		||||
        """
 | 
			
		||||
        return self.route_prefix
 | 
			
		||||
 | 
			
		||||
    def flash_create(self, model):
 | 
			
		||||
        if 'create' in self.flash:
 | 
			
		||||
            self.request.session.flash(self.flash['create'])
 | 
			
		||||
        else:
 | 
			
		||||
            super(BaseCrud, self).flash_create(model)
 | 
			
		||||
 | 
			
		||||
    def flash_delete(self, model):
 | 
			
		||||
        if 'delete' in self.flash:
 | 
			
		||||
            self.request.session.flash(self.flash['delete'])
 | 
			
		||||
        else:
 | 
			
		||||
            super(BaseCrud, self).flash_delete(model)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BatchCrud(BaseCrud):
 | 
			
		||||
    """
 | 
			
		||||
    Base CRUD view for batches.
 | 
			
		||||
    """
 | 
			
		||||
    refreshable = False
 | 
			
		||||
    flash = {}
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def batch_class(self):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def mapped_class(self):
 | 
			
		||||
        return self.batch_class
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def permission_prefix(self):
 | 
			
		||||
        """
 | 
			
		||||
        Permission prefix for the grid view.  This is used to automatically
 | 
			
		||||
        protect certain views common to all batches.  Derived classes can - and
 | 
			
		||||
        typically should - override this.
 | 
			
		||||
        """
 | 
			
		||||
        return self.route_prefix
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def home_route(self):
 | 
			
		||||
        """
 | 
			
		||||
        The "home" route for the batch type, i.e. its grid view.
 | 
			
		||||
        """
 | 
			
		||||
        return self.route_prefix
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def batch_display_plural(self):
 | 
			
		||||
        """
 | 
			
		||||
        Plural display text for the batch type.
 | 
			
		||||
        """
 | 
			
		||||
        return "{0}s".format(self.batch_display)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, request):
 | 
			
		||||
        self.request = request
 | 
			
		||||
        self.handler = self.batch_handler_class(config=self.request.rattail_config)
 | 
			
		||||
 | 
			
		||||
    def fieldset(self, model):
 | 
			
		||||
        """
 | 
			
		||||
        Creates the fieldset for the view.  Derived classes should *not*
 | 
			
		||||
        override this, but :meth:`configure_fieldset()` instead.
 | 
			
		||||
        """
 | 
			
		||||
        fs = self.make_fieldset(model)
 | 
			
		||||
        fs.created.set(renderer=DateTimeFieldRenderer(self.request.rattail_config))
 | 
			
		||||
        fs.created_by.set(label="Created by", renderer=UserFieldRenderer)
 | 
			
		||||
        fs.cognized.set(renderer=DateTimeFieldRenderer(self.request.rattail_config))
 | 
			
		||||
        fs.cognized_by.set(label="Cognized by", renderer=UserFieldRenderer)
 | 
			
		||||
        fs.executed.set(renderer=DateTimeFieldRenderer(self.request.rattail_config))
 | 
			
		||||
        fs.executed_by.set(label="Executed by", renderer=UserFieldRenderer)
 | 
			
		||||
        self.configure_fieldset(fs)
 | 
			
		||||
        if self.creating:
 | 
			
		||||
            del fs.created
 | 
			
		||||
            del fs.created_by
 | 
			
		||||
            del fs.cognized
 | 
			
		||||
            del fs.cognized_by
 | 
			
		||||
        return fs
 | 
			
		||||
 | 
			
		||||
    def configure_fieldset(self, fieldset):
 | 
			
		||||
        """
 | 
			
		||||
        Derived classes can override this.  Customizes a fieldset which has
 | 
			
		||||
        already been created with defaults by the base class.
 | 
			
		||||
        """
 | 
			
		||||
        fs = fieldset
 | 
			
		||||
        fs.configure(
 | 
			
		||||
            include=[
 | 
			
		||||
                fs.created,
 | 
			
		||||
                fs.created_by,
 | 
			
		||||
                # fs.cognized,
 | 
			
		||||
                # fs.cognized_by,
 | 
			
		||||
                fs.executed,
 | 
			
		||||
                fs.executed_by,
 | 
			
		||||
                ])
 | 
			
		||||
 | 
			
		||||
    def template_kwargs(self, form):
 | 
			
		||||
        """
 | 
			
		||||
        Add some things to the template context: current batch model, batch
 | 
			
		||||
        type display name, route and permission prefixes, batch row grid.
 | 
			
		||||
        """
 | 
			
		||||
        batch = form.fieldset.model
 | 
			
		||||
        batch.refreshable = self.refreshable
 | 
			
		||||
        return {
 | 
			
		||||
            'batch': batch,
 | 
			
		||||
            'batch_display': self.batch_display,
 | 
			
		||||
            'batch_display_plural': self.batch_display_plural,
 | 
			
		||||
            'route_prefix': self.route_prefix,
 | 
			
		||||
            'permission_prefix': self.permission_prefix,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
    def flash_create(self, batch):
 | 
			
		||||
        if 'create' in self.flash:
 | 
			
		||||
            self.request.session.flash(self.flash['create'])
 | 
			
		||||
        else:
 | 
			
		||||
            super(BatchCrud, self).flash_create(batch)
 | 
			
		||||
 | 
			
		||||
    def flash_delete(self, batch):
 | 
			
		||||
        if 'delete' in self.flash:
 | 
			
		||||
            self.request.session.flash(self.flash['delete'])
 | 
			
		||||
        else:
 | 
			
		||||
            super(BatchCrud, self).flash_delete(batch)
 | 
			
		||||
 | 
			
		||||
    def current_batch(self):
 | 
			
		||||
        """
 | 
			
		||||
        Return the current batch, based on the UUID within the URL.
 | 
			
		||||
        """
 | 
			
		||||
        return Session.query(self.mapped_class).get(self.request.matchdict['uuid'])
 | 
			
		||||
 | 
			
		||||
    def refresh(self):
 | 
			
		||||
        """
 | 
			
		||||
        View which will attempt to refresh all data for the batch.  What
 | 
			
		||||
        exactly this means will depend on the type of batch etc.
 | 
			
		||||
        """
 | 
			
		||||
        batch = self.current_batch()
 | 
			
		||||
 | 
			
		||||
        # If handler doesn't declare the need for progress indicator, things
 | 
			
		||||
        # are nice and simple.
 | 
			
		||||
        if not self.handler.show_progress:
 | 
			
		||||
            self.refresh_data(Session, batch)
 | 
			
		||||
            self.request.session.flash("Batch data has been refreshed.")
 | 
			
		||||
            return HTTPFound(location=self.view_url(batch.uuid))
 | 
			
		||||
 | 
			
		||||
        # Showing progress requires a separate thread; start that first.
 | 
			
		||||
        key = '{0}.refresh'.format(self.batch_class.__tablename__)
 | 
			
		||||
        progress = SessionProgress(self.request, key)
 | 
			
		||||
        thread = Thread(target=self.refresh_thread, args=(batch.uuid, progress))
 | 
			
		||||
        thread.start()
 | 
			
		||||
 | 
			
		||||
        # Send user to progress page.
 | 
			
		||||
        kwargs = {
 | 
			
		||||
            'key': key,
 | 
			
		||||
            'cancel_url': self.view_url(batch.uuid),
 | 
			
		||||
            'cancel_msg': "Batch refresh was canceled.",
 | 
			
		||||
            }
 | 
			
		||||
        return render_to_response('/progress.mako', kwargs, request=self.request)
 | 
			
		||||
 | 
			
		||||
    def refresh_data(self, session, batch, progress_factory=None):
 | 
			
		||||
        """
 | 
			
		||||
        Instruct the batch handler to refresh all data for the batch.
 | 
			
		||||
        """
 | 
			
		||||
        self.handler.refresh_data(session, batch, progress_factory=progress_factory)
 | 
			
		||||
        batch.cognized = datetime.datetime.utcnow()
 | 
			
		||||
        batch.cognized_by = self.request.user
 | 
			
		||||
 | 
			
		||||
    def refresh_thread(self, batch_uuid, progress):
 | 
			
		||||
        """
 | 
			
		||||
        Thread target for refreshing batch data with progress indicator.
 | 
			
		||||
        """
 | 
			
		||||
        # Refresh data for the batch, with progress.  Note that we must use the
 | 
			
		||||
        # rattail session here; can't use tailbone because it has web request
 | 
			
		||||
        # transaction binding etc.
 | 
			
		||||
        session = RatSession()
 | 
			
		||||
        batch = session.query(self.batch_class).get(batch_uuid)
 | 
			
		||||
        self.refresh_data(session, batch, progress_factory=progress)
 | 
			
		||||
        session.commit()
 | 
			
		||||
        session.refresh(batch)
 | 
			
		||||
        session.close()
 | 
			
		||||
 | 
			
		||||
        # Finalize progress indicator.
 | 
			
		||||
        progress.session.load()
 | 
			
		||||
        progress.session['complete'] = True
 | 
			
		||||
        progress.session['success_url'] = self.view_url(batch.uuid)
 | 
			
		||||
        progress.session.save()
 | 
			
		||||
        
 | 
			
		||||
    def view_url(self, uuid=None):
 | 
			
		||||
        """
 | 
			
		||||
        Returns the URL for viewing a batch; defaults to current batch.
 | 
			
		||||
        """
 | 
			
		||||
        if uuid is None:
 | 
			
		||||
            uuid = self.request.matchdict['uuid']
 | 
			
		||||
        return self.request.route_url('{0}.view'.format(self.route_prefix), uuid=uuid)
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        batch = self.current_batch()
 | 
			
		||||
        if self.handler.execute(batch):
 | 
			
		||||
            batch.executed = datetime.datetime.utcnow()
 | 
			
		||||
            batch.executed_by = self.request.user
 | 
			
		||||
        return HTTPFound(location=self.view_url(batch.uuid))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FileBatchCrud(BatchCrud):
 | 
			
		||||
    """
 | 
			
		||||
    Base CRUD view for batches which involve a file upload as the first step.
 | 
			
		||||
    """
 | 
			
		||||
    refreshable = True
 | 
			
		||||
 | 
			
		||||
    def pre_crud(self, batch):
 | 
			
		||||
        """
 | 
			
		||||
        Force refresh if batch has yet to be cognized.
 | 
			
		||||
        """
 | 
			
		||||
        if not self.creating and not batch.cognized:
 | 
			
		||||
            return HTTPFound(location=self.request.route_url(
 | 
			
		||||
                    '{0}.refresh'.format(self.route_prefix), uuid=batch.uuid))
 | 
			
		||||
 | 
			
		||||
    def fieldset(self, model):
 | 
			
		||||
        """
 | 
			
		||||
        Creates the fieldset for the view.  Derived classes should *not*
 | 
			
		||||
        override this, but :meth:`configure_fieldset()` instead.
 | 
			
		||||
        """
 | 
			
		||||
        fs = self.make_fieldset(model)
 | 
			
		||||
        fs.created.set(label="Uploaded", renderer=DateTimeFieldRenderer(self.request.rattail_config))
 | 
			
		||||
        fs.created_by.set(label="Uploaded by", renderer=UserFieldRenderer)
 | 
			
		||||
        fs.cognized.set(renderer=DateTimeFieldRenderer(self.request.rattail_config))
 | 
			
		||||
        fs.cognized_by.set(label="Cognized by", renderer=UserFieldRenderer)
 | 
			
		||||
        fs.executed.set(renderer=DateTimeFieldRenderer(self.request.rattail_config))
 | 
			
		||||
        fs.executed_by.set(label="Executed by", renderer=UserFieldRenderer)
 | 
			
		||||
        fs.append(formalchemy.Field('data_file'))
 | 
			
		||||
        fs.data_file.set(renderer=formalchemy.fields.FileFieldRenderer)
 | 
			
		||||
        self.configure_fieldset(fs)
 | 
			
		||||
        if self.creating:
 | 
			
		||||
            del fs.created
 | 
			
		||||
            del fs.created_by
 | 
			
		||||
            del fs.filename
 | 
			
		||||
            if 'cognized' in fs.render_fields:
 | 
			
		||||
                del fs.cognized
 | 
			
		||||
            if 'cognized_by' in fs.render_fields:
 | 
			
		||||
                del fs.cognized_by
 | 
			
		||||
            if 'executed' in fs.render_fields:
 | 
			
		||||
                del fs.executed
 | 
			
		||||
            if 'executed_by' in fs.render_fields:
 | 
			
		||||
                del fs.executed_by
 | 
			
		||||
            if 'data_rows' in fs.render_fields:
 | 
			
		||||
                del fs.data_rows
 | 
			
		||||
        else:
 | 
			
		||||
            if 'data_file' in fs.render_fields:
 | 
			
		||||
                del fs.data_file
 | 
			
		||||
            batch = fs.model
 | 
			
		||||
            if not batch.executed:
 | 
			
		||||
                if 'executed' in fs.render_fields:
 | 
			
		||||
                    del fs.executed
 | 
			
		||||
                if 'executed_by' in fs.render_fields:
 | 
			
		||||
                    del fs.executed_by
 | 
			
		||||
        return fs
 | 
			
		||||
 | 
			
		||||
    def configure_fieldset(self, fieldset):
 | 
			
		||||
        """
 | 
			
		||||
        Derived classes can override this.  Customizes a fieldset which has
 | 
			
		||||
        already been created with defaults by the base class.
 | 
			
		||||
        """
 | 
			
		||||
        fs = fieldset
 | 
			
		||||
        fs.configure(
 | 
			
		||||
            include=[
 | 
			
		||||
                fs.created,
 | 
			
		||||
                fs.created_by,
 | 
			
		||||
                fs.data_file,
 | 
			
		||||
                fs.filename,
 | 
			
		||||
                # fs.cognized,
 | 
			
		||||
                # fs.cognized_by,
 | 
			
		||||
                fs.executed,
 | 
			
		||||
                fs.executed_by,
 | 
			
		||||
                ])
 | 
			
		||||
 | 
			
		||||
    def save_form(self, form):
 | 
			
		||||
        """
 | 
			
		||||
        Save the uploaded data file if necessary, etc.
 | 
			
		||||
        """
 | 
			
		||||
        # Transfer form data to batch instance.
 | 
			
		||||
        form.fieldset.sync()
 | 
			
		||||
        batch = form.fieldset.model
 | 
			
		||||
 | 
			
		||||
        # For new batches, assign current user as creator, save file etc.
 | 
			
		||||
        if self.creating:
 | 
			
		||||
            batch.created_by = self.request.user
 | 
			
		||||
            batch.filename = form.fieldset.data_file.renderer._filename
 | 
			
		||||
            # Expunge batch from session to prevent it from being flushed.
 | 
			
		||||
            Session.expunge(batch)
 | 
			
		||||
            self.init_batch(batch)
 | 
			
		||||
            Session.add(batch)
 | 
			
		||||
            batch.write_file(self.request.rattail_config, form.fieldset.data_file.value)
 | 
			
		||||
 | 
			
		||||
    def init_batch(self, batch):
 | 
			
		||||
        """
 | 
			
		||||
        Initialize a new batch.  Derived classes can override this to
 | 
			
		||||
        effectively provide default values for a batch, etc.  This method is
 | 
			
		||||
        invoked after a batch has been fully prepared for insertion to the
 | 
			
		||||
        database, but before the push to the database occurs.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
    def post_save_url(self, form):
 | 
			
		||||
        """
 | 
			
		||||
        Redirect to "view batch" after creating or updating a batch.
 | 
			
		||||
        """
 | 
			
		||||
        batch = form.fieldset.model
 | 
			
		||||
        return self.view_url(batch.uuid)
 | 
			
		||||
 | 
			
		||||
    def pre_delete(self, batch):
 | 
			
		||||
        """
 | 
			
		||||
        Delete all data (files etc.) for the batch.
 | 
			
		||||
        """
 | 
			
		||||
        batch.delete_data(self.request.rattail_config)
 | 
			
		||||
        del batch.data_rows[:]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BatchRowGrid(BaseGrid):
 | 
			
		||||
    """
 | 
			
		||||
    Base grid view for batch rows, which can be filtered and sorted.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def row_class(self):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def mapped_class(self):
 | 
			
		||||
        return self.row_class
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def config_prefix(self):
 | 
			
		||||
        """
 | 
			
		||||
        Config prefix for the grid view.  This is used to keep track of current
 | 
			
		||||
        filtering and sorting, within the user's session.  Derived classes may
 | 
			
		||||
        override this.
 | 
			
		||||
        """
 | 
			
		||||
        return '{0}.{1}'.format(self.mapped_class.__name__.lower(),
 | 
			
		||||
                                self.request.matchdict['uuid'])
 | 
			
		||||
 | 
			
		||||
    def current_batch(self):
 | 
			
		||||
        """
 | 
			
		||||
        Return the current batch, based on the UUID within the URL.
 | 
			
		||||
        """
 | 
			
		||||
        batch_class = self.row_class.__batch_class__
 | 
			
		||||
        return Session.query(batch_class).get(self.request.matchdict['uuid'])
 | 
			
		||||
 | 
			
		||||
    def modify_query(self, q):
 | 
			
		||||
        q = super(BatchRowGrid, self).modify_query(q)
 | 
			
		||||
        q = q.filter_by(batch=self.current_batch())
 | 
			
		||||
        q = q.filter_by(removed=False)
 | 
			
		||||
        return q
 | 
			
		||||
 | 
			
		||||
    def join_map(self):
 | 
			
		||||
        """
 | 
			
		||||
        Provides the default join map for batch row grid views.  Derived
 | 
			
		||||
        classes should *not* override this, but :meth:`join_map_extras()`
 | 
			
		||||
        instead.
 | 
			
		||||
        """
 | 
			
		||||
        return self.join_map_extras()
 | 
			
		||||
 | 
			
		||||
    def filter_map(self):
 | 
			
		||||
        """
 | 
			
		||||
        Provides the default filter map for batch row grid views.  Derived
 | 
			
		||||
        classes should *not* override this, but :meth:`filter_map_extras()`
 | 
			
		||||
        instead.
 | 
			
		||||
        """
 | 
			
		||||
        return self.make_filter_map(exact=['status_code'])
 | 
			
		||||
 | 
			
		||||
    def filter_config(self):
 | 
			
		||||
        """
 | 
			
		||||
        Provides the default filter config for batch grid views.  Derived
 | 
			
		||||
        classes should *not* override this, but :meth:`filter_config_extras()`
 | 
			
		||||
        instead.
 | 
			
		||||
        """
 | 
			
		||||
        kwargs = {'filter_label_status_code': "Status",
 | 
			
		||||
                  'filter_factory_status_code': EnumSearchFilter(self.row_class.STATUS)}
 | 
			
		||||
        kwargs.update(self.filter_config_extras())
 | 
			
		||||
        return self.make_filter_config(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def sort_map(self):
 | 
			
		||||
        """
 | 
			
		||||
        Provides the default sort map for batch grid views.  Derived classes
 | 
			
		||||
        should *not* override this, but :meth:`sort_map_extras()` instead.
 | 
			
		||||
        """
 | 
			
		||||
        map_ = self.make_sort_map()
 | 
			
		||||
        map_.update(self.sort_map_extras())
 | 
			
		||||
        return map_
 | 
			
		||||
 | 
			
		||||
    def sort_config(self):
 | 
			
		||||
        """
 | 
			
		||||
        Provides the default sort config for batch grid views.  Derived classes
 | 
			
		||||
        may override this.
 | 
			
		||||
        """
 | 
			
		||||
        return self.make_sort_config(sort='sequence', dir='asc')
 | 
			
		||||
 | 
			
		||||
    def grid(self):
 | 
			
		||||
        """
 | 
			
		||||
        Creates the grid for the view.  Derived classes should *not* override
 | 
			
		||||
        this, but :meth:`configure_grid()` instead.
 | 
			
		||||
        """
 | 
			
		||||
        g = self.make_grid()
 | 
			
		||||
        g.extra_row_class = self.tr_class
 | 
			
		||||
        g.sequence.set(label="Seq.")
 | 
			
		||||
        g.status_code.set(label="Status", renderer=EnumFieldRenderer(self.row_class.STATUS))
 | 
			
		||||
        self._configure_grid(g)
 | 
			
		||||
        self.configure_grid(g)
 | 
			
		||||
 | 
			
		||||
        batch = self.current_batch()
 | 
			
		||||
        # g.viewable = True
 | 
			
		||||
        # g.view_route_name = '{0}.rows.view'.format(self.route_prefix)
 | 
			
		||||
        if not batch.executed and self.request.has_perm('{0}.edit'.format(self.permission_prefix)):
 | 
			
		||||
            # g.editable = True
 | 
			
		||||
            # g.edit_route_name = '{0}.rows.edit'.format(self.route_prefix)
 | 
			
		||||
            g.deletable = True
 | 
			
		||||
            g.delete_route_name = '{0}.rows.delete'.format(self.route_prefix)
 | 
			
		||||
        return g
 | 
			
		||||
 | 
			
		||||
    def tr_class(self, row, i):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProductBatchRowGrid(BatchRowGrid):
 | 
			
		||||
    """
 | 
			
		||||
    Base grid view for batch rows which deal directly with products.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def filter_map(self):
 | 
			
		||||
        """
 | 
			
		||||
        Provides the default filter map for batch row grid views.  Derived
 | 
			
		||||
        classes should *not* override this, but :meth:`filter_map_extras()`
 | 
			
		||||
        instead.
 | 
			
		||||
        """
 | 
			
		||||
        return self.make_filter_map(exact=['upc', 'status_code'],
 | 
			
		||||
                                    ilike=['brand_name', 'description', 'size'])
 | 
			
		||||
 | 
			
		||||
    def filter_config(self):
 | 
			
		||||
        """
 | 
			
		||||
        Provides the default filter config for batch grid views.  Derived
 | 
			
		||||
        classes should *not* override this, but :meth:`filter_config_extras()`
 | 
			
		||||
        instead.
 | 
			
		||||
        """
 | 
			
		||||
        kwargs = {'filter_label_status_code': "Status",
 | 
			
		||||
                  'filter_factory_status_code': EnumSearchFilter(self.row_class.STATUS),
 | 
			
		||||
                  'filter_label_upc': "UPC",
 | 
			
		||||
                  'filter_label_brand_name': "Brand"}
 | 
			
		||||
        kwargs.update(self.filter_config_extras())
 | 
			
		||||
        return self.make_filter_config(**kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BatchRowCrud(BaseCrud):
 | 
			
		||||
    """
 | 
			
		||||
    Base CRUD view for batch rows.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def row_class(self):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def mapped_class(self):
 | 
			
		||||
        return self.row_class
 | 
			
		||||
 | 
			
		||||
    def delete(self):
 | 
			
		||||
        """
 | 
			
		||||
        "Delete" a row from the batch.  This sets the ``removed`` flag on the
 | 
			
		||||
        row but does not truly delete it.
 | 
			
		||||
        """
 | 
			
		||||
        row = self.get_model_from_request()
 | 
			
		||||
        if not row:
 | 
			
		||||
            return HTTPNotFound()
 | 
			
		||||
        row.removed = True
 | 
			
		||||
        return HTTPFound(location=self.request.route_url(
 | 
			
		||||
                '{0}.view'.format(self.route_prefix), uuid=row.batch_uuid))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def defaults(config, batch_grid, batch_crud, row_grid, row_crud, url_prefix,
 | 
			
		||||
             route_prefix=None, permission_prefix=None, template_prefix=None):
 | 
			
		||||
    """
 | 
			
		||||
    Apply default configuration to the Pyramid configurator object, for the
 | 
			
		||||
    given batch grid and CRUD views.
 | 
			
		||||
    """
 | 
			
		||||
    assert batch_grid
 | 
			
		||||
    assert batch_crud
 | 
			
		||||
    assert url_prefix
 | 
			
		||||
    if route_prefix is None:
 | 
			
		||||
        route_prefix = batch_grid.route_prefix
 | 
			
		||||
    if permission_prefix is None:
 | 
			
		||||
        permission_prefix = route_prefix
 | 
			
		||||
    if template_prefix is None:
 | 
			
		||||
        template_prefix = url_prefix
 | 
			
		||||
    template_prefix.rstrip('/')
 | 
			
		||||
 | 
			
		||||
    # Batches grid
 | 
			
		||||
    config.add_route(route_prefix, url_prefix)
 | 
			
		||||
    config.add_view(batch_grid, route_name=route_prefix,
 | 
			
		||||
                    renderer='{0}/index.mako'.format(template_prefix),
 | 
			
		||||
                    permission='{0}.view'.format(permission_prefix))
 | 
			
		||||
 | 
			
		||||
    # Create batch
 | 
			
		||||
    config.add_route('{0}.create'.format(route_prefix), '{0}new'.format(url_prefix))
 | 
			
		||||
    config.add_view(batch_crud, attr='create', route_name='{0}.create'.format(route_prefix),
 | 
			
		||||
                    renderer='{0}/create.mako'.format(template_prefix),
 | 
			
		||||
                    permission='{0}.create'.format(permission_prefix))
 | 
			
		||||
 | 
			
		||||
    # View batch
 | 
			
		||||
    config.add_route('{0}.view'.format(route_prefix), '{0}{{uuid}}'.format(url_prefix))
 | 
			
		||||
    config.add_view(batch_crud, attr='read', route_name='{0}.view'.format(route_prefix),
 | 
			
		||||
                    renderer='{0}/view.mako'.format(template_prefix),
 | 
			
		||||
                    permission='{0}.view'.format(permission_prefix))
 | 
			
		||||
 | 
			
		||||
    # Edit batch
 | 
			
		||||
    config.add_route('{0}.edit'.format(route_prefix), '{0}{{uuid}}/edit'.format(url_prefix))
 | 
			
		||||
    config.add_view(batch_crud, attr='update', route_name='{0}.edit'.format(route_prefix),
 | 
			
		||||
                    renderer='{0}/edit.mako'.format(template_prefix),
 | 
			
		||||
                    permission='{0}.edit'.format(permission_prefix))
 | 
			
		||||
 | 
			
		||||
    # Refresh batch row data
 | 
			
		||||
    config.add_route('{0}.refresh'.format(route_prefix), '{0}{{uuid}}/refresh'.format(url_prefix))
 | 
			
		||||
    config.add_view(batch_crud, attr='refresh', route_name='{0}.refresh'.format(route_prefix),
 | 
			
		||||
                    permission='{0}.edit'.format(permission_prefix))
 | 
			
		||||
 | 
			
		||||
    # Execute batch
 | 
			
		||||
    config.add_route('{0}.execute'.format(route_prefix), '{0}{{uuid}}/execute'.format(url_prefix))
 | 
			
		||||
    config.add_view(batch_crud, attr='execute', route_name='{0}.execute'.format(route_prefix),
 | 
			
		||||
                    permission='{0}.execute'.format(permission_prefix))
 | 
			
		||||
 | 
			
		||||
    # Delete batch
 | 
			
		||||
    config.add_route('{0}.delete'.format(route_prefix), '{0}{{uuid}}/delete'.format(url_prefix))
 | 
			
		||||
    config.add_view(batch_crud, attr='delete', route_name='{0}.delete'.format(route_prefix),
 | 
			
		||||
                    permission='{0}.delete'.format(permission_prefix))
 | 
			
		||||
 | 
			
		||||
    # Batch rows grid
 | 
			
		||||
    config.add_route('{0}.rows'.format(route_prefix), '{0}{{uuid}}/rows/'.format(url_prefix))
 | 
			
		||||
    config.add_view(row_grid, route_name='{0}.rows'.format(route_prefix),
 | 
			
		||||
                    renderer='/batch/rows.mako',
 | 
			
		||||
                    permission='{0}.view'.format(permission_prefix))
 | 
			
		||||
 | 
			
		||||
    # Delete batch row
 | 
			
		||||
    config.add_route('{0}.rows.delete'.format(route_prefix), '{0}delete-row/{{uuid}}'.format(url_prefix))
 | 
			
		||||
    config.add_view(row_crud, attr='delete', route_name='{0}.rows.delete'.format(route_prefix),
 | 
			
		||||
                    permission='{0}.edit'.format(permission_prefix))
 | 
			
		||||
| 
						 | 
				
			
			@ -2,7 +2,7 @@
 | 
			
		|||
################################################################################
 | 
			
		||||
#
 | 
			
		||||
#  Rattail -- Retail Software Framework
 | 
			
		||||
#  Copyright © 2010-2014 Lance Edgar
 | 
			
		||||
#  Copyright © 2010-2015 Lance Edgar
 | 
			
		||||
#
 | 
			
		||||
#  This file is part of Rattail.
 | 
			
		||||
#
 | 
			
		||||
| 
						 | 
				
			
			@ -87,13 +87,6 @@ class CrudView(View):
 | 
			
		|||
        return self.make_fieldset(model)
 | 
			
		||||
 | 
			
		||||
    def make_form(self, model, form_factory=AlchemyForm, **kwargs):
 | 
			
		||||
        if self.readonly:
 | 
			
		||||
            self.creating = False
 | 
			
		||||
            self.updating = False
 | 
			
		||||
        else:
 | 
			
		||||
            self.creating = model is self.mapped_class
 | 
			
		||||
            self.updating = not self.creating
 | 
			
		||||
 | 
			
		||||
        fieldset = self.fieldset(model)
 | 
			
		||||
        kwargs.setdefault('pretty_name', self.pretty_name)
 | 
			
		||||
        kwargs.setdefault('action_url', self.request.current_route_url())
 | 
			
		||||
| 
						 | 
				
			
			@ -119,21 +112,32 @@ class CrudView(View):
 | 
			
		|||
    def form(self, model):
 | 
			
		||||
        return self.make_form(model)
 | 
			
		||||
 | 
			
		||||
    def save_form(self, form):
 | 
			
		||||
        form.save()
 | 
			
		||||
 | 
			
		||||
    def crud(self, model, readonly=False):
 | 
			
		||||
 | 
			
		||||
        if readonly:
 | 
			
		||||
            self.readonly = True
 | 
			
		||||
        self.readonly = readonly
 | 
			
		||||
        if self.readonly:
 | 
			
		||||
            self.creating = False
 | 
			
		||||
            self.updating = False
 | 
			
		||||
        else:
 | 
			
		||||
            self.creating = model is self.mapped_class
 | 
			
		||||
            self.updating = not self.creating
 | 
			
		||||
 | 
			
		||||
        result = self.pre_crud(model)
 | 
			
		||||
        if result is not None:
 | 
			
		||||
            return result
 | 
			
		||||
 | 
			
		||||
        form = self.form(model)
 | 
			
		||||
        if readonly:
 | 
			
		||||
            form.readonly = True
 | 
			
		||||
        form.readonly = self.readonly
 | 
			
		||||
        if not self.readonly and self.request.method == 'POST':
 | 
			
		||||
 | 
			
		||||
        if not form.readonly and self.request.POST:
 | 
			
		||||
            if form.validate():
 | 
			
		||||
                form.save()
 | 
			
		||||
                self.save_form(form)
 | 
			
		||||
 | 
			
		||||
                result = self.post_save(form)
 | 
			
		||||
                if result:
 | 
			
		||||
                if result is not None:
 | 
			
		||||
                    return result
 | 
			
		||||
 | 
			
		||||
                if form.creating:
 | 
			
		||||
| 
						 | 
				
			
			@ -153,6 +157,9 @@ class CrudView(View):
 | 
			
		|||
        kwargs['form'] = form
 | 
			
		||||
        return kwargs
 | 
			
		||||
 | 
			
		||||
    def pre_crud(self, model):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def template_kwargs(self, form):
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -206,14 +213,25 @@ class CrudView(View):
 | 
			
		|||
        return self.crud(model)
 | 
			
		||||
 | 
			
		||||
    def delete(self):
 | 
			
		||||
        """
 | 
			
		||||
        View for deleting a record.  Derived classes shouldn't override this,
 | 
			
		||||
        but see also :meth:`pre_delete()` and :meth:`post_delete()`.
 | 
			
		||||
        """
 | 
			
		||||
        model = self.get_model_from_request()
 | 
			
		||||
        if not model:
 | 
			
		||||
            return HTTPNotFound()
 | 
			
		||||
 | 
			
		||||
        # Let derived classes prep for (or cancel) deletion.
 | 
			
		||||
        result = self.pre_delete(model)
 | 
			
		||||
        if result:
 | 
			
		||||
        if result is not None:
 | 
			
		||||
            return result
 | 
			
		||||
 | 
			
		||||
        # Flush the deletion immediately so that we know it will succeed prior
 | 
			
		||||
        # to setting a flash message etc.
 | 
			
		||||
        Session.delete(model)
 | 
			
		||||
        Session.flush() # Don't set flash message if delete fails.
 | 
			
		||||
        Session.flush()
 | 
			
		||||
 | 
			
		||||
        # Derived classes can do extra things here; set flash and go home.
 | 
			
		||||
        self.post_delete(model)
 | 
			
		||||
        self.flash_delete(model)
 | 
			
		||||
        return HTTPFound(location=self.home_url)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,7 +33,9 @@ def progress(request):
 | 
			
		|||
    key = request.matchdict['key']
 | 
			
		||||
    session = get_progress_session(request, key)
 | 
			
		||||
    if session.get('complete'):
 | 
			
		||||
        request.session.flash(session.get('success_msg', "The process has completed successfully."))
 | 
			
		||||
        msg = session.get('success_msg')
 | 
			
		||||
        if msg:
 | 
			
		||||
            request.session.flash(msg)
 | 
			
		||||
    elif session.get('error'):
 | 
			
		||||
        request.session.flash(session.get('error_msg', "An unspecified error occurred."), 'error')
 | 
			
		||||
    return session
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										34
									
								
								tailbone/views/vendors/__init__.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								tailbone/views/vendors/__init__.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
################################################################################
 | 
			
		||||
#
 | 
			
		||||
#  Rattail -- Retail Software Framework
 | 
			
		||||
#  Copyright © 2010-2015 Lance Edgar
 | 
			
		||||
#
 | 
			
		||||
#  This file is part of Rattail.
 | 
			
		||||
#
 | 
			
		||||
#  Rattail is free software: you can redistribute it and/or modify it under the
 | 
			
		||||
#  terms of the GNU Affero General Public License as published by the Free
 | 
			
		||||
#  Software Foundation, either version 3 of the License, or (at your option)
 | 
			
		||||
#  any later version.
 | 
			
		||||
#
 | 
			
		||||
#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
 | 
			
		||||
#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | 
			
		||||
#  FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
 | 
			
		||||
#  more details.
 | 
			
		||||
#
 | 
			
		||||
#  You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
################################################################################
 | 
			
		||||
"""
 | 
			
		||||
Views pertaining to vendors
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from .core import VendorsGrid, VendorCrud, VendorsAutocomplete, add_routes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def includeme(config):
 | 
			
		||||
    config.include('tailbone.views.vendors.core')
 | 
			
		||||
    config.include('tailbone.views.vendors.catalogs')
 | 
			
		||||
							
								
								
									
										158
									
								
								tailbone/views/vendors/catalogs.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								tailbone/views/vendors/catalogs.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,158 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
################################################################################
 | 
			
		||||
#
 | 
			
		||||
#  Rattail -- Retail Software Framework
 | 
			
		||||
#  Copyright © 2010-2015 Lance Edgar
 | 
			
		||||
#
 | 
			
		||||
#  This file is part of Rattail.
 | 
			
		||||
#
 | 
			
		||||
#  Rattail is free software: you can redistribute it and/or modify it under the
 | 
			
		||||
#  terms of the GNU Affero General Public License as published by the Free
 | 
			
		||||
#  Software Foundation, either version 3 of the License, or (at your option)
 | 
			
		||||
#  any later version.
 | 
			
		||||
#
 | 
			
		||||
#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
 | 
			
		||||
#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | 
			
		||||
#  FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
 | 
			
		||||
#  more details.
 | 
			
		||||
#
 | 
			
		||||
#  You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
################################################################################
 | 
			
		||||
"""
 | 
			
		||||
Views for maintaining vendor catalogs
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from rattail.db import model
 | 
			
		||||
from rattail.db.api import get_vendor
 | 
			
		||||
from rattail.db.batch.vendorcatalog import VendorCatalogHandler
 | 
			
		||||
from rattail.vendors.catalogs import iter_catalog_parsers, require_catalog_parser
 | 
			
		||||
 | 
			
		||||
import formalchemy
 | 
			
		||||
 | 
			
		||||
from tailbone.db import Session
 | 
			
		||||
from tailbone.views.batch import FileBatchGrid, FileBatchCrud, BatchRowGrid, BatchRowCrud, defaults
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VendorCatalogGrid(FileBatchGrid):
 | 
			
		||||
    """
 | 
			
		||||
    Grid view for vendor catalogs.
 | 
			
		||||
    """
 | 
			
		||||
    batch_class = model.VendorCatalog
 | 
			
		||||
    batch_display = "Vendor Catalog"
 | 
			
		||||
    route_prefix = 'vendors.catalogs'
 | 
			
		||||
 | 
			
		||||
    def join_map_extras(self):
 | 
			
		||||
        return {'vendor': lambda q: q.join(model.Vendor)}
 | 
			
		||||
 | 
			
		||||
    def filter_map_extras(self):
 | 
			
		||||
        return {'vendor': self.filter_ilike(model.Vendor.name)}
 | 
			
		||||
 | 
			
		||||
    def filter_config_extras(self):
 | 
			
		||||
        return {'filter_type_vendor': 'lk',
 | 
			
		||||
                'include_filter_vendor': True}
 | 
			
		||||
 | 
			
		||||
    def sort_map_extras(self):
 | 
			
		||||
        return {'vendor': self.sorter(model.Vendor.name)}
 | 
			
		||||
 | 
			
		||||
    def configure_grid(self, g):
 | 
			
		||||
        g.configure(
 | 
			
		||||
            include=[
 | 
			
		||||
                g.created,
 | 
			
		||||
                g.created_by,
 | 
			
		||||
                g.vendor,
 | 
			
		||||
                g.effective,
 | 
			
		||||
                g.filename,
 | 
			
		||||
                g.executed,
 | 
			
		||||
                ],
 | 
			
		||||
            readonly=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VendorCatalogCrud(FileBatchCrud):
 | 
			
		||||
    """
 | 
			
		||||
    CRUD view for vendor catalogs.
 | 
			
		||||
    """
 | 
			
		||||
    batch_class = model.VendorCatalog
 | 
			
		||||
    batch_handler_class = VendorCatalogHandler
 | 
			
		||||
    route_prefix = 'vendors.catalogs'
 | 
			
		||||
 | 
			
		||||
    batch_display = "Vendor Catalog"
 | 
			
		||||
    flash = {'create': "New vendor catalog has been uploaded.",
 | 
			
		||||
             'delete': "Vendor catalog has been deleted."}
 | 
			
		||||
 | 
			
		||||
    def configure_fieldset(self, fs):
 | 
			
		||||
        parsers = sorted(iter_catalog_parsers(), key=lambda p: p.display)
 | 
			
		||||
        parser_options = [(p.display, p.key) for p in parsers]
 | 
			
		||||
        parser_options.insert(0, ("(please choose)", ''))
 | 
			
		||||
        fs.parser_key.set(renderer=formalchemy.fields.SelectFieldRenderer,
 | 
			
		||||
                          options=parser_options)
 | 
			
		||||
        fs.configure(
 | 
			
		||||
            include=[
 | 
			
		||||
                fs.created,
 | 
			
		||||
                fs.created_by,
 | 
			
		||||
                fs.vendor,
 | 
			
		||||
                fs.data_file.label("Catalog File"),
 | 
			
		||||
                fs.filename,
 | 
			
		||||
                fs.parser_key.label("File Type"),
 | 
			
		||||
                fs.effective,
 | 
			
		||||
                fs.executed,
 | 
			
		||||
                fs.executed_by,
 | 
			
		||||
                ])
 | 
			
		||||
        if self.creating:
 | 
			
		||||
            del fs.vendor
 | 
			
		||||
            del fs.effective
 | 
			
		||||
        else:
 | 
			
		||||
            del fs.parser_key
 | 
			
		||||
 | 
			
		||||
    def init_batch(self, batch):
 | 
			
		||||
        parser = require_catalog_parser(batch.parser_key)
 | 
			
		||||
        batch.vendor = get_vendor(Session, parser.vendor_key)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VendorCatalogRowGrid(BatchRowGrid):
 | 
			
		||||
    """
 | 
			
		||||
    Grid view for vendor catalog rows.
 | 
			
		||||
    """
 | 
			
		||||
    row_class = model.VendorCatalogRow
 | 
			
		||||
    route_prefix = 'vendors.catalogs'
 | 
			
		||||
 | 
			
		||||
    def filter_map_extras(self):
 | 
			
		||||
        return {'ilike': ['upc', 'brand_name', 'description', 'size', 'vendor_code']}
 | 
			
		||||
 | 
			
		||||
    def filter_config_extras(self):
 | 
			
		||||
        return {'filter_label_upc': "UPC",
 | 
			
		||||
                'filter_label_brand_name': "Brand"}
 | 
			
		||||
 | 
			
		||||
    def configure_grid(self, g):
 | 
			
		||||
        g.configure(
 | 
			
		||||
            include=[
 | 
			
		||||
                g.sequence,
 | 
			
		||||
                g.upc.label("UPC"),
 | 
			
		||||
                g.brand_name.label("Brand"),
 | 
			
		||||
                g.description,
 | 
			
		||||
                g.size,
 | 
			
		||||
                g.vendor_code,
 | 
			
		||||
                g.old_unit_cost.label("Old Cost"),
 | 
			
		||||
                g.unit_cost.label("New Cost"),
 | 
			
		||||
                g.unit_cost_diff.label("Diff."),
 | 
			
		||||
                g.status_code,
 | 
			
		||||
                ],
 | 
			
		||||
            readonly=True)
 | 
			
		||||
 | 
			
		||||
    def tr_class(self, row, i):
 | 
			
		||||
        if row.status_code in (row.STATUS_NEW_COST, row.STATUS_UPDATE_COST):
 | 
			
		||||
            return 'notice'
 | 
			
		||||
        if row.status_code == row.STATUS_PRODUCT_NOT_FOUND:
 | 
			
		||||
            return 'warning'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VendorCatalogRowCrud(BatchRowCrud):
 | 
			
		||||
    row_class = model.VendorCatalogRow
 | 
			
		||||
    route_prefix = 'vendors.catalogs'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def includeme(config):
 | 
			
		||||
    defaults(config, VendorCatalogGrid, VendorCatalogCrud, VendorCatalogRowGrid, VendorCatalogRowCrud, '/vendors/catalogs/')
 | 
			
		||||
| 
						 | 
				
			
			@ -26,8 +26,8 @@
 | 
			
		|||
Vendor Views
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from . import SearchableAlchemyGridView, CrudView, AutocompleteView
 | 
			
		||||
from ..forms import AssociationProxyField, PersonFieldRenderer
 | 
			
		||||
from tailbone.views import SearchableAlchemyGridView, CrudView, AutocompleteView
 | 
			
		||||
from tailbone.forms import AssociationProxyField, PersonFieldRenderer
 | 
			
		||||
from rattail.db.model import Vendor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue