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. | ||||
| # | ||||
|  | @ -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, | ||||
|                 sa.and_( | ||||
|                     ProductCost.product_uuid == Product.uuid, | ||||
|                     ProductCost.preference == 1, | ||||
|                     )) | ||||
|             q = q.outerjoin(Vendor) | ||||
|             return q | ||||
|             return q.outerjoin(model.ProductCost, | ||||
|                                sa.and_( | ||||
|                                    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, | ||||
|                 self.VendorAny.uuid == self.ProductCostAny.vendor_uuid) | ||||
|             return q | ||||
|             return q.outerjoin(self.ProductCostAny, | ||||
|                                self.ProductCostAny.product_uuid == model.Product.uuid)\ | ||||
|                     .outerjoin(self.VendorAny, | ||||
|                                self.VendorAny.uuid == self.ProductCostAny.vendor_uuid) | ||||
| 
 | ||||
|         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