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)} @@ -113,30 +112,30 @@ <%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 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 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 name="movement_panel()"> @@ -149,7 +148,7 @@ <%def name="render_movement_fields(form)"> - ${render_field_readonly(form.fieldset.last_sold)} + ${form.render_field_readonly('last_sold')} <%def name="lookup_codes_panel()"> @@ -210,7 +209,7 @@

Notes

-
${form.fieldset.notes.render_readonly()}
+
${form.render_field_readonly('notes')}
@@ -219,7 +218,7 @@

Ingredients

- ${render_field_readonly(form.fieldset.ingredients)} + ${form.render_field_readonly('ingredients')}
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()