diff --git a/tailbone/exceptions.py b/tailbone/exceptions.py new file mode 100644 index 00000000..beea1366 --- /dev/null +++ b/tailbone/exceptions.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2020 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 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 General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# Rattail. If not, see . +# +################################################################################ +""" +Tailbone Exceptions +""" + +from __future__ import unicode_literals, absolute_import + +import six + +from rattail.exceptions import RattailError + + +class TailboneError(RattailError): + """ + Base class for all Tailbone exceptions. + """ + + +@six.python_2_unicode_compatible +class TailboneJSONFieldError(TailboneError): + """ + Error raised when JSON serialization of a form field results in an error. + This is just a simple wrapper, to make the error message more helpful for + the developer. + """ + + def __init__(self, field, error): + self.field = field + self.error = error + + def __str__(self): + return ("Failed to serialize field '{}' as JSON! " + "Original error was: {}".format(self.field, self.error)) diff --git a/tailbone/forms/core.py b/tailbone/forms/core.py index a923346c..5cc41771 100644 --- a/tailbone/forms/core.py +++ b/tailbone/forms/core.py @@ -51,6 +51,7 @@ from webhelpers2.html import tags, HTML from tailbone.util import raw_datetime from . import types from .widgets import ReadonlyWidget, PlainDateWidget, JQueryDateWidget, JQueryTimeWidget +from tailbone.exceptions import TailboneJSONFieldError log = logging.getLogger(__name__) @@ -786,7 +787,10 @@ class Form(object): if field.cstruct is colander.null: return 'null' - return json.dumps(field.cstruct) + try: + return json.dumps(field.cstruct) + except Exception as error: + raise TailboneJSONFieldError(field.name, error) def messages_json(self, messages): dump = json.dumps(messages) diff --git a/tailbone/views/products.py b/tailbone/views/products.py index e86eaaa0..bfe01fc1 100644 --- a/tailbone/views/products.py +++ b/tailbone/views/products.py @@ -786,11 +786,17 @@ class ProductsView(MasterView): if not require_report_code: report_code_values.insert(0, ('', "(none)")) f.set_widget('report_code_uuid', dfwidget.SelectWidget(values=report_code_values)) - f.set_label('report_code_uuid', "Report_Code") + f.set_label('report_code_uuid', "Report Code") else: f.set_readonly('report_code') # f.set_renderer('report_code', self.render_report_code) + # regular_price_amount + if self.editing: + f.set_node('regular_price_amount', colander.Decimal()) + f.set_default('regular_price_amount', product.regular_price.price if product.regular_price else None) + f.set_label('regular_price_amount', "Regular Price") + # deposit_link if self.creating or self.editing: if 'deposit_link' in f.fields: @@ -803,7 +809,7 @@ class ProductsView(MasterView): if not require_deposit_link: deposit_link_values.insert(0, ('', "(none)")) f.set_widget('deposit_link_uuid', dfwidget.SelectWidget(values=deposit_link_values)) - f.set_label('deposit_link_uuid', "Deposit_Link") + f.set_label('deposit_link_uuid', "Deposit Link") else: f.set_readonly('deposit_link') # f.set_renderer('deposit_link', self.render_deposit_link) @@ -831,18 +837,24 @@ class ProductsView(MasterView): f.set_readonly('tax3') # brand - if self.creating: - f.replace('brand', 'brand_uuid') - brand_display = "" - if self.request.method == 'POST': - if self.request.POST.get('brand_uuid'): - brand = self.Session.query(model.Brand).get(self.request.POST['brand_uuid']) - if brand: - brand_display = six.text_type(brand) - brands_url = self.request.route_url('brands.autocomplete') - f.set_widget('brand_uuid', forms.widgets.JQueryAutocompleteWidget( - field_display=brand_display, service_url=brands_url)) - f.set_label('brand_uuid', "Brand") + if self.creating or self.editing: + if 'brand' in f.fields: + f.replace('brand', 'brand_uuid') + f.set_node('brand_uuid', colander.String(), missing=colander.null) + brand_display = "" + if self.request.method == 'POST': + if self.request.POST.get('brand_uuid'): + brand = self.Session.query(model.Brand).get(self.request.POST['brand_uuid']) + if brand: + brand_display = six.text_type(brand) + elif self.editing: + brand_display = six.text_type(product.brand or '') + brands_url = self.request.route_url('brands.autocomplete') + f.set_widget('brand_uuid', forms.widgets.JQueryAutocompleteWidget( + field_display=brand_display, service_url=brands_url)) + f.set_label('brand_uuid', "Brand") + else: + f.set_readonly('brand') # status_code f.set_label('status_code', "Status") @@ -856,6 +868,17 @@ class ProductsView(MasterView): if not self.request.has_perm('products.view_deleted'): f.remove('deleted') + def objectify(self, form, data=None): + if data is None: + data = form.validated + product = super(ProductsView, self).objectify(form, data=data) + + # regular_price_amount + if (self.creating or self.editing) and 'regular_price_amount' in form.fields: + api.set_regular_price(product, data['regular_price_amount']) + + return product + def render_department(self, product, field): department = product.department if not department: