tailbone/rattail/pyramid/views/products.py
Lance Edgar cf10fe19e8 extensive commit (see note)
The following changes are included:

- Added support for GPC data type.

- Added eager import of ``rattail.sil`` in ``before_render`` hook.

- Removed ``rattail.pyramid.util`` module.

- Added initial batch support: views, templates, creation from Product grid.

- Added batch params template for ``PrintLabels`` provider.

- Added support for ``rattail.LabelProfile`` class.

- Improved Product grid to include filter/sort on Vendor.
2012-08-29 11:37:17 -07:00

295 lines
10 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2012 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``rattail.pyramid.views.products`` -- Product Views
"""
import threading
from sqlalchemy import and_
from sqlalchemy.orm import joinedload
from webhelpers.html.tags import link_to
from pyramid.httpexceptions import HTTPFound
from pyramid.renderers import render_to_response
import edbob
from edbob.pyramid import Session
from edbob.pyramid.progress import SessionProgress
from edbob.pyramid.views import SearchableAlchemyGridView, CrudView
import rattail
import rattail.labels
from rattail import sil
from rattail import batches
from rattail.exceptions import LabelPrintingError
from rattail.pyramid.forms import GPCFieldRenderer, PriceFieldRenderer
class ProductsGrid(SearchableAlchemyGridView):
mapped_class = rattail.Product
config_prefix = 'products'
sort = 'description'
clickable = True
def join_map(self):
def join_vendor(q):
q = q.outerjoin(
rattail.ProductCost,
and_(
rattail.ProductCost.product_uuid == rattail.Product.uuid,
rattail.ProductCost.preference == 1,
))
q = q.outerjoin(rattail.Vendor)
return q
return {
'brand':
lambda q: q.outerjoin(rattail.Brand),
'department':
lambda q: q.outerjoin(rattail.Department,
rattail.Department.uuid == rattail.Product.department_uuid),
'subdepartment':
lambda q: q.outerjoin(rattail.Subdepartment,
rattail.Subdepartment.uuid == rattail.Product.subdepartment_uuid),
'regular_price':
lambda q: q.outerjoin(rattail.ProductPrice,
rattail.ProductPrice.uuid == rattail.Product.regular_price_uuid),
'current_price':
lambda q: q.outerjoin(rattail.ProductPrice,
rattail.ProductPrice.uuid == rattail.Product.current_price_uuid),
'vendor':
join_vendor,
}
def filter_map(self):
return self.make_filter_map(
exact=['upc'],
ilike=['description', 'size'],
brand=self.filter_ilike(rattail.Brand.name),
department=self.filter_ilike(rattail.Department.name),
subdepartment=self.filter_ilike(rattail.Subdepartment.name),
vendor=self.filter_ilike(rattail.Vendor.name))
def filter_config(self):
return self.make_filter_config(
include_filter_upc=True,
filter_type_upc='eq',
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',
include_filter_vendor=True,
filter_type_vendor='lk')
def sort_map(self):
return self.make_sort_map(
'upc', 'description', 'size',
brand=self.sorter(rattail.Brand.name),
department=self.sorter(rattail.Department.name),
subdepartment=self.sorter(rattail.Subdepartment.name),
regular_price=self.sorter(rattail.ProductPrice.price),
current_price=self.sorter(rattail.ProductPrice.price),
vendor=self.sorter(rattail.Vendor.name))
def query(self):
q = self.make_query()
q = q.options(joinedload(rattail.Product.brand))
q = q.options(joinedload(rattail.Product.department))
q = q.options(joinedload(rattail.Product.subdepartment))
q = q.options(joinedload(rattail.Product.regular_price))
q = q.options(joinedload(rattail.Product.current_price))
q = q.options(joinedload(rattail.Product.vendor))
return q
def grid(self):
g = self.make_grid()
g.upc.set(renderer=GPCFieldRenderer)
g.regular_price.set(renderer=PriceFieldRenderer)
g.current_price.set(renderer=PriceFieldRenderer)
g.configure(
include=[
g.upc.label("UPC"),
g.brand,
g.description,
g.size,
g.subdepartment,
g.vendor,
g.regular_price.label("Reg. Price"),
g.current_price.label("Cur. Price"),
],
readonly=True)
g.click_route_name = 'product.read'
q = Session.query(rattail.LabelProfile)
if q.count():
def labels(row):
return link_to("Print", '#', class_='print-label')
g.add_column('labels', "Labels", labels)
return g
def render_kwargs(self):
q = Session.query(rattail.LabelProfile)
q = q.order_by(rattail.LabelProfile.ordinal)
return {'label_profiles': q.all()}
class ProductCrud(CrudView):
mapped_class = rattail.Product
home_route = 'products'
def fieldset(self, model):
fs = self.make_fieldset(model)
fs.upc.set(renderer=GPCFieldRenderer)
fs.regular_price.set(renderer=PriceFieldRenderer)
fs.current_price.set(renderer=PriceFieldRenderer)
fs.configure(
include=[
fs.upc.label("UPC"),
fs.brand,
fs.description,
fs.size,
fs.department,
fs.subdepartment,
fs.regular_price,
fs.current_price,
])
return fs
def print_labels(request):
profile = request.params.get('profile')
profile = Session.query(rattail.LabelProfile).get(profile) if profile else None
if not profile:
return {'error': "Label profile not found"}
product = request.params.get('product')
product = Session.query(rattail.Product).get(product) if product else None
if not product:
return {'error': "Product not found"}
quantity = request.params.get('quantity')
if not quantity.isdigit():
return {'error': "Quantity must be numeric"}
quantity = int(quantity)
printer = profile.get_printer()
if not printer:
return {'error': "Couldn't get printer from label profile"}
try:
printer.print_labels([(product, quantity)])
except LabelPrintingError, error:
return {'error': str(error)}
return {}
class CreateProductsBatch(ProductsGrid):
def make_batch(self, provider):
session = edbob.Session()
self._filter_config = self.filter_config()
self._sort_config = self.sort_config()
products = self.make_query(session)
progress = SessionProgress(self.request.session, 'products.batch')
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['success_url'] = self.request.route_url('batch', 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(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)
thread = threading.Thread(target=self.make_batch, args=(provider,))
thread.start()
kwargs = {
'key': 'products.batch',
'cancel_url': self.request.route_url('products'),
'cancel_msg': "Batch creation was cancelled.",
}
return render_to_response('/progress.mako', kwargs, request=self.request)
providers = [(x.name, x.description) for x in batches.iter_providers()]
return {'providers': providers}
def includeme(config):
config.add_route('products', '/products')
config.add_view(ProductsGrid, route_name='products',
renderer='/products/index.mako',
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_route('products.create_batch', '/products/batch')
config.add_view(CreateProductsBatch, route_name='products.create_batch',
renderer='/products/batch.mako',
permission='batches.create')
config.add_route('product.read', '/products/{uuid}')
config.add_view(ProductCrud, attr='read', route_name='product.read',
renderer='/products/read.mako',
permission='products.read')