Refactor products view(s) per new master pattern.
Finally!
This commit is contained in:
		
							parent
							
								
									254c68034a
								
							
						
					
					
						commit
						583548cad5
					
				
					 12 changed files with 421 additions and 394 deletions
				
			
		|  | @ -2,7 +2,7 @@ | |||
| ################################################################################ | ||||
| # | ||||
| #  Rattail -- Retail Software Framework | ||||
| #  Copyright © 2010-2015 Lance Edgar | ||||
| #  Copyright © 2010-2016 Lance Edgar | ||||
| # | ||||
| #  This file is part of Rattail. | ||||
| # | ||||
|  | @ -24,18 +24,20 @@ | |||
| FormAlchemy Grid Classes | ||||
| """ | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| from __future__ import unicode_literals, absolute_import | ||||
| 
 | ||||
| import logging | ||||
| 
 | ||||
| import sqlalchemy as sa | ||||
| from sqlalchemy import orm | ||||
| 
 | ||||
| from edbob.util import prettify | ||||
| 
 | ||||
| from rattail.db.types import GPCType | ||||
| 
 | ||||
| import formalchemy | ||||
| from webhelpers import paginate | ||||
| 
 | ||||
| from edbob.util import prettify | ||||
| 
 | ||||
| from tailbone.db import Session | ||||
| from tailbone.newgrids import Grid, GridColumn, filters | ||||
| 
 | ||||
|  | @ -64,7 +66,8 @@ class AlchemyGrid(Grid): | |||
|     def __init__(self, *args, **kwargs): | ||||
|         super(AlchemyGrid, self).__init__(*args, **kwargs) | ||||
|         fa_grid = formalchemy.Grid(self.model_class, instances=self.data, | ||||
|                                    session=Session(), request=self.request) | ||||
|                                    session=kwargs.get('session', Session()), | ||||
|                                    request=self.request) | ||||
|         fa_grid.prettify = prettify | ||||
|         self._fa_grid = fa_grid | ||||
| 
 | ||||
|  | @ -99,6 +102,8 @@ class AlchemyGrid(Grid): | |||
|             factory = filters.AlchemyBooleanFilter | ||||
|         elif isinstance(column.type, (sa.Date, sa.DateTime)): | ||||
|             factory = filters.AlchemyDateFilter | ||||
|         elif isinstance(column.type, GPCType): | ||||
|             factory = filters.AlchemyGPCFilter | ||||
|         return factory(key, column=column, **kwargs) | ||||
| 
 | ||||
|     def iter_filters(self): | ||||
|  | @ -107,6 +112,24 @@ class AlchemyGrid(Grid): | |||
|         """ | ||||
|         return self.filters.itervalues() | ||||
| 
 | ||||
|     def filter_data(self, query): | ||||
|         """ | ||||
|         Filter and return the given data set, according to current settings. | ||||
|         """ | ||||
|         # This overrides the core version only slightly, in that it will only | ||||
|         # invoke a join if any particular filter(s) actually modifies the | ||||
|         # query.  The main motivation for this is on the products page, where | ||||
|         # the tricky "vendor (any)" filter has a weird join and causes | ||||
|         # unpredictable results.  Now we can skip the join for that unless the | ||||
|         # user actually enters some criteria for it. | ||||
|         for filtr in self.iter_active_filters(): | ||||
|             original = query | ||||
|             query = filtr.filter(query) | ||||
|             if query is not original and filtr.key in self.joiners and filtr.key not in self.joined: | ||||
|                 query = self.joiners[filtr.key](query) | ||||
|                 self.joined.add(filtr.key) | ||||
|         return query | ||||
| 
 | ||||
|     def make_sorters(self, sorters): | ||||
|         """ | ||||
|         Returns a mapping of sort options for the grid.  Keyword args override | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| ################################################################################ | ||||
| # | ||||
| #  Rattail -- Retail Software Framework | ||||
| #  Copyright © 2010-2015 Lance Edgar | ||||
| #  Copyright © 2010-2016 Lance Edgar | ||||
| # | ||||
| #  This file is part of Rattail. | ||||
| # | ||||
|  | @ -24,7 +24,7 @@ | |||
| Core Grid Classes | ||||
| """ | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| from __future__ import unicode_literals, absolute_import | ||||
| 
 | ||||
| from edbob.util import prettify | ||||
| 
 | ||||
|  | @ -246,11 +246,17 @@ class Grid(object): | |||
|         """ | ||||
|         Check to see if the current user has default settings on file for this grid. | ||||
|         """ | ||||
|         if not self.request.user: | ||||
|         user = self.request.user | ||||
|         if not user: | ||||
|             return False | ||||
| 
 | ||||
|         session = getattr(self, 'session', Session()) | ||||
|         if user not in session: | ||||
|             user = session.merge(user) | ||||
| 
 | ||||
|         # User defaults should have all or nothing, so just check one key. | ||||
|         key = 'tailbone.{0}.grid.{1}.sortkey'.format(self.request.user.uuid, self.key) | ||||
|         return get_setting(Session(), key) is not None | ||||
|         key = 'tailbone.{}.grid.{}.sortkey'.format(user.uuid, self.key) | ||||
|         return get_setting(session, key) is not None | ||||
| 
 | ||||
|     def apply_user_defaults(self, settings): | ||||
|         """ | ||||
|  | @ -600,10 +606,11 @@ class Grid(object): | |||
|         """ | ||||
|         url = action.get_url(row) | ||||
|         if url: | ||||
|             kwargs = {'class_': action.key} | ||||
|             if action.icon: | ||||
|                 icon = HTML.tag('span', class_='ui-icon ui-icon-{0}'.format(action.icon)) | ||||
|                 return tags.link_to(icon + action.label, url) | ||||
|             return tags.link_to(action.label, url) | ||||
|                 icon = HTML.tag('span', class_='ui-icon ui-icon-{}'.format(action.icon)) | ||||
|                 return tags.link_to(icon + action.label, url, **kwargs) | ||||
|             return tags.link_to(action.label, url, **kwargs) | ||||
| 
 | ||||
|     def iter_rows(self): | ||||
|         return self.make_visible_data() | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| ################################################################################ | ||||
| # | ||||
| #  Rattail -- Retail Software Framework | ||||
| #  Copyright © 2010-2015 Lance Edgar | ||||
| #  Copyright © 2010-2016 Lance Edgar | ||||
| # | ||||
| #  This file is part of Rattail. | ||||
| # | ||||
|  | @ -24,12 +24,13 @@ | |||
| Grid Filters | ||||
| """ | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| from __future__ import unicode_literals, absolute_import | ||||
| 
 | ||||
| import sqlalchemy as sa | ||||
| 
 | ||||
| from edbob.util import prettify | ||||
| 
 | ||||
| from rattail.gpc import GPC | ||||
| from rattail.util import OrderedDict | ||||
| from rattail.core import UNSPECIFIED | ||||
| 
 | ||||
|  | @ -367,6 +368,44 @@ class AlchemyDateFilter(AlchemyGridFilter): | |||
|         return self.filter_is_null(query, value) | ||||
| 
 | ||||
| 
 | ||||
| class AlchemyGPCFilter(AlchemyGridFilter): | ||||
|     """ | ||||
|     GPC filter for SQLAlchemy. | ||||
|     """ | ||||
|     default_verbs = ['equal', 'not_equal'] | ||||
| 
 | ||||
|     def filter_equal(self, query, value): | ||||
|         """ | ||||
|         Filter data with an equal ('=') query. | ||||
|         """ | ||||
|         if value is None or value == '': | ||||
|             return query | ||||
|         try: | ||||
|             return query.filter(self.column.in_(( | ||||
|                 GPC(value), | ||||
|                 GPC(value, calc_check_digit='upc')))) | ||||
|         except ValueError: | ||||
|             return query | ||||
| 
 | ||||
|     def filter_not_equal(self, query, value): | ||||
|         """ | ||||
|         Filter data with a not eqaul ('!=') query. | ||||
|         """ | ||||
|         if value is None or value == '': | ||||
|             return query | ||||
| 
 | ||||
|         # When saying something is 'not equal' to something else, we must also | ||||
|         # include things which are nothing at all, in our result set. | ||||
|         try: | ||||
|             return query.filter(sa.or_( | ||||
|                 ~self.column.in_(( | ||||
|                     GPC(value), | ||||
|                     GPC(value, calc_check_digit='upc'))), | ||||
|                 self.column == None)) | ||||
|         except ValueError: | ||||
|             return query | ||||
| 
 | ||||
| 
 | ||||
| class GridFilterSet(OrderedDict): | ||||
|     """ | ||||
|     Collection class for :class:`GridFilter` instances. | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ | |||
| .newgrid-wrapper .newfilters fieldset { | ||||
|     margin: -8px 0 5px 0; | ||||
|     padding: 1px 5px 5px 5px; | ||||
|     width: 80%; | ||||
| } | ||||
| 
 | ||||
| .newgrid-wrapper .newfilters .filter { | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ | |||
| 
 | ||||
|   <div class="buttons"> | ||||
|     ${h.submit('create', "Create Batch")} | ||||
|     <button type="button" onclick="location.href = '${url('products')}';">Cancel</button> | ||||
|     ${h.link_to("Cancel", url('products'), class_='button')} | ||||
|   </div> | ||||
| 
 | ||||
|   ${h.end_form()} | ||||
|  |  | |||
|  | @ -1,16 +0,0 @@ | |||
| ## -*- coding: utf-8 -*- | ||||
| <%inherit file="/crud.mako" /> | ||||
| 
 | ||||
| <%def name="context_menu_items()"> | ||||
|   <li>${h.link_to("Back to Products", url('products'))}</li> | ||||
|   % if form.readonly and request.has_perm('products.update'): | ||||
|       <li>${h.link_to("Edit this Product", url('product.update', uuid=form.fieldset.model.uuid))}</li> | ||||
|   % elif form.updating: | ||||
|       <li>${h.link_to("View this Product", url('product.read', uuid=form.fieldset.model.uuid))}</li> | ||||
|   % endif | ||||
|   % if version_count is not Undefined and request.has_perm('product.versions.view'): | ||||
|       <li>${h.link_to("View Change History ({0})".format(version_count), url('product.versions', uuid=form.fieldset.model.uuid))}</li> | ||||
|   % endif | ||||
| </%def> | ||||
| 
 | ||||
| ${parent.body()} | ||||
							
								
								
									
										11
									
								
								tailbone/templates/products/edit.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								tailbone/templates/products/edit.mako
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| ## -*- coding: utf-8 -*- | ||||
| <%inherit file="/master/edit.mako" /> | ||||
| 
 | ||||
| <%def name="context_menu_items()"> | ||||
|   ${parent.context_menu_items()} | ||||
|   % if version_count is not Undefined and request.has_perm('product.versions.view'): | ||||
|       <li>${h.link_to("View Change History ({})".format(version_count), url('product.versions', uuid=instance.uuid))}</li> | ||||
|   % endif | ||||
| </%def> | ||||
| 
 | ||||
| ${parent.body()} | ||||
|  | @ -1,7 +1,5 @@ | |||
| ## -*- coding: utf-8 -*- | ||||
| <%inherit file="/grid.mako" /> | ||||
| 
 | ||||
| <%def name="title()">Products</%def> | ||||
| <%inherit file="/master/index.mako" /> | ||||
| 
 | ||||
| <%def name="head_tags()"> | ||||
|   ${parent.head_tags()} | ||||
|  | @ -35,24 +33,24 @@ | |||
|      | ||||
|   </style> | ||||
|   % if label_profiles and request.has_perm('products.print_labels'): | ||||
|       <script language="javascript" type="text/javascript"> | ||||
|       <script type="text/javascript"> | ||||
| 
 | ||||
|       $(function() { | ||||
|             $('div.grid a.print-label').live('click', function() { | ||||
|                 var quantity = $('#label-quantity').val(); | ||||
|                 if (isNaN(quantity)) { | ||||
|           $('.newgrid-wrapper').on('click', 'a.print_label', function() { | ||||
|               var quantity = $('table.label-printing #label-quantity'); | ||||
|               if (isNaN(quantity.val())) { | ||||
|                   alert("You must provide a valid label quantity."); | ||||
|                     $('#label-quantity').select(); | ||||
|                     $('#label-quantity').focus(); | ||||
|                   quantity.select(); | ||||
|                   quantity.focus(); | ||||
|               } else { | ||||
|                     $.ajax({ | ||||
|                         url: '${url('products.print_labels')}', | ||||
|                         data: { | ||||
|                             'product': get_uuid(this), | ||||
|                             'profile': $('#label-profile').val(), | ||||
|                             'quantity': quantity, | ||||
|                         }, | ||||
|                         success: function(data) { | ||||
|                   quantity = quantity.val(); | ||||
|                   var data = { | ||||
|                       product: get_uuid(this), | ||||
|                       profile: $('#label-profile').val(), | ||||
|                       quantity: quantity | ||||
|                   }; | ||||
|                   console.log(data); | ||||
|                   $.get('${url('products.print_labels')}', data, function(data) { | ||||
|                       if (data.error) { | ||||
|                           alert("An error occurred while attempting to print:\n\n" + data.error); | ||||
|                       } else if (quantity == '1') { | ||||
|  | @ -60,7 +58,6 @@ | |||
|                       } else { | ||||
|                           alert(quantity + " labels have been printed."); | ||||
|                       } | ||||
|                         }, | ||||
|                   }); | ||||
|               } | ||||
|               return false; | ||||
|  | @ -71,13 +68,13 @@ | |||
|   % endif | ||||
| </%def> | ||||
| 
 | ||||
| <%def name="tools()"> | ||||
| <%def name="grid_tools()"> | ||||
|   % if label_profiles and request.has_perm('products.print_labels'): | ||||
|       <table> | ||||
|       <table class="label-printing"> | ||||
|         <thead> | ||||
|           <tr> | ||||
|             <td>Label</td> | ||||
|             <td>Qty.</td> | ||||
|             <th>Label</th> | ||||
|             <th>Qty.</th> | ||||
|           </tr> | ||||
|         </thead> | ||||
|         <tbody> | ||||
|  | @ -97,9 +94,7 @@ | |||
| </%def> | ||||
| 
 | ||||
| <%def name="context_menu_items()"> | ||||
|   % if request.has_perm('products.create'): | ||||
|       <li>${h.link_to("Create a new Product", url('product.create'))}</li> | ||||
|   % endif | ||||
|   ${parent.context_menu_items()} | ||||
|   % if request.has_perm('batches.create'): | ||||
|       <li>${h.link_to("Create Batch from Results", url('products.create_batch'))}</li> | ||||
|   % endif | ||||
|  |  | |||
|  | @ -1,7 +1,9 @@ | |||
| ## -*- coding: utf-8 -*- | ||||
| <%inherit file="/products/crud.mako" /> | ||||
| <%inherit file="/master/view.mako" /> | ||||
| <%namespace file="/forms/lib.mako" import="render_field_readonly" /> | ||||
| 
 | ||||
| <% product = instance %> | ||||
| 
 | ||||
| <%def name="head_tags()"> | ||||
|   ${parent.head_tags()} | ||||
|   <style type="text/css"> | ||||
|  | @ -20,7 +22,12 @@ | |||
|   </style> | ||||
| </%def> | ||||
| 
 | ||||
| <% product = form.fieldset.model %> | ||||
| <%def name="context_menu_items()"> | ||||
|   ${parent.context_menu_items()} | ||||
|   % if version_count is not Undefined and request.has_perm('product.versions.view'): | ||||
|       <li>${h.link_to("View Change History ({})".format(version_count), url('product.versions', uuid=product.uuid))}</li> | ||||
|   % endif | ||||
| </%def> | ||||
| 
 | ||||
| <%def name="render_organization_fields(form)"> | ||||
|     ${render_field_readonly(form.fieldset.department)} | ||||
|  | @ -330,20 +330,18 @@ class MasterView(View): | |||
|         """ | ||||
|         return getattr(cls, 'grid_key', '{0}s'.format(cls.get_normalized_model_name())) | ||||
| 
 | ||||
|     def make_grid_kwargs(self): | ||||
|     def make_grid_kwargs(self, **kwargs): | ||||
|         """ | ||||
|         Return a dictionary of kwargs to be passed to the factory when creating | ||||
|         new grid instances. | ||||
|         """ | ||||
|         return { | ||||
|         defaults = { | ||||
|             'width': 'full', | ||||
|             'filterable': self.filterable, | ||||
|             'sortable': True, | ||||
|             'default_sortkey': getattr(self, 'default_sortkey', None), | ||||
|             'sortdir': getattr(self, 'sortdir', 'asc'), | ||||
|             'pageable': self.pageable, | ||||
|             'main_actions': self.get_main_actions(), | ||||
|             'more_actions': self.get_more_actions(), | ||||
|             'checkboxes': self.checkboxes, | ||||
|             'checked': self.checked, | ||||
|             'row_attrs': self.get_row_attrs, | ||||
|  | @ -353,6 +351,15 @@ class MasterView(View): | |||
|             'permission_prefix': self.get_permission_prefix(), | ||||
|             'route_prefix': self.get_route_prefix(), | ||||
|         } | ||||
|         if 'main_actions' not in kwargs and 'more_actions' not in kwargs: | ||||
|             main, more = self.get_grid_actions() | ||||
|             defaults['main_actions'] = main | ||||
|             defaults['more_actions'] = more | ||||
|         defaults.update(kwargs) | ||||
|         return defaults | ||||
| 
 | ||||
|     def get_grid_actions(self): | ||||
|         return self.get_main_actions(), self.get_more_actions() | ||||
| 
 | ||||
|     def get_row_attrs(self, row, i): | ||||
|         """ | ||||
|  | @ -379,7 +386,8 @@ class MasterView(View): | |||
|         Return a list of 'main' actions for the grid. | ||||
|         """ | ||||
|         actions = [] | ||||
|         if self.viewable: | ||||
|         prefix = self.get_permission_prefix() | ||||
|         if self.viewable and self.request.has_perm('{}.view'.format(prefix)): | ||||
|             actions.append(self.make_action('view', icon='zoomin')) | ||||
|         return actions | ||||
| 
 | ||||
|  | @ -388,9 +396,10 @@ class MasterView(View): | |||
|         Return a list of 'more' actions for the grid. | ||||
|         """ | ||||
|         actions = [] | ||||
|         if self.editable: | ||||
|         prefix = self.get_permission_prefix() | ||||
|         if self.editable and self.request.has_perm('{}.edit'.format(prefix)): | ||||
|             actions.append(self.make_action('edit', icon='pencil')) | ||||
|         if self.deletable: | ||||
|         if self.deletable and self.request.has_perm('{}.delete'.format(prefix)): | ||||
|             actions.append(self.make_action('delete', icon='trash', url=self.default_delete_url)) | ||||
|         return actions | ||||
| 
 | ||||
|  | @ -421,14 +430,14 @@ class MasterView(View): | |||
|             values = [getattr(row, k) for k in keys] | ||||
|             return dict(zip(keys, values)) | ||||
| 
 | ||||
|     def make_grid(self): | ||||
|     def make_grid(self, **kwargs): | ||||
|         """ | ||||
|         Make and return a new (configured) grid instance. | ||||
|         """ | ||||
|         factory = self.get_grid_factory() | ||||
|         key = self.get_grid_key() | ||||
|         data = self.get_data() | ||||
|         kwargs = self.make_grid_kwargs() | ||||
|         data = self.get_data(session=kwargs.get('session')) | ||||
|         kwargs = self.make_grid_kwargs(**kwargs) | ||||
|         grid = factory(key, self.request, data=data, model_class=self.get_model_class(error=False), **kwargs) | ||||
|         self.configure_grid(grid) | ||||
|         grid.load_settings() | ||||
|  |  | |||
|  | @ -106,8 +106,12 @@ class TerseRecipientsFieldRenderer(formalchemy.FieldRenderer): | |||
|         recipients = self.raw_value | ||||
|         if not recipients: | ||||
|             return '' | ||||
|         recips = filter(lambda r: r.recipient is not self.request.user, recipients) | ||||
|         message = self.field.parent.model | ||||
|         recips = [r for r in recipients if r.recipient is not self.request.user] | ||||
|         recips = sorted([r.recipient.display_name for r in recips]) | ||||
|         if len(recips) < len(recipients) and ( | ||||
|                 message.sender is not self.request.user or not recips): | ||||
|             recips.insert(0, 'you') | ||||
|         if len(recips) < 5: | ||||
|             return ', '.join(recips) | ||||
|         return "{}, ...".format(', '.join(recips[:4])) | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| ################################################################################ | ||||
| # | ||||
| #  Rattail -- Retail Software Framework | ||||
| #  Copyright © 2010-2015 Lance Edgar | ||||
| #  Copyright © 2010-2016 Lance Edgar | ||||
| # | ||||
| #  This file is part of Rattail. | ||||
| # | ||||
|  | @ -32,158 +32,126 @@ import re | |||
| import sqlalchemy as sa | ||||
| from sqlalchemy import orm | ||||
| 
 | ||||
| from rattail import enum, pod, sil, batches | ||||
| from rattail.db import model, api, auth, Session as RattailSession | ||||
| from rattail.gpc import GPC | ||||
| from rattail.threads import Thread | ||||
| from rattail.exceptions import LabelPrintingError | ||||
| 
 | ||||
| import formalchemy | ||||
| from pyramid.httpexceptions import HTTPFound | ||||
| from pyramid import httpexceptions | ||||
| from pyramid.renderers import render_to_response | ||||
| from webhelpers.html import tags | ||||
| 
 | ||||
| import rattail.labels | ||||
| from rattail import enum | ||||
| from rattail import sil | ||||
| from rattail import batches | ||||
| from rattail.threads import Thread | ||||
| from rattail.exceptions import LabelPrintingError | ||||
| from rattail.db import model | ||||
| from rattail.db.model import ( | ||||
|     Product, ProductPrice, ProductCost, ProductCode, | ||||
|     Brand, Vendor, Department, Subdepartment, LabelProfile) | ||||
| from rattail.gpc import GPC | ||||
| from rattail.db.api import get_product_by_upc | ||||
| from rattail.db.util import configure_session | ||||
| from rattail.pod import get_image_url, get_image_path | ||||
| 
 | ||||
| from tailbone.views import SearchableAlchemyGridView, CrudView, AutocompleteView | ||||
| from tailbone.views.continuum import VersionView, version_defaults | ||||
| from tailbone.forms import EnumFieldRenderer, DateTimeFieldRenderer | ||||
| from tailbone import forms | ||||
| from tailbone.db import Session | ||||
| from tailbone.forms import GPCFieldRenderer, BrandFieldRenderer, PriceFieldRenderer | ||||
| from tailbone.forms.renderers import products as forms | ||||
| from tailbone.views import MasterView, SearchableAlchemyGridView, AutocompleteView | ||||
| from tailbone.views.continuum import VersionView, version_defaults | ||||
| from tailbone.forms.renderers import products as products_forms | ||||
| from tailbone.newgrids import GridAction | ||||
| from tailbone.progress import SessionProgress | ||||
| 
 | ||||
| 
 | ||||
| class ProductsGrid(SearchableAlchemyGridView): | ||||
| class ProductsView(MasterView): | ||||
|     """ | ||||
|     Master view for the Product class. | ||||
|     """ | ||||
|     model_class = model.Product | ||||
| 
 | ||||
|     mapped_class = Product | ||||
|     config_prefix = 'products' | ||||
|     sort = 'description' | ||||
|     # child_version_classes = [ | ||||
|     #     (model.ProductCode, 'product_uuid'), | ||||
|     #     (model.ProductCost, 'product_uuid'), | ||||
|     #     (model.ProductPrice, 'product_uuid'), | ||||
|     #     ] | ||||
| 
 | ||||
|     # These aliases enable the grid queries to filter products which may be | ||||
|     # purchased from *any* vendor, and yet sort by only the "preferred" vendor | ||||
|     # (since that's what shows up in the grid column). | ||||
|     ProductCostAny = orm.aliased(ProductCost) | ||||
|     VendorAny = orm.aliased(Vendor) | ||||
|     ProductCostAny = orm.aliased(model.ProductCost) | ||||
|     VendorAny = orm.aliased(model.Vendor) | ||||
| 
 | ||||
|     def join_map(self): | ||||
|     def __init__(self, request): | ||||
|         self.request = request | ||||
|         self.print_labels = request.rattail_config.getbool('tailbone', 'products.print_labels', default=False) | ||||
| 
 | ||||
|     def query(self, session): | ||||
|         user = self.request.user | ||||
|         if user and user not in session: | ||||
|             user = session.merge(user) | ||||
| 
 | ||||
|         query = session.query(model.Product) | ||||
|         if not auth.has_permission(session, user, 'products.view_deleted'): | ||||
|             query = query.filter(model.Product.deleted == False) | ||||
| 
 | ||||
|         # TODO: This used to be a good idea I thought...but in dev it didn't | ||||
|         # seem to make much difference, except with a larger (50K) data set it | ||||
|         # totally bogged things down instead of helping... | ||||
|         # query = query\ | ||||
|         #     .options(orm.joinedload(model.Product.brand))\ | ||||
|         #     .options(orm.joinedload(model.Product.department))\ | ||||
|         #     .options(orm.joinedload(model.Product.subdepartment))\ | ||||
|         #     .options(orm.joinedload(model.Product.regular_price))\ | ||||
|         #     .options(orm.joinedload(model.Product.current_price))\ | ||||
|         #     .options(orm.joinedload(model.Product.vendor)) | ||||
| 
 | ||||
|         return query | ||||
| 
 | ||||
|     def configure_grid(self, g): | ||||
| 
 | ||||
|         def join_vendor(q): | ||||
|             q = q.outerjoin( | ||||
|                 ProductCost, | ||||
|             return q.outerjoin(model.ProductCost, | ||||
|                                sa.and_( | ||||
|                     ProductCost.product_uuid == Product.uuid, | ||||
|                     ProductCost.preference == 1, | ||||
|                     )) | ||||
|             q = q.outerjoin(Vendor) | ||||
|             return q | ||||
|                                    model.ProductCost.product_uuid == model.Product.uuid, | ||||
|                                    model.ProductCost.preference == 1))\ | ||||
|                     .outerjoin(model.Vendor) | ||||
| 
 | ||||
|         def join_vendor_any(q): | ||||
|             q = q.outerjoin( | ||||
|                 self.ProductCostAny, | ||||
|                 self.ProductCostAny.product_uuid == Product.uuid) | ||||
|             q = q.outerjoin( | ||||
|                 self.VendorAny, | ||||
|             return q.outerjoin(self.ProductCostAny, | ||||
|                                self.ProductCostAny.product_uuid == model.Product.uuid)\ | ||||
|                     .outerjoin(self.VendorAny, | ||||
|                                self.VendorAny.uuid == self.ProductCostAny.vendor_uuid) | ||||
|             return q | ||||
| 
 | ||||
|         return { | ||||
|             'brand': | ||||
|                 lambda q: q.outerjoin(Brand), | ||||
|             'family': | ||||
|                 lambda q: q.outerjoin(model.Family), | ||||
|             'department': | ||||
|                 lambda q: q.outerjoin(Department, | ||||
|                                       Department.uuid == Product.department_uuid), | ||||
|             'subdepartment': | ||||
|                 lambda q: q.outerjoin(Subdepartment, | ||||
|                                       Subdepartment.uuid == Product.subdepartment_uuid), | ||||
|             u'report_code': | ||||
|                 lambda q: q.outerjoin(model.ReportCode), | ||||
|             'regular_price': | ||||
|                 lambda q: q.outerjoin(ProductPrice, | ||||
|                                       ProductPrice.uuid == Product.regular_price_uuid), | ||||
|             'current_price': | ||||
|                 lambda q: q.outerjoin(ProductPrice, | ||||
|                                       ProductPrice.uuid == Product.current_price_uuid), | ||||
|             'vendor': | ||||
|                 join_vendor, | ||||
|             'vendor_any': | ||||
|                 join_vendor_any, | ||||
|             'code': | ||||
|                 lambda q: q.outerjoin(ProductCode), | ||||
|             } | ||||
|         g.joiners['brand'] = lambda q: q.outerjoin(model.Brand) | ||||
|         g.joiners['family'] = lambda q: q.outerjoin(model.Family) | ||||
|         g.joiners['department'] = lambda q: q.outerjoin(model.Department, | ||||
|                                                         model.Department.uuid == model.Product.department_uuid) | ||||
|         g.joiners['subdepartment'] = lambda q: q.outerjoin(model.Subdepartment, | ||||
|                                                            model.Subdepartment.uuid == model.Product.subdepartment_uuid) | ||||
|         g.joiners['report_code'] = lambda q: q.outerjoin(model.ReportCode) | ||||
|         g.joiners['regular_price'] = lambda q: q.outerjoin(model.ProductPrice, | ||||
|                                                            model.ProductPrice.uuid == model.Product.regular_price_uuid) | ||||
|         g.joiners['current_price'] = lambda q: q.outerjoin(model.ProductPrice, | ||||
|                                                            model.ProductPrice.uuid == model.Product.current_price_uuid) | ||||
|         g.joiners['vendor'] = join_vendor | ||||
|         g.joiners['vendor_any'] = join_vendor_any | ||||
|         g.joiners['code'] = lambda q: q.outerjoin(model.ProductCode) | ||||
| 
 | ||||
|     def filter_map(self): | ||||
|         return self.make_filter_map( | ||||
|             ilike=['description', 'size'], | ||||
|             upc=self.filter_gpc(model.Product.upc), | ||||
|             brand=self.filter_ilike(Brand.name), | ||||
|             family=self.filter_ilike(model.Family.name), | ||||
|             department=self.filter_ilike(Department.name), | ||||
|             report_code=self.filter_ilike(model.ReportCode.name), | ||||
|             subdepartment=self.filter_ilike(Subdepartment.name), | ||||
|             vendor=self.filter_ilike(Vendor.name), | ||||
|             vendor_any=self.filter_ilike(self.VendorAny.name), | ||||
|             code=self.filter_ilike(ProductCode.code)) | ||||
|         g.sorters['brand'] = g.make_sorter(model.Brand.name) | ||||
|         g.sorters['department'] = g.make_sorter(model.Department.name) | ||||
|         g.sorters['subdepartment'] = g.make_sorter(model.Subdepartment.name) | ||||
|         g.sorters['vendor'] = g.make_sorter(model.Vendor.name) | ||||
| 
 | ||||
|     def filter_config(self): | ||||
|         return self.make_filter_config( | ||||
|             include_filter_upc=True, | ||||
|             filter_type_upc='is', | ||||
|             filter_label_upc="UPC", | ||||
|             include_filter_brand=True, | ||||
|             filter_type_brand='lk', | ||||
|             include_filter_description=True, | ||||
|             filter_type_description='lk', | ||||
|             include_filter_department=True, | ||||
|             filter_type_department='lk', | ||||
|             filter_label_vendor="Vendor (preferred)", | ||||
|             include_filter_vendor_any=True, | ||||
|             filter_label_vendor_any="Vendor (any)", | ||||
|             filter_type_vendor_any='lk') | ||||
|         g.filters['upc'].default_active = True | ||||
|         g.filters['upc'].default_verb = 'equal' | ||||
|         g.filters['upc'].label = "UPC" | ||||
|         g.filters['description'].default_active = True | ||||
|         g.filters['description'].default_verb = 'contains' | ||||
|         g.filters['brand'] = g.make_filter('brand', model.Brand.name, | ||||
|                                            default_active=True, default_verb='contains') | ||||
|         g.filters['family'] = g.make_filter('family', model.Family.name) | ||||
|         g.filters['department'] = g.make_filter('department', model.Department.name, | ||||
|                                                 default_active=True, default_verb='contains') | ||||
|         g.filters['subdepartment'] = g.make_filter('subdepartment', model.Subdepartment.name) | ||||
|         g.filters['report_code'] = g.make_filter('report_code', model.ReportCode.name) | ||||
|         g.filters['vendor'] = g.make_filter('vendor', model.Vendor.name, label="Vendor (preferred)") | ||||
|         g.filters['vendor_any'] = g.make_filter('vendor_any', self.VendorAny.name, label="Vendor (any)") | ||||
|         g.filters['code'] = g.make_filter('code', model.ProductCode.code) | ||||
| 
 | ||||
|     def sort_map(self): | ||||
|         return self.make_sort_map( | ||||
|             'upc', 'description', 'size', | ||||
|             brand=self.sorter(Brand.name), | ||||
|             department=self.sorter(Department.name), | ||||
|             subdepartment=self.sorter(Subdepartment.name), | ||||
|             regular_price=self.sorter(ProductPrice.price), | ||||
|             current_price=self.sorter(ProductPrice.price), | ||||
|             vendor=self.sorter(Vendor.name)) | ||||
|         g.default_sortkey = 'description' | ||||
| 
 | ||||
|     def query(self): | ||||
|         q = self.make_query() | ||||
|         if not self.request.has_perm('products.view_deleted'): | ||||
|             q = q.filter(model.Product.deleted == False) | ||||
|         q = q.options(orm.joinedload(Product.brand)) | ||||
|         q = q.options(orm.joinedload(Product.department)) | ||||
|         q = q.options(orm.joinedload(Product.subdepartment)) | ||||
|         q = q.options(orm.joinedload(Product.regular_price)) | ||||
|         q = q.options(orm.joinedload(Product.current_price)) | ||||
|         q = q.options(orm.joinedload(Product.vendor)) | ||||
|         return q | ||||
| 
 | ||||
|     def grid(self): | ||||
|         def extra_row_class(row, i): | ||||
|             cls = [] | ||||
|             if row.not_for_sale: | ||||
|                 cls.append('not-for-sale') | ||||
|             if row.deleted: | ||||
|                 cls.append('deleted') | ||||
|             return ' '.join(cls) if cls else None | ||||
|         g = self.make_grid(extra_row_class=extra_row_class) | ||||
|         g.upc.set(renderer=GPCFieldRenderer) | ||||
|         g.regular_price.set(renderer=PriceFieldRenderer) | ||||
|         g.current_price.set(renderer=PriceFieldRenderer) | ||||
|         g.upc.set(renderer=forms.renderers.GPCFieldRenderer) | ||||
|         g.regular_price.set(renderer=forms.renderers.PriceFieldRenderer) | ||||
|         g.current_price.set(renderer=forms.renderers.PriceFieldRenderer) | ||||
|         g.configure( | ||||
|             include=[ | ||||
|                 g.upc.label("UPC"), | ||||
|  | @ -197,80 +165,68 @@ class ProductsGrid(SearchableAlchemyGridView): | |||
|                 ], | ||||
|             readonly=True) | ||||
| 
 | ||||
|         if self.request.has_perm('products.read'): | ||||
|             g.viewable = True | ||||
|             g.view_route_name = 'product.read' | ||||
|         if self.request.has_perm('products.update'): | ||||
|             g.editable = True | ||||
|             g.edit_route_name = 'product.update' | ||||
|         if self.request.has_perm('products.delete'): | ||||
|             g.deletable = True | ||||
|             g.delete_route_name = 'product.delete' | ||||
|         # TODO: need to check for 'print labels' permission here also | ||||
|         if self.print_labels: | ||||
|             g.more_actions.append(GridAction('print_label', icon='print')) | ||||
| 
 | ||||
|         # Maybe add Print Label column. | ||||
|         if self.rattail_config.getbool('tailbone', 'products.print_labels', default=True): | ||||
|             q = Session.query(LabelProfile) | ||||
|             if q.count(): | ||||
|                 def labels(row): | ||||
|                     return tags.link_to("Print", '#', class_='print-label') | ||||
|                 g.add_column('labels', "Labels", labels) | ||||
|     def template_kwargs_index(self, **kwargs): | ||||
|         if self.print_labels: | ||||
|             kwargs['label_profiles'] = Session.query(model.LabelProfile)\ | ||||
|                                               .filter(model.LabelProfile.visible == True)\ | ||||
|                                               .order_by(model.LabelProfile.ordinal)\ | ||||
|                                               .all() | ||||
|         return kwargs | ||||
| 
 | ||||
|         return g | ||||
|     def row_attrs(self, row, i): | ||||
| 
 | ||||
|     def render_kwargs(self): | ||||
|         q = Session.query(LabelProfile) | ||||
|         q = q.filter(LabelProfile.visible == True) | ||||
|         q = q.order_by(LabelProfile.ordinal) | ||||
|         return {'label_profiles': q.all()} | ||||
|         attrs = {'uuid': row.uuid} | ||||
| 
 | ||||
|         classes = [] | ||||
|         if row.not_for_sale: | ||||
|             classes.append('not-for-sale') | ||||
|         if row.deleted: | ||||
|             classes.append('deleted') | ||||
|         if classes: | ||||
|             attrs['class_'] = ' '.join(classes) | ||||
| 
 | ||||
| class ProductCrud(CrudView): | ||||
|     """ | ||||
|     Product CRUD view class. | ||||
|     """ | ||||
|     mapped_class = Product | ||||
|     home_route = 'products' | ||||
|     child_version_classes = [ | ||||
|         (model.ProductCode, 'product_uuid'), | ||||
|         (model.ProductCost, 'product_uuid'), | ||||
|         (model.ProductPrice, 'product_uuid'), | ||||
|         ] | ||||
|         return attrs | ||||
| 
 | ||||
|     def get_model(self, key): | ||||
|         model = super(ProductCrud, self).get_model(key) | ||||
|         if model: | ||||
|             return model | ||||
|         model = Session.query(ProductPrice).get(key) | ||||
|         if model: | ||||
|             return model.product | ||||
|         return None | ||||
|     def get_instance(self): | ||||
|         key = self.request.matchdict['uuid'] | ||||
|         product = Session.query(model.Product).get(key) | ||||
|         if product: | ||||
|             return product | ||||
|         price = Session.query(model.ProductPrice).get(key) | ||||
|         if price: | ||||
|             return price.product | ||||
|         raise httpexceptions.HTTPNotFound() | ||||
| 
 | ||||
|     def fieldset(self, model): | ||||
|         fs = self.make_fieldset(model) | ||||
|         fs.upc.set(renderer=GPCFieldRenderer) | ||||
|     def configure_fieldset(self, fs): | ||||
| 
 | ||||
|         fs.upc.set(renderer=forms.renderers.GPCFieldRenderer) | ||||
|         fs.brand.set(options=[]) | ||||
|         fs.unit_of_measure.set(renderer=EnumFieldRenderer(enum.UNIT_OF_MEASURE)) | ||||
|         fs.regular_price.set(renderer=PriceFieldRenderer) | ||||
|         fs.current_price.set(renderer=PriceFieldRenderer) | ||||
|         fs.unit_of_measure.set(renderer=forms.renderers.EnumFieldRenderer(enum.UNIT_OF_MEASURE)) | ||||
|         fs.regular_price.set(renderer=forms.renderers.PriceFieldRenderer) | ||||
|         fs.current_price.set(renderer=forms.renderers.PriceFieldRenderer) | ||||
|         fs.last_sold.set(renderer=forms.renderers.DateTimeFieldRenderer(self.rattail_config)) | ||||
| 
 | ||||
|         fs.append(formalchemy.Field('current_price_ends')) | ||||
|         fs.current_price_ends.set(value=lambda p: p.current_price.ends if p.current_price else None) | ||||
|         fs.current_price_ends.set(renderer=DateTimeFieldRenderer(self.request.rattail_config)) | ||||
|         fs.append(formalchemy.Field('current_price_ends', | ||||
|                                     value=lambda p: p.current_price.ends if p.current_price else None, | ||||
|                                     renderer=forms.renderers.DateTimeFieldRenderer(self.rattail_config))) | ||||
| 
 | ||||
|         fs.last_sold.set(renderer=DateTimeFieldRenderer(self.request.rattail_config)) | ||||
|         fs.configure( | ||||
|             include=[ | ||||
|                 fs.upc.label("UPC"), | ||||
|                 fs.brand.with_renderer(BrandFieldRenderer), | ||||
|                 fs.brand.with_renderer(forms.renderers.BrandFieldRenderer), | ||||
|                 fs.description, | ||||
|                 fs.unit_size, | ||||
|                 fs.unit_of_measure.label("Unit of Measure"), | ||||
|                 fs.size, | ||||
|                 fs.weighed, | ||||
|                 fs.case_pack, | ||||
|                 fs.department.with_renderer(forms.DepartmentFieldRenderer), | ||||
|                 fs.subdepartment.with_renderer(forms.SubdepartmentFieldRenderer), | ||||
|                 fs.category.with_renderer(forms.CategoryFieldRenderer), | ||||
|                 fs.department.with_renderer(products_forms.DepartmentFieldRenderer), | ||||
|                 fs.subdepartment.with_renderer(products_forms.SubdepartmentFieldRenderer), | ||||
|                 fs.category.with_renderer(products_forms.CategoryFieldRenderer), | ||||
|                 fs.family, | ||||
|                 fs.report_code, | ||||
|                 fs.regular_price, | ||||
|  | @ -285,33 +241,119 @@ class ProductCrud(CrudView): | |||
|                 fs.deleted, | ||||
|                 fs.last_sold, | ||||
|                 ]) | ||||
|         if not self.readonly: | ||||
|         if not self.viewing: | ||||
|             del fs.regular_price | ||||
|             del fs.current_price | ||||
|         if not self.request.has_perm('products.view_deleted'): | ||||
|             del fs.deleted | ||||
|         return fs | ||||
| 
 | ||||
|     def pre_crud(self, product): | ||||
|         self.product_deleted = not self.creating and product.deleted | ||||
| 
 | ||||
|     def post_crud(self, product, form): | ||||
|         if self.product_deleted: | ||||
|             self.request.session.flash("This product is marked as deleted.", 'error') | ||||
| 
 | ||||
|     def template_kwargs(self, form): | ||||
|         kwargs = super(ProductCrud, self).template_kwargs(form) | ||||
|     def template_kwargs_view(self, **kwargs): | ||||
|         kwargs['image'] = False | ||||
|         product = form.fieldset.model | ||||
|         product = kwargs['instance'] | ||||
|         if product.upc: | ||||
|             kwargs['image_url'] = get_image_url( | ||||
|                 self.request.rattail_config, product.upc) | ||||
|             kwargs['image_path'] = get_image_path( | ||||
|                 self.request.rattail_config, product.upc) | ||||
|             kwargs['image_url'] = pod.get_image_url(self.rattail_config, product.upc) | ||||
|             kwargs['image_path'] = pod.get_image_path(self.rattail_config, product.upc) | ||||
|             if os.path.exists(kwargs['image_path']): | ||||
|                 kwargs['image'] = True | ||||
|         return kwargs | ||||
| 
 | ||||
|     def edit(self): | ||||
|         # TODO: Should add some more/better hooks, so don't have to duplicate | ||||
|         # so much code here. | ||||
|         self.editing = True | ||||
|         instance = self.get_instance() | ||||
|         form = self.make_form(instance) | ||||
|         product_deleted = instance.deleted | ||||
|         if self.request.method == 'POST': | ||||
|             if form.validate(): | ||||
|                 self.save_form(form) | ||||
|                 self.after_edit(instance) | ||||
|                 self.request.session.flash("{} {} has been updated.".format( | ||||
|                     self.get_model_title(), self.get_instance_title(instance))) | ||||
|                 return self.redirect(self.get_action_url('view', instance)) | ||||
|         if product_deleted: | ||||
|             self.request.session.flash("This product is marked as deleted.", 'error') | ||||
|         return self.render_to_response('edit', {'instance': instance, | ||||
|                                                 'instance_title': self.get_instance_title(instance), | ||||
|                                                 'form': form}) | ||||
| 
 | ||||
|     def make_batch(self): | ||||
|         if self.request.method == 'POST': | ||||
|             provider = self.request.POST.get('provider') | ||||
|             if provider: | ||||
|                 provider = batches.get_provider(self.rattail_config, provider) | ||||
|                 if provider: | ||||
| 
 | ||||
|                     if self.request.POST.get('params') == 'True': | ||||
|                         provider.set_params(Session(), **self.request.POST) | ||||
| 
 | ||||
|                     else: | ||||
|                         try: | ||||
|                             url = self.request.route_url('batch_params.{}'.format(provider.name)) | ||||
|                         except KeyError: | ||||
|                             pass | ||||
|                         else: | ||||
|                             self.request.session['referer'] = self.request.current_route_url() | ||||
|                             return httpexceptions.HTTPFound(location=url) | ||||
|                      | ||||
|                     progress = SessionProgress(self.request, 'products.batch') | ||||
|                     thread = Thread(target=self.make_batch_thread, args=(provider, progress)) | ||||
|                     thread.start() | ||||
|                     kwargs = { | ||||
|                         'key': 'products.batch', | ||||
|                         'cancel_url': self.request.route_url('products'), | ||||
|                         'cancel_msg': "Batch creation was canceled.", | ||||
|                         } | ||||
|                     return render_to_response('/progress.mako', kwargs, request=self.request) | ||||
| 
 | ||||
|         enabled = self.rattail_config.get('rattail.pyramid', 'batches.providers') | ||||
|         if enabled: | ||||
|             enabled = enabled.split() | ||||
| 
 | ||||
|         providers = [] | ||||
|         for provider in batches.iter_providers(): | ||||
|             if not enabled or provider.name in enabled: | ||||
|                 providers.append((provider.name, provider.description)) | ||||
| 
 | ||||
|         return {'providers': providers} | ||||
| 
 | ||||
|     def make_batch_thread(self, provider, progress): | ||||
|         """ | ||||
|         Threat target for making a batch from current products query. | ||||
|         """ | ||||
|         session = RattailSession() | ||||
| 
 | ||||
|         grid = self.make_grid(session=session, pageable=False, | ||||
|                               main_actions=[], more_actions=[]) | ||||
|         products = grid._fa_grid.rows | ||||
| 
 | ||||
|         batch = provider.make_batch(session, products, progress) | ||||
|         if not batch: | ||||
|             session.rollback() | ||||
|             session.close() | ||||
|             return | ||||
| 
 | ||||
|         session.commit() | ||||
|         session.refresh(batch) | ||||
|         session.close() | ||||
| 
 | ||||
|         progress.session.load() | ||||
|         progress.session['complete'] = True | ||||
|         progress.session['success_url'] = self.request.route_url('batch.read', uuid=batch.uuid) | ||||
|         progress.session['success_msg'] = 'Batch "{}" has been created.'.format(batch.description) | ||||
|         progress.session.save() | ||||
| 
 | ||||
|     @classmethod | ||||
|     def defaults(cls, config): | ||||
| 
 | ||||
|         # make batch from product query | ||||
|         config.add_route('products.create_batch', '/products/batch') | ||||
|         config.add_view(cls, attr='make_batch', route_name='products.create_batch', | ||||
|                         renderer='/products/batch.mako', | ||||
|                         permission='batches.create') | ||||
| 
 | ||||
|         cls._defaults(config) | ||||
| 
 | ||||
| 
 | ||||
| class ProductVersionView(VersionView): | ||||
|     """ | ||||
|  | @ -331,7 +373,7 @@ class ProductVersionView(VersionView): | |||
|         """ | ||||
|         uuid = self.request.matchdict['uuid'] | ||||
|         product = Session.query(model.Product).get(uuid) | ||||
|         assert product, "No product found for UUID: {0}".format(repr(uuid)) | ||||
|         assert product, "No product found for UUID: {}".format(repr(uuid)) | ||||
|         if product.deleted: | ||||
|             self.request.session.flash("This product is marked as deleted.", 'error') | ||||
| 
 | ||||
|  | @ -354,8 +396,8 @@ class ProductsAutocomplete(AutocompleteView): | |||
|     def query(self, term): | ||||
|         q = Session.query(model.Product).outerjoin(model.Brand) | ||||
|         q = q.filter(sa.or_( | ||||
|                 model.Brand.name.ilike('%{0}%'.format(term)), | ||||
|                 model.Product.description.ilike('%{0}%'.format(term)))) | ||||
|                 model.Brand.name.ilike('%{}%'.format(term)), | ||||
|                 model.Product.description.ilike('%{}%'.format(term)))) | ||||
|         if not self.request.has_perm('products.view_deleted'): | ||||
|             q = q.filter(model.Product.deleted == False) | ||||
|         q = q.order_by(model.Brand.name, model.Product.description) | ||||
|  | @ -377,11 +419,11 @@ def products_search(request): | |||
|     upc = request.GET.get('upc', '').strip() | ||||
|     upc = re.sub(r'\D', '', upc) | ||||
|     if upc: | ||||
|         product = get_product_by_upc(Session, upc) | ||||
|         product = api.get_product_by_upc(Session(), upc) | ||||
|         if not product: | ||||
|             # Try again, assuming caller did not include check digit. | ||||
|             upc = GPC(upc, calc_check_digit='upc') | ||||
|             product = get_product_by_upc(Session, upc) | ||||
|             product = api.get_product_by_upc(Session(), upc) | ||||
|         if product: | ||||
|             if product.deleted and not request.has_perm('products.view_deleted'): | ||||
|                 product = None | ||||
|  | @ -396,12 +438,12 @@ def products_search(request): | |||
| 
 | ||||
| def print_labels(request): | ||||
|     profile = request.params.get('profile') | ||||
|     profile = Session.query(LabelProfile).get(profile) if profile else None | ||||
|     profile = Session.query(model.LabelProfile).get(profile) if profile else None | ||||
|     if not profile: | ||||
|         return {'error': "Label profile not found"} | ||||
| 
 | ||||
|     product = request.params.get('product') | ||||
|     product = Session.query(Product).get(product) if product else None | ||||
|     product = Session.query(model.Product).get(product) if product else None | ||||
|     if not product: | ||||
|         return {'error': "Product not found"} | ||||
| 
 | ||||
|  | @ -421,114 +463,19 @@ def print_labels(request): | |||
|     return {} | ||||
| 
 | ||||
| 
 | ||||
| class CreateProductsBatch(ProductsGrid): | ||||
| 
 | ||||
|     def make_batch(self, provider, progress): | ||||
|         from rattail.db import Session | ||||
|         session = Session() | ||||
|         configure_session(self.request.rattail_config, session) | ||||
| 
 | ||||
|         self._filter_config = self.filter_config() | ||||
|         self._sort_config = self.sort_config() | ||||
|         products = self.make_query(session) | ||||
| 
 | ||||
|         batch = provider.make_batch(session, products, progress) | ||||
|         if not batch: | ||||
|             session.rollback() | ||||
|             session.close() | ||||
|             return | ||||
| 
 | ||||
|         session.commit() | ||||
|         session.refresh(batch) | ||||
|         session.close() | ||||
| 
 | ||||
|         progress.session.load() | ||||
|         progress.session['complete'] = True | ||||
|         progress.session['success_url'] = self.request.route_url('batch.read', uuid=batch.uuid) | ||||
|         progress.session['success_msg'] = "Batch \"%s\" has been created." % batch.description | ||||
|         progress.session.save() | ||||
| 
 | ||||
|     def __call__(self): | ||||
|         if self.request.POST: | ||||
|             provider = self.request.POST.get('provider') | ||||
|             if provider: | ||||
|                 provider = batches.get_provider(self.request.rattail_config, provider) | ||||
|                 if provider: | ||||
| 
 | ||||
|                     if self.request.POST.get('params') == 'True': | ||||
|                         provider.set_params(Session(), **self.request.POST) | ||||
| 
 | ||||
|                     else: | ||||
|                         try: | ||||
|                             url = self.request.route_url('batch_params.%s' % provider.name) | ||||
|                         except KeyError: | ||||
|                             pass | ||||
|                         else: | ||||
|                             self.request.session['referer'] = self.request.current_route_url() | ||||
|                             return HTTPFound(location=url) | ||||
|                      | ||||
|                     progress = SessionProgress(self.request, 'products.batch') | ||||
|                     thread = Thread(target=self.make_batch, args=(provider, progress)) | ||||
|                     thread.start() | ||||
|                     kwargs = { | ||||
|                         'key': 'products.batch', | ||||
|                         'cancel_url': self.request.route_url('products'), | ||||
|                         'cancel_msg': "Batch creation was canceled.", | ||||
|                         } | ||||
|                     return render_to_response('/progress.mako', kwargs, request=self.request) | ||||
| 
 | ||||
|         enabled = self.request.rattail_config.get('rattail.pyramid', 'batches.providers') | ||||
|         if enabled: | ||||
|             enabled = enabled.split() | ||||
| 
 | ||||
|         providers = [] | ||||
|         for provider in batches.iter_providers(): | ||||
|             if not enabled or provider.name in enabled: | ||||
|                 providers.append((provider.name, provider.description)) | ||||
| 
 | ||||
|         return {'providers': providers} | ||||
| 
 | ||||
| 
 | ||||
| def add_routes(config): | ||||
|     config.add_route('products', '/products') | ||||
|     config.add_route('products.autocomplete', '/products/autocomplete') | ||||
|     config.add_route('products.search', '/products/search') | ||||
|     config.add_route('products.print_labels', '/products/labels') | ||||
|     config.add_route('products.create_batch', '/products/batch') | ||||
|     config.add_route('product.create', '/products/new') | ||||
|     config.add_route('product.read', '/products/{uuid}') | ||||
|     config.add_route('product.update', '/products/{uuid}/edit') | ||||
|     config.add_route('product.delete', '/products/{uuid}/delete') | ||||
|      | ||||
| 
 | ||||
| def includeme(config): | ||||
|     add_routes(config) | ||||
| 
 | ||||
|     config.add_view(ProductsGrid, route_name='products', | ||||
|                     renderer='/products/index.mako', | ||||
|                     permission='products.list') | ||||
| 
 | ||||
|     config.add_route('products.autocomplete', '/products/autocomplete') | ||||
|     config.add_view(ProductsAutocomplete, route_name='products.autocomplete', | ||||
|                     renderer='json', | ||||
|                     permission='products.list') | ||||
|                     renderer='json', permission='products.list') | ||||
| 
 | ||||
|     config.add_route('products.print_labels', '/products/labels') | ||||
|     config.add_view(print_labels, route_name='products.print_labels', | ||||
|                     renderer='json', permission='products.print_labels') | ||||
|     config.add_view(CreateProductsBatch, route_name='products.create_batch', | ||||
|                     renderer='/products/batch.mako', | ||||
|                     permission='batches.create') | ||||
|     config.add_view(ProductCrud, attr='create', route_name='product.create', | ||||
|                     renderer='/products/crud.mako', | ||||
|                     permission='products.create') | ||||
|     config.add_view(ProductCrud, attr='read', route_name='product.read', | ||||
|                     renderer='/products/read.mako', | ||||
|                     permission='products.read') | ||||
|     config.add_view(ProductCrud, attr='update', route_name='product.update', | ||||
|                     renderer='/products/crud.mako', | ||||
|                     permission='products.update') | ||||
|     config.add_view(ProductCrud, attr='delete', route_name='product.delete', | ||||
|                     permission='products.delete') | ||||
| 
 | ||||
|     config.add_route('products.search', '/products/search') | ||||
|     config.add_view(products_search, route_name='products.search', | ||||
|                     renderer='json', permission='products.list') | ||||
| 
 | ||||
|     ProductsView.defaults(config) | ||||
|     version_defaults(config, ProductVersionView, 'product') | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar