diff --git a/tailbone/forms/widgets.py b/tailbone/forms/widgets.py index d8976337..ad9d9c31 100644 --- a/tailbone/forms/widgets.py +++ b/tailbone/forms/widgets.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2019 Lance Edgar +# Copyright © 2010-2021 Lance Edgar # # This file is part of Rattail. # @@ -36,6 +36,7 @@ import colander from deform import widget as dfwidget from webhelpers2.html import tags, HTML +from tailbone.db import Session from tailbone.forms.types import ProductQuantity @@ -278,3 +279,30 @@ class JQueryAutocompleteWidget(dfwidget.AutocompleteInputWidget): tmpl_values = self.get_template_values(field, cstruct, kw) template = readonly and self.readonly_template or self.template return field.renderer(template, **tmpl_values) + + +class DepartmentWidget(dfwidget.SelectWidget): + """ + Custom select widget for a Department reference field. + + Constructor accepts the normal ``values`` kwarg but if not + provided then the widget will fetch department list from Rattail + DB. + + Constructor also accepts ``required`` kwarg, which defaults to + true unless specified. + """ + + def __init__(self, request, **kwargs): + + if 'values' not in kwargs: + model = request.rattail_config.get_model() + departments = Session.query(model.Department)\ + .order_by(model.Department.number) + values = [(dept.uuid, six.text_type(dept)) + for dept in departments] + if not kwargs.pop('required', True): + values.insert(0, ('', "(none)")) + kwargs['values'] = values + + super(DepartmentWidget, self).__init__(**kwargs) diff --git a/tailbone/views/master.py b/tailbone/views/master.py index 1eb7686a..f288ec34 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -739,9 +739,11 @@ class MasterView(View): if obj.emails: return obj.emails[0].address - def render_product_key_value(self, obj): + def render_product_key_value(self, obj, field=None): """ Render the "canonical" product key value for the given object. + + nb. the ``field`` kwarg is ignored if present """ product_key = self.rattail_config.product_key() if product_key == 'upc': diff --git a/tailbone/views/products.py b/tailbone/views/products.py index b2cb835e..351dd832 100644 --- a/tailbone/views/products.py +++ b/tailbone/views/products.py @@ -1885,6 +1885,165 @@ class ProductView(MasterView): permission='{}.versions'.format(permission_prefix)) +class PendingProductView(MasterView): + """ + Master view for the Pending Product class. + """ + model_class = model.PendingProduct + route_prefix = 'pending_products' + url_prefix = '/products/pending' + + labels = { + 'regular_price_amount': "Regular Price", + 'status_code': "Status", + 'user': "Created by", + } + + grid_columns = [ + '_product_key_', + 'department_name', + 'brand_name', + 'description', + 'size', + 'created', + 'user', + 'status_code', + ] + + form_fields = [ + '_product_key_', + 'department_name', + 'department', + 'brand_name', + 'brand', + 'description', + 'size', + 'case_size', + 'regular_price_amount', + 'special_order', + 'notes', + 'created', + 'user', + 'status_code', + ] + + def configure_grid(self, g): + super(PendingProductView, self).configure_grid(g) + + # product key + if '_product_key_' in g.columns: + key = self.rattail_config.product_key() + g.replace('_product_key_', key) + g.set_label(key, self.rattail_config.product_key_title(key)) + g.set_link(key) + + g.set_enum('status_code', self.enum.PENDING_PRODUCT_STATUS) + + g.set_sort_defaults('created', 'desc') + + g.set_link('description') + + def configure_form(self, f): + super(PendingProductView, self).configure_form(f) + model = self.model + pending = f.model_instance + + # product key + if '_product_key_' in f: + key = self.rattail_config.product_key() + f.replace('_product_key_', key) + f.set_label(key, self.rattail_config.product_key_title(key)) + f.set_renderer(key, self.render_product_key_value) + + # department + if self.creating or self.editing: + if 'department' in f: + f.remove('department_name') + f.replace('department', 'department_uuid') + f.set_widget('department_uuid', forms.widgets.DepartmentWidget(self.request, required=False)) + f.set_label('department_uuid', "Department") + else: + f.set_renderer('department', self.render_department) + if pending.department: + f.remove('department_name') + + # brand + if self.creating or self.editing: + f.remove('brand_name') + f.replace('brand', 'brand_uuid') + f.set_label('brand_uuid', "Brand") + + 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(pending.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)) + else: + f.set_renderer('brand', self.render_brand) + if pending.brand: + f.remove('brand_name') + + # description + f.set_required('description') + + # case_size + f.set_type('case_size', 'quantity') + + # regular_price_amount + f.set_type('regular_price_amount', 'currency') + + # notes + f.set_type('notes', 'text') + + # created + if self.creating: + f.remove('created') + else: + f.set_readonly('created') + + # user + if self.creating: + f.remove('user') + else: + f.set_readonly('user') + f.set_renderer('user', self.render_user) + + # status_code + if self.creating: + f.remove('status_code') + else: + # f.set_readonly('status_code') + f.set_enum('status_code', self.enum.PENDING_PRODUCT_STATUS) + + def objectify(self, form, data=None): + if data is None: + data = form.validated + + pending = super(PendingProductView, self).objectify(form, data) + + if not pending.user: + pending.user = self.request.user + + self.Session.add(pending) + self.Session.flush() + self.Session.refresh(pending) + + if pending.department: + pending.department_name = pending.department.name + + if pending.brand: + pending.brand_name = pending.brand.name + + return pending + + def print_labels(request): profile = request.params.get('profile') profile = Session.query(model.LabelProfile).get(profile) if profile else None @@ -1920,3 +2079,4 @@ def includeme(config): renderer='json', permission='products.print_labels') ProductView.defaults(config) + PendingProductView.defaults(config)