diff --git a/tailbone/forms2/core.py b/tailbone/forms2/core.py
index 9dad0652..be67c585 100644
--- a/tailbone/forms2/core.py
+++ b/tailbone/forms2/core.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2017 Lance Edgar
+# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
@@ -482,6 +482,8 @@ class Form(object):
self.set_renderer(key, self.render_currency)
elif type_ == 'quantity':
self.set_renderer(key, self.render_quantity)
+ elif type_ == 'gpc':
+ self.set_renderer(key, self.render_gpc)
elif type_ == 'enum':
self.set_renderer(key, self.render_enum)
elif type_ == 'codeblock':
@@ -638,9 +640,12 @@ class Form(object):
value = self.obtain_value(record, field_name)
if value is None:
return ""
- if value < 0:
- return "(${:0,.2f})".format(0 - value)
- return "${:0,.2f}".format(value)
+ try:
+ if value < 0:
+ return "(${:0,.2f})".format(0 - value)
+ return "${:0,.2f}".format(value)
+ except ValueError:
+ return six.text_type(value)
def render_quantity(self, obj, field):
value = self.obtain_value(obj, field)
@@ -648,6 +653,12 @@ class Form(object):
return ""
return pretty_quantity(value)
+ def render_gpc(self, obj, field):
+ value = self.obtain_value(obj, field)
+ if value is None:
+ return ""
+ return value.pretty()
+
def render_enum(self, record, field_name):
value = self.obtain_value(record, field_name)
if value is None:
diff --git a/tailbone/templates/products/view.mako b/tailbone/templates/products/view.mako
index e7026713..10db733e 100644
--- a/tailbone/templates/products/view.mako
+++ b/tailbone/templates/products/view.mako
@@ -1,6 +1,5 @@
## -*- coding: utf-8; -*-
<%inherit file="/master/view.mako" />
-<%namespace file="/forms/lib.mako" import="render_field_readonly" />
<%def name="extra_styles()">
${parent.extra_styles()}
@@ -63,15 +62,15 @@
##############################
<%def name="render_main_fields(form)">
- ${render_field_readonly(form.fieldset.upc)}
- ${render_field_readonly(form.fieldset.brand)}
- ${render_field_readonly(form.fieldset.description)}
- ${render_field_readonly(form.fieldset.size)}
- ${render_field_readonly(form.fieldset.unit_size)}
- ${render_field_readonly(form.fieldset.unit_of_measure)}
- ${render_field_readonly(form.fieldset.unit)}
- ${render_field_readonly(form.fieldset.pack_size)}
- ${render_field_readonly(form.fieldset.case_size)}
+ ${form.render_field_readonly('upc')}
+ ${form.render_field_readonly('brand')}
+ ${form.render_field_readonly('description')}
+ ${form.render_field_readonly('size')}
+ ${form.render_field_readonly('unit_size')}
+ ${form.render_field_readonly('unit_of_measure')}
+ ${form.render_field_readonly('unit')}
+ ${form.render_field_readonly('pack_size')}
+ ${form.render_field_readonly('case_size')}
${self.extra_main_fields(form)}
%def>
@@ -113,30 +112,30 @@
%def>
<%def name="render_organization_fields(form)">
- ${render_field_readonly(form.fieldset.department)}
- ${render_field_readonly(form.fieldset.subdepartment)}
- ${render_field_readonly(form.fieldset.category)}
- ${render_field_readonly(form.fieldset.family)}
- ${render_field_readonly(form.fieldset.report_code)}
+ ${form.render_field_readonly('department')}
+ ${form.render_field_readonly('subdepartment')}
+ ${form.render_field_readonly('category')}
+ ${form.render_field_readonly('family')}
+ ${form.render_field_readonly('report_code')}
%def>
<%def name="render_price_fields(form)">
- ${render_field_readonly(form.fieldset.price_required)}
- ${render_field_readonly(form.fieldset.regular_price)}
- ${render_field_readonly(form.fieldset.current_price)}
- ${render_field_readonly(form.fieldset.current_price_ends)}
- ${render_field_readonly(form.fieldset.deposit_link)}
- ${render_field_readonly(form.fieldset.tax)}
+ ${form.render_field_readonly('price_required')}
+ ${form.render_field_readonly('regular_price')}
+ ${form.render_field_readonly('current_price')}
+ ${form.render_field_readonly('current_price_ends')}
+ ${form.render_field_readonly('deposit_link')}
+ ${form.render_field_readonly('tax')}
%def>
<%def name="render_flag_fields(form)">
- ${render_field_readonly(form.fieldset.weighed)}
- ${render_field_readonly(form.fieldset.discountable)}
- ${render_field_readonly(form.fieldset.special_order)}
- ${render_field_readonly(form.fieldset.organic)}
- ${render_field_readonly(form.fieldset.not_for_sale)}
- ${render_field_readonly(form.fieldset.discontinued)}
- ${render_field_readonly(form.fieldset.deleted)}
+ ${form.render_field_readonly('weighed')}
+ ${form.render_field_readonly('discountable')}
+ ${form.render_field_readonly('special_order')}
+ ${form.render_field_readonly('organic')}
+ ${form.render_field_readonly('not_for_sale')}
+ ${form.render_field_readonly('discontinued')}
+ ${form.render_field_readonly('deleted')}
%def>
<%def name="movement_panel()">
@@ -149,7 +148,7 @@
%def>
<%def name="render_movement_fields(form)">
- ${render_field_readonly(form.fieldset.last_sold)}
+ ${form.render_field_readonly('last_sold')}
%def>
<%def name="lookup_codes_panel()">
@@ -210,7 +209,7 @@
Notes
-
${form.fieldset.notes.render_readonly()}
+
${form.render_field_readonly('notes')}
%def>
@@ -219,7 +218,7 @@
Ingredients
- ${render_field_readonly(form.fieldset.ingredients)}
+ ${form.render_field_readonly('ingredients')}
%def>
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index c5573397..3f4a88c4 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -381,6 +381,8 @@ class MasterView(View):
'instance_deletable': self.deletable_instance(instance),
'form': form,
}
+ if hasattr(form, 'make_deform_form'):
+ context['dform'] = form.make_deform_form()
if self.has_rows:
context['rows_grid'] = grid.render_complete(allow_save_defaults=False,
tools=self.make_row_grid_tools(instance))
diff --git a/tailbone/views/products.py b/tailbone/views/products.py
index f60abc75..461cb956 100644
--- a/tailbone/views/products.py
+++ b/tailbone/views/products.py
@@ -41,16 +41,15 @@ from rattail.util import load_object, pretty_quantity
from rattail.batch import get_batch_handler
import colander
-import formalchemy as fa
from deform import widget as dfwidget
from pyramid import httpexceptions
-from pyramid.renderers import render_to_response
from webhelpers2.html import tags, HTML
-from tailbone import forms, forms2, grids
+from tailbone import forms2 as forms, grids
from tailbone.db import Session
-from tailbone.views import MasterView2 as MasterView, AutocompleteView
+from tailbone.views import MasterView3 as MasterView, AutocompleteView
from tailbone.progress import SessionProgress
+from tailbone.util import raw_datetime
# TODO: For a moment I thought this was going to be necessary, but now I think
@@ -91,6 +90,46 @@ class ProductsView(MasterView):
'current_price',
]
+ form_fields = [
+ 'upc',
+ 'brand',
+ 'description',
+ 'unit_size',
+ 'unit_of_measure',
+ 'size',
+ 'unit',
+ 'pack_size',
+ 'case_size',
+ 'weighed',
+ 'department',
+ 'subdepartment',
+ 'category',
+ 'family',
+ 'report_code',
+ 'regular_price',
+ 'current_price',
+ 'current_price_ends',
+ 'deposit_link',
+ 'tax',
+ 'organic',
+ 'kosher',
+ 'vegan',
+ 'vegetarian',
+ 'gluten_free',
+ 'sugar_free',
+ 'discountable',
+ 'special_order',
+ 'not_for_sale',
+ 'ingredients',
+ 'notes',
+ 'status_code',
+ 'discontinued',
+ 'deleted',
+ 'last_sold',
+ 'inventory_on_hand',
+ 'inventory_on_order',
+ ]
+
labels = {
'status_code': "Status",
}
@@ -312,71 +351,136 @@ class ProductsView(MasterView):
return price.product
raise httpexceptions.HTTPNotFound()
- def _preconfigure_fieldset(self, fs):
- fs.upc.set(label="UPC", renderer=forms.renderers.GPCFieldRenderer)
- fs.brand.set(renderer=forms.renderers.BrandFieldRenderer, options=[])
- fs.department.set(renderer=forms.renderers.DepartmentFieldRenderer)
- fs.subdepartment.set(renderer=forms.renderers.SubdepartmentFieldRenderer)
- fs.category.set(renderer=forms.renderers.CategoryFieldRenderer)
- fs.unit_size.set(renderer=forms.renderers.QuantityFieldRenderer)
- fs.unit_of_measure.set(label="Unit of Measure",
- renderer=forms.renderers.EnumFieldRenderer(self.enum.UNIT_OF_MEASURE))
- fs.unit.set(renderer=forms.renderers.ProductFieldRenderer, label="Unit Item")
- fs.pack_size.set(renderer=forms.renderers.QuantityFieldRenderer)
- fs.regular_price.set(renderer=forms.renderers.PriceFieldRenderer, readonly=True)
- fs.current_price.set(renderer=forms.renderers.PriceFieldRenderer, readonly=True)
- fs.last_sold.set(readonly=True)
- fs.status_code.set(label="Status")
- fs.notes.set(renderer=fa.TextAreaFieldRenderer, size=(80, 10))
- fs.append(fa.Field('current_price_ends', type=fa.types.DateTime, readonly=True,
- value=lambda p: p.current_price.ends if p.current_price else None))
- fs.append(fa.Field('inventory_on_hand', readonly=True, label="On Hand",
- value=lambda p: p.inventory.on_hand if p.inventory else None))
- fs.append(fa.Field('inventory_on_order', readonly=True, label="On Order",
- value=lambda p: p.inventory.on_order if p.inventory else None))
+ def configure_form(self, f):
+ super(ProductsView, self).configure_form(f)
+
+ # upc
+ f.set_type('upc', 'gpc')
+ f.set_label('upc', "UPC")
+
+ # department
+ f.set_renderer('department', self.render_department)
+
+ # subdepartment
+ f.set_renderer('subdepartment', self.render_subdepartment)
+
+ # category
+ f.set_renderer('category', self.render_category)
+
+ # unit_size
+ f.set_type('unit_size', 'quantity')
+
+ # unit_of_measure
+ f.set_enum('unit_of_measure', self.enum.UNIT_OF_MEASURE)
+ f.set_label('unit_of_measure', "Unit of Measure")
+
+ # unit
+ f.set_renderer('unit', self.render_unit)
+ f.set_label('unit', "Unit Item")
+
+ # pack_size
+ f.set_type('pack_size', 'quantity')
+
+ # regular_price
+ f.set_readonly('regular_price')
+ f.set_renderer('regular_price', self.render_price)
+
+ # current_price
+ f.set_readonly('current_price')
+ f.set_renderer('current_price', self.render_price)
+
+ # last_sold
+ f.set_readonly('last_sold')
+
+ # status_code
+ f.set_label('status_code', "Status")
+
+ # notes
+ f.set_widget('notes', dfwidget.TextAreaWidget(cols=80, rows=10))
+
+ # current_price_ends
+ f.set_readonly('current_price_ends')
+ f.set_renderer('current_price_ends', self.render_current_price_ends)
+
+ # inventory_on_hand
+ f.set_readonly('inventory_on_hand')
+ f.set_renderer('inventory_on_hand', self.render_inventory_on_hand)
+ f.set_label('inventory_on_hand', "On Hand")
+
+ # inventory_on_order
+ f.set_readonly('inventory_on_order')
+ f.set_renderer('inventory_on_order', self.render_inventory_on_order)
+ f.set_label('inventory_on_order', "On Order")
- def configure_fieldset(self, fs):
- fs.configure(
- include=[
- fs.upc,
- fs.brand,
- fs.description,
- fs.unit_size,
- fs.unit_of_measure,
- fs.size,
- fs.unit,
- fs.pack_size,
- fs.case_size,
- fs.weighed,
- fs.department,
- fs.subdepartment,
- fs.category,
- fs.family,
- fs.report_code,
- fs.price_required,
- fs.regular_price,
- fs.current_price,
- fs.current_price_ends,
- fs.deposit_link,
- fs.tax,
- fs.organic,
- fs.kosher,
- fs.vegan,
- fs.vegetarian,
- fs.gluten_free,
- fs.sugar_free,
- fs.discountable,
- fs.special_order,
- fs.not_for_sale,
- fs.ingredients,
- fs.notes,
- fs.status_code,
- fs.discontinued,
- fs.deleted,
- fs.last_sold,
- ])
if not self.request.has_perm('products.view_deleted'):
- del fs.deleted
+ f.remove('deleted')
+
+ def render_department(self, product, field):
+ department = product.department
+ if not department:
+ return ""
+ if department.number:
+ text = '({}) {}'.format(department.number, department.name)
+ else:
+ text = department.name
+ url = self.request.route_url('departments.view', uuid=department.uuid)
+ return tags.link_to(text, url)
+
+ def render_subdepartment(self, product, field):
+ subdepartment = product.subdepartment
+ if not subdepartment:
+ return ""
+ if subdepartment.number:
+ text = '({}) {}'.format(subdepartment.number, subdepartment.name)
+ else:
+ text = subdepartment.name
+ url = self.request.route_url('subdepartments.view', uuid=subdepartment.uuid)
+ return tags.link_to(text, url)
+
+ def render_category(self, product, field):
+ category = product.category
+ if not category:
+ return ""
+ if category.code:
+ text = '({}) {}'.format(category.code, category.name)
+ elif category.number:
+ text = '({}) {}'.format(category.number, category.name)
+ else:
+ text = category.name
+ url = self.request.route_url('categories.view', uuid=category.uuid)
+ return tags.link_to(text, url)
+
+ def render_unit(self, product, field):
+ product = product.unit
+ if not product:
+ return ""
+ text = product.full_description
+ url = self.request.route_url('products.view', uuid=product.uuid)
+ return tags.link_to(text, url)
+
+ def render_current_price_ends(self, product, field):
+ if not product.current_price:
+ return ""
+ value = product.current_price.ends
+ if not value:
+ return ""
+ return raw_datetime(self.request.rattail_config, value)
+
+ def render_inventory_on_hand(self, product, field):
+ if not product.inventory:
+ return ""
+ value = product.inventory.on_hand
+ if not value:
+ return ""
+ return pretty_quantity(value)
+
+ def render_inventory_on_order(self, product, field):
+ if not product.inventory:
+ return ""
+ value = product.inventory.on_order
+ if not value:
+ return ""
+ return pretty_quantity(value)
def template_kwargs_view(self, **kwargs):
kwargs['image'] = False
@@ -491,8 +595,8 @@ class ProductsView(MasterView):
colander.SchemaNode(colander.String(), name='notes', missing=colander.null),
)
- form = forms2.Form(schema=schema, request=self.request,
- cancel_url=self.get_index_url())
+ form = forms.Form(schema=schema, request=self.request,
+ cancel_url=self.get_index_url())
form.set_type('notes', 'text')
params_forms = {}
@@ -504,7 +608,7 @@ class ProductsView(MasterView):
for node in schema:
node.param_name = node.name
node.name = '{}_{}'.format(key, node.name)
- params_forms[key] = forms2.Form(schema=schema, request=self.request)
+ params_forms[key] = forms.Form(schema=schema, request=self.request)
if self.request.method == 'POST':
controls = self.request.POST.items()