Refactor products view to use master3

This commit is contained in:
Lance Edgar 2018-01-08 18:03:51 -06:00
parent d9a5b4a0f5
commit 3097f46aa1
4 changed files with 221 additions and 105 deletions

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2017 Lance Edgar # Copyright © 2010-2018 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -482,6 +482,8 @@ class Form(object):
self.set_renderer(key, self.render_currency) self.set_renderer(key, self.render_currency)
elif type_ == 'quantity': elif type_ == 'quantity':
self.set_renderer(key, self.render_quantity) self.set_renderer(key, self.render_quantity)
elif type_ == 'gpc':
self.set_renderer(key, self.render_gpc)
elif type_ == 'enum': elif type_ == 'enum':
self.set_renderer(key, self.render_enum) self.set_renderer(key, self.render_enum)
elif type_ == 'codeblock': elif type_ == 'codeblock':
@ -638,9 +640,12 @@ class Form(object):
value = self.obtain_value(record, field_name) value = self.obtain_value(record, field_name)
if value is None: if value is None:
return "" return ""
try:
if value < 0: if value < 0:
return "(${:0,.2f})".format(0 - value) return "(${:0,.2f})".format(0 - value)
return "${:0,.2f}".format(value) return "${:0,.2f}".format(value)
except ValueError:
return six.text_type(value)
def render_quantity(self, obj, field): def render_quantity(self, obj, field):
value = self.obtain_value(obj, field) value = self.obtain_value(obj, field)
@ -648,6 +653,12 @@ class Form(object):
return "" return ""
return pretty_quantity(value) 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): def render_enum(self, record, field_name):
value = self.obtain_value(record, field_name) value = self.obtain_value(record, field_name)
if value is None: if value is None:

View file

@ -1,6 +1,5 @@
## -*- coding: utf-8; -*- ## -*- coding: utf-8; -*-
<%inherit file="/master/view.mako" /> <%inherit file="/master/view.mako" />
<%namespace file="/forms/lib.mako" import="render_field_readonly" />
<%def name="extra_styles()"> <%def name="extra_styles()">
${parent.extra_styles()} ${parent.extra_styles()}
@ -63,15 +62,15 @@
############################## ##############################
<%def name="render_main_fields(form)"> <%def name="render_main_fields(form)">
${render_field_readonly(form.fieldset.upc)} ${form.render_field_readonly('upc')}
${render_field_readonly(form.fieldset.brand)} ${form.render_field_readonly('brand')}
${render_field_readonly(form.fieldset.description)} ${form.render_field_readonly('description')}
${render_field_readonly(form.fieldset.size)} ${form.render_field_readonly('size')}
${render_field_readonly(form.fieldset.unit_size)} ${form.render_field_readonly('unit_size')}
${render_field_readonly(form.fieldset.unit_of_measure)} ${form.render_field_readonly('unit_of_measure')}
${render_field_readonly(form.fieldset.unit)} ${form.render_field_readonly('unit')}
${render_field_readonly(form.fieldset.pack_size)} ${form.render_field_readonly('pack_size')}
${render_field_readonly(form.fieldset.case_size)} ${form.render_field_readonly('case_size')}
${self.extra_main_fields(form)} ${self.extra_main_fields(form)}
</%def> </%def>
@ -113,30 +112,30 @@
</%def> </%def>
<%def name="render_organization_fields(form)"> <%def name="render_organization_fields(form)">
${render_field_readonly(form.fieldset.department)} ${form.render_field_readonly('department')}
${render_field_readonly(form.fieldset.subdepartment)} ${form.render_field_readonly('subdepartment')}
${render_field_readonly(form.fieldset.category)} ${form.render_field_readonly('category')}
${render_field_readonly(form.fieldset.family)} ${form.render_field_readonly('family')}
${render_field_readonly(form.fieldset.report_code)} ${form.render_field_readonly('report_code')}
</%def> </%def>
<%def name="render_price_fields(form)"> <%def name="render_price_fields(form)">
${render_field_readonly(form.fieldset.price_required)} ${form.render_field_readonly('price_required')}
${render_field_readonly(form.fieldset.regular_price)} ${form.render_field_readonly('regular_price')}
${render_field_readonly(form.fieldset.current_price)} ${form.render_field_readonly('current_price')}
${render_field_readonly(form.fieldset.current_price_ends)} ${form.render_field_readonly('current_price_ends')}
${render_field_readonly(form.fieldset.deposit_link)} ${form.render_field_readonly('deposit_link')}
${render_field_readonly(form.fieldset.tax)} ${form.render_field_readonly('tax')}
</%def> </%def>
<%def name="render_flag_fields(form)"> <%def name="render_flag_fields(form)">
${render_field_readonly(form.fieldset.weighed)} ${form.render_field_readonly('weighed')}
${render_field_readonly(form.fieldset.discountable)} ${form.render_field_readonly('discountable')}
${render_field_readonly(form.fieldset.special_order)} ${form.render_field_readonly('special_order')}
${render_field_readonly(form.fieldset.organic)} ${form.render_field_readonly('organic')}
${render_field_readonly(form.fieldset.not_for_sale)} ${form.render_field_readonly('not_for_sale')}
${render_field_readonly(form.fieldset.discontinued)} ${form.render_field_readonly('discontinued')}
${render_field_readonly(form.fieldset.deleted)} ${form.render_field_readonly('deleted')}
</%def> </%def>
<%def name="movement_panel()"> <%def name="movement_panel()">
@ -149,7 +148,7 @@
</%def> </%def>
<%def name="render_movement_fields(form)"> <%def name="render_movement_fields(form)">
${render_field_readonly(form.fieldset.last_sold)} ${form.render_field_readonly('last_sold')}
</%def> </%def>
<%def name="lookup_codes_panel()"> <%def name="lookup_codes_panel()">
@ -210,7 +209,7 @@
<div class="panel"> <div class="panel">
<h2>Notes</h2> <h2>Notes</h2>
<div class="panel-body"> <div class="panel-body">
<div class="field">${form.fieldset.notes.render_readonly()}</div> <div class="field">${form.render_field_readonly('notes')}</div>
</div> </div>
</div> </div>
</%def> </%def>
@ -219,7 +218,7 @@
<div class="panel"> <div class="panel">
<h2>Ingredients</h2> <h2>Ingredients</h2>
<div class="panel-body"> <div class="panel-body">
${render_field_readonly(form.fieldset.ingredients)} ${form.render_field_readonly('ingredients')}
</div> </div>
</div> </div>
</%def> </%def>

View file

@ -381,6 +381,8 @@ class MasterView(View):
'instance_deletable': self.deletable_instance(instance), 'instance_deletable': self.deletable_instance(instance),
'form': form, 'form': form,
} }
if hasattr(form, 'make_deform_form'):
context['dform'] = form.make_deform_form()
if self.has_rows: if self.has_rows:
context['rows_grid'] = grid.render_complete(allow_save_defaults=False, context['rows_grid'] = grid.render_complete(allow_save_defaults=False,
tools=self.make_row_grid_tools(instance)) tools=self.make_row_grid_tools(instance))

View file

@ -41,16 +41,15 @@ from rattail.util import load_object, pretty_quantity
from rattail.batch import get_batch_handler from rattail.batch import get_batch_handler
import colander import colander
import formalchemy as fa
from deform import widget as dfwidget from deform import widget as dfwidget
from pyramid import httpexceptions from pyramid import httpexceptions
from pyramid.renderers import render_to_response
from webhelpers2.html import tags, HTML 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.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.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 # 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', '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 = { labels = {
'status_code': "Status", 'status_code': "Status",
} }
@ -312,71 +351,136 @@ class ProductsView(MasterView):
return price.product return price.product
raise httpexceptions.HTTPNotFound() raise httpexceptions.HTTPNotFound()
def _preconfigure_fieldset(self, fs): def configure_form(self, f):
fs.upc.set(label="UPC", renderer=forms.renderers.GPCFieldRenderer) super(ProductsView, self).configure_form(f)
fs.brand.set(renderer=forms.renderers.BrandFieldRenderer, options=[])
fs.department.set(renderer=forms.renderers.DepartmentFieldRenderer) # upc
fs.subdepartment.set(renderer=forms.renderers.SubdepartmentFieldRenderer) f.set_type('upc', 'gpc')
fs.category.set(renderer=forms.renderers.CategoryFieldRenderer) f.set_label('upc', "UPC")
fs.unit_size.set(renderer=forms.renderers.QuantityFieldRenderer)
fs.unit_of_measure.set(label="Unit of Measure", # department
renderer=forms.renderers.EnumFieldRenderer(self.enum.UNIT_OF_MEASURE)) f.set_renderer('department', self.render_department)
fs.unit.set(renderer=forms.renderers.ProductFieldRenderer, label="Unit Item")
fs.pack_size.set(renderer=forms.renderers.QuantityFieldRenderer) # subdepartment
fs.regular_price.set(renderer=forms.renderers.PriceFieldRenderer, readonly=True) f.set_renderer('subdepartment', self.render_subdepartment)
fs.current_price.set(renderer=forms.renderers.PriceFieldRenderer, readonly=True)
fs.last_sold.set(readonly=True) # category
fs.status_code.set(label="Status") f.set_renderer('category', self.render_category)
fs.notes.set(renderer=fa.TextAreaFieldRenderer, size=(80, 10))
fs.append(fa.Field('current_price_ends', type=fa.types.DateTime, readonly=True, # unit_size
value=lambda p: p.current_price.ends if p.current_price else None)) f.set_type('unit_size', 'quantity')
fs.append(fa.Field('inventory_on_hand', readonly=True, label="On Hand",
value=lambda p: p.inventory.on_hand if p.inventory else None)) # unit_of_measure
fs.append(fa.Field('inventory_on_order', readonly=True, label="On Order", f.set_enum('unit_of_measure', self.enum.UNIT_OF_MEASURE)
value=lambda p: p.inventory.on_order if p.inventory else None)) 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'): 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): def template_kwargs_view(self, **kwargs):
kwargs['image'] = False kwargs['image'] = False
@ -491,7 +595,7 @@ class ProductsView(MasterView):
colander.SchemaNode(colander.String(), name='notes', missing=colander.null), colander.SchemaNode(colander.String(), name='notes', missing=colander.null),
) )
form = forms2.Form(schema=schema, request=self.request, form = forms.Form(schema=schema, request=self.request,
cancel_url=self.get_index_url()) cancel_url=self.get_index_url())
form.set_type('notes', 'text') form.set_type('notes', 'text')
@ -504,7 +608,7 @@ class ProductsView(MasterView):
for node in schema: for node in schema:
node.param_name = node.name node.param_name = node.name
node.name = '{}_{}'.format(key, 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': if self.request.method == 'POST':
controls = self.request.POST.items() controls = self.request.POST.items()