diff --git a/tailbone/forms/widgets.py b/tailbone/forms/widgets.py index 28b24678..f672ab47 100644 --- a/tailbone/forms/widgets.py +++ b/tailbone/forms/widgets.py @@ -472,3 +472,92 @@ class DepartmentWidget(dfwidget.SelectWidget): kwargs['values'] = values super(DepartmentWidget, self).__init__(**kwargs) + + +def make_vendor_widget(request, **kwargs): + """ + Make a vendor widget; will be either autocomplete or dropdown + depending on config. + """ + # use autocomplete widget by default + factory = VendorAutocompleteWidget + + # caller may request dropdown widget + if kwargs.pop('dropdown', False): + factory = VendorDropdownWidget + + else: # or, config may say to use dropdown + app = request.rattail_config.get_app() + vendor_handler = app.get_vendor_handler() + if vendor_handler.choice_uses_dropdown(): + factory = VendorDropdownWidget + + # instantiate whichever + return factory(request, **kwargs) + + +class VendorAutocompleteWidget(JQueryAutocompleteWidget): + """ + Autocomplete widget for a Vendor reference field. + """ + + def __init__(self, request, *args, **kwargs): + super(VendorAutocompleteWidget, self).__init__(*args, **kwargs) + self.request = request + model = self.request.rattail_config.get_model() + + # must figure out URL providing autocomplete service + if 'service_url' not in kwargs: + + # caller can just pass 'url' instead of 'service_url' + if 'url' in kwargs: + self.service_url = kwargs['url'] + + else: # use default url + self.service_url = self.request.route_url('vendors.autocomplete') + + # # TODO + # if 'input_callback' not in kwargs: + # if 'input_handler' in kwargs: + # self.input_callback = input_handler + + def serialize(self, field, cstruct, **kw): + + # fetch vendor to provide button label, if we have a value + if cstruct: + model = self.request.rattail_config.get_model() + vendor = Session.get(model.Vendor, cstruct) + if vendor: + self.field_display = str(vendor) + + return super(VendorAutocompleteWidget, self).serialize( + field, cstruct, **kw) + + +class VendorDropdownWidget(dfwidget.SelectWidget): + """ + Dropdown widget for a Vendor reference field. + """ + + def __init__(self, request, *args, **kwargs): + super(VendorDropdownWidget, self).__init__(*args, **kwargs) + self.request = request + + # must figure out dropdown values, if they weren't given + if 'values' not in kwargs: + + # use what caller gave us, if they did + if 'vendors' in kwargs: + vendors = kwargs['vendors'] + if callable(vendors): + vendors = vendors() + + else: # default vendor list + model = self.request.rattail_config.get_model() + vendors = Session.query(model.Vendor)\ + .order_by(model.Vendor.name)\ + .all() + + # convert vendor list to option values + self.values = [(c.uuid, c.name) + for c in vendors] diff --git a/tailbone/menus.py b/tailbone/menus.py index 1732c084..98006c00 100644 --- a/tailbone/menus.py +++ b/tailbone/menus.py @@ -464,6 +464,12 @@ class MenuHandler(GenericHandler): 'route': 'vendorcatalogs', 'perm': 'vendorcatalogs.list', }, + {'type': 'sep'}, + { + 'title': "Sample Files", + 'route': 'vendorsamplefiles', + 'perm': 'vendorsamplefiles.list', + }, ], } diff --git a/tailbone/views/vendors/__init__.py b/tailbone/views/vendors/__init__.py index 7d35780e..210df39e 100644 --- a/tailbone/views/vendors/__init__.py +++ b/tailbone/views/vendors/__init__.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2022 Lance Edgar +# Copyright © 2010-2023 Lance Edgar # # This file is part of Rattail. # @@ -24,8 +24,6 @@ Views pertaining to vendors """ -from __future__ import unicode_literals, absolute_import - from .core import VendorView @@ -36,3 +34,4 @@ def defaults(config, **kwargs): def includeme(config): config.include('tailbone.views.vendors.core') + config.include('tailbone.views.vendors.samplefiles') diff --git a/tailbone/views/vendors/samplefiles.py b/tailbone/views/vendors/samplefiles.py new file mode 100644 index 00000000..41982259 --- /dev/null +++ b/tailbone/views/vendors/samplefiles.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2023 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 . +# +################################################################################ +""" +Model View for Vendor Sample Files +""" + +from rattail.db.model import VendorSampleFile + +from webhelpers2.html import tags + +from tailbone import forms +from tailbone.views import MasterView + + +class VendorSampleFileView(MasterView): + """ + Master model view for Vendor Sample Files + """ + model_class = VendorSampleFile + route_prefix = 'vendorsamplefiles' + url_prefix = '/vendors/sample-files' + downloadable = True + has_versions = True + + grid_columns = [ + 'vendor', + 'file_type', + 'effective_date', + 'filename', + 'created_by', + ] + + form_fields = [ + 'vendor', + 'file_type', + 'filename', + 'effective_date', + 'notes', + 'created_by', + ] + + def configure_grid(self, g): + super(VendorSampleFileView, self).configure_grid(g) + + # vendor + g.set_link('vendor') + + # filename + g.set_link('filename') + + # effective_date + g.set_sort_defaults('effective_date', 'desc') + + def configure_form(self, f): + super(VendorSampleFileView, self).configure_form(f) + + # vendor + f.set_renderer('vendor', self.render_vendor) + if self.creating: + f.replace('vendor', 'vendor_uuid') + f.set_label('vendor_uuid', "Vendor") + f.set_widget('vendor_uuid', + forms.widgets.make_vendor_widget(self.request)) + else: + f.set_readonly('vendor') + + # filename + if self.creating: + f.replace('filename', 'file') + f.set_type('file', 'file') + else: + f.set_readonly('filename') + f.set_renderer('filename', self.render_filename) + + # effective_date + f.set_type('effective_date', 'date_jquery') + + # notes + f.set_type('notes', 'text') + + # created_by + if self.creating or self.editing: + f.remove('created_by') + else: + f.set_readonly('created_by') + f.set_renderer('created_by', self.render_user) + + def objectify(self, form, data=None): + if data is None: + data = form.validated + + sample = super(VendorSampleFileView, self).objectify(form, data=data) + + if self.creating: + sample.filename = data['file']['filename'] + data['file']['fp'].seek(0) + sample.bytes = data['file']['fp'].read() + sample.created_by = self.request.user + + return sample + + def render_filename(self, sample, field): + filename = getattr(sample, field) + if not filename: + return + + size = self.readable_size(None, size=len(sample.bytes)) + text = "{} ({})".format(filename, size) + url = self.get_action_url('download', sample) + return tags.link_to(text, url) + + def download(self): + """ + View for downloading a sample file. + + We override default logic to send raw bytes from DB, and avoid + writing file to disk. + """ + sample = self.get_instance() + + response = self.request.response + response.content_length = len(sample.bytes) + response.content_disposition = 'attachment; filename="{}"'.format( + sample.filename) + response.body = sample.bytes + return response + + +def defaults(config, **kwargs): + base = globals() + + VendorSampleFileView = kwargs.get('VendorSampleFileView', base['VendorSampleFileView']) + VendorSampleFileView.defaults(config) + + +def includeme(config): + defaults(config)