Add convenience wrapper to make customer field widget, etc.
customer widget is either autocomplete or dropdown, per config also added a way to pass arbitrary kwargs to the chameleon template rendering for a field also moved the logic for rendering a <b-field> out of the template and into the Form class also start to prefer `input_handler` over `input_callback` when specifying client-side JS hook
This commit is contained in:
parent
5334cf1871
commit
d6aeb1d10f
|
@ -332,7 +332,7 @@ class Form(object):
|
|||
|
||||
def __init__(self, fields=None, schema=None, request=None, readonly=False, readonly_fields=[],
|
||||
model_instance=None, model_class=None, appstruct=UNSPECIFIED, nodes={}, enums={}, labels={},
|
||||
assume_local_times=False, renderers=None,
|
||||
assume_local_times=False, renderers=None, renderer_kwargs={},
|
||||
hidden={}, widgets={}, defaults={}, validators={}, required={}, helptext={}, focus_spec=None,
|
||||
action_url=None, cancel_url=None, use_buefy=None, component='tailbone-form',
|
||||
vuejs_field_converters={},
|
||||
|
@ -361,6 +361,7 @@ class Form(object):
|
|||
self.renderers = self.make_renderers()
|
||||
else:
|
||||
self.renderers = renderers or {}
|
||||
self.renderer_kwargs = renderer_kwargs or {}
|
||||
self.hidden = hidden or {}
|
||||
self.widgets = widgets or {}
|
||||
self.defaults = defaults or {}
|
||||
|
@ -660,6 +661,22 @@ class Form(object):
|
|||
else:
|
||||
self.renderers[key] = renderer
|
||||
|
||||
def add_renderer_kwargs(self, key, kwargs):
|
||||
self.renderer_kwargs.setdefault(key, {}).update(kwargs)
|
||||
|
||||
def get_renderer_kwargs(self, key):
|
||||
return self.renderer_kwargs.get(key, {})
|
||||
|
||||
def set_renderer_kwargs(self, key, kwargs):
|
||||
self.renderer_kwargs[key] = kwargs
|
||||
|
||||
def set_input_handler(self, key, value):
|
||||
"""
|
||||
Convenience method to assign "input handler" callback code for
|
||||
the given field.
|
||||
"""
|
||||
self.add_renderer_kwargs(key, {'input_handler': value})
|
||||
|
||||
def set_hidden(self, key, hidden=True):
|
||||
self.hidden[key] = hidden
|
||||
|
||||
|
@ -858,6 +875,58 @@ class Form(object):
|
|||
return False
|
||||
return True
|
||||
|
||||
def render_buefy_field(self, fieldname, bfield_attrs={}):
|
||||
"""
|
||||
Render the given field in a Buefy-compatible way. Note that
|
||||
this is meant to render *editable* fields, i.e. showing a
|
||||
widget, unless the field input is hidden. In other words it's
|
||||
not for "readonly" fields.
|
||||
"""
|
||||
dform = self.make_deform_form()
|
||||
field = dform[fieldname]
|
||||
|
||||
if self.field_visible(fieldname):
|
||||
|
||||
# these attrs will be for the <b-field> (*not* the widget)
|
||||
attrs = {
|
||||
':horizontal': 'true',
|
||||
'label': self.get_label(fieldname),
|
||||
}
|
||||
|
||||
# add some magic for file input fields
|
||||
if isinstance(field.schema.typ, deform.FileData):
|
||||
attrs['class_'] = 'file'
|
||||
|
||||
# show helptext if present
|
||||
if self.has_helptext(fieldname):
|
||||
attrs['message'] = self.render_helptext(fieldname)
|
||||
|
||||
# show errors if present
|
||||
error_messages = self.get_error_messages(field)
|
||||
if error_messages:
|
||||
attrs.update({
|
||||
'type': 'is-danger',
|
||||
# ':message': self.messages_json(error_messages),
|
||||
':message': error_messages,
|
||||
})
|
||||
|
||||
# merge anything caller provided
|
||||
attrs.update(bfield_attrs)
|
||||
|
||||
# render the field widget or whatever
|
||||
html = field.serialize(use_buefy=True,
|
||||
**self.get_renderer_kwargs(fieldname))
|
||||
# TODO: why do we not get HTML literal from serialize() ?
|
||||
html = HTML.literal(html)
|
||||
|
||||
# and finally wrap it all in a <b-field>
|
||||
return HTML.tag('b-field', c=[html], **attrs)
|
||||
|
||||
else: # hidden field
|
||||
|
||||
# can just do normal thing for these
|
||||
return field.serialize()
|
||||
|
||||
def render_field_readonly(self, field_name, **kwargs):
|
||||
"""
|
||||
Render the given field completely, but in read-only fashion.
|
||||
|
|
|
@ -289,6 +289,95 @@ class JQueryAutocompleteWidget(dfwidget.AutocompleteInputWidget):
|
|||
return field.renderer(template, **tmpl_values)
|
||||
|
||||
|
||||
def make_customer_widget(request, **kwargs):
|
||||
"""
|
||||
Make a customer widget; will be either autocomplete or dropdown
|
||||
depending on config.
|
||||
"""
|
||||
# use autocomplete widget by default
|
||||
factory = CustomerAutocompleteWidget
|
||||
|
||||
# caller may request dropdown widget
|
||||
if kwargs.pop('dropdown', False):
|
||||
factory = CustomerDropdownWidget
|
||||
|
||||
else: # or, config may say to use dropdown
|
||||
if request.rattail_config.getbool(
|
||||
'rattail', 'customers.choice_uses_dropdown',
|
||||
default=False):
|
||||
factory = CustomerDropdownWidget
|
||||
|
||||
# instantiate whichever
|
||||
return factory(request, **kwargs)
|
||||
|
||||
|
||||
class CustomerAutocompleteWidget(JQueryAutocompleteWidget):
|
||||
"""
|
||||
Autocomplete widget for a Customer reference field.
|
||||
"""
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(CustomerAutocompleteWidget, 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('customers.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 customer to provide button label, if we have a value
|
||||
if cstruct:
|
||||
model = self.request.rattail_config.get_model()
|
||||
customer = Session.query(model.Customer).get(cstruct)
|
||||
if customer:
|
||||
self.field_display = six.text_type(customer)
|
||||
|
||||
return super(CustomerAutocompleteWidget, self).serialize(
|
||||
field, cstruct, **kw)
|
||||
|
||||
|
||||
class CustomerDropdownWidget(dfwidget.SelectWidget):
|
||||
"""
|
||||
Dropdown widget for a Customer reference field.
|
||||
"""
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(CustomerDropdownWidget, 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 'customers' in kwargs:
|
||||
customers = kwargs['customers']
|
||||
if callable(customers):
|
||||
customers = customers()
|
||||
|
||||
else: # default customer list
|
||||
model = self.request.rattail_config.get_model()
|
||||
customers = Session.query(model.Customer)\
|
||||
.order_by(model.Customer.name)\
|
||||
.all()
|
||||
|
||||
# convert customer list to option values
|
||||
self.values = [(c.uuid, c.name)
|
||||
for c in customers]
|
||||
|
||||
|
||||
class DepartmentWidget(dfwidget.SelectWidget):
|
||||
"""
|
||||
Custom select widget for a Department reference field.
|
||||
|
|
|
@ -3,6 +3,20 @@
|
|||
|
||||
<%def name="form_content()">
|
||||
|
||||
<h3 class="block is-size-3">General</h3>
|
||||
<div class="block" style="padding-left: 2rem;">
|
||||
|
||||
<b-field message="If not set, customer chooser is an autocomplete field.">
|
||||
<b-checkbox name="rattail.customers.choice_uses_dropdown"
|
||||
v-model="simpleSettings['rattail.customers.choice_uses_dropdown']"
|
||||
native-value="true"
|
||||
@input="settingsNeedSaved = true">
|
||||
Show customer chooser as dropdown (select) element
|
||||
</b-checkbox>
|
||||
</b-field>
|
||||
|
||||
</div>
|
||||
|
||||
<h3 class="block is-size-3">POS</h3>
|
||||
<div class="block" style="padding-left: 2rem;">
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
v-model="${vmodel}"
|
||||
initial-label="${field_display}"
|
||||
tal:attributes=":assigned-label assigned_label or 'null';
|
||||
@input input_callback|'';
|
||||
@input input_handler|input_callback|'';
|
||||
@new-label new_label_callback|'';">
|
||||
|
||||
</tailbone-autocomplete>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%namespace file="/forms/util.mako" import="render_buefy_field" />
|
||||
|
||||
<script type="text/x-template" id="${form.component}-template">
|
||||
|
||||
|
@ -21,7 +20,7 @@
|
|||
</b-field>
|
||||
|
||||
% elif field in dform:
|
||||
${render_buefy_field(dform[field])}
|
||||
${form.render_buefy_field(field)}
|
||||
% endif
|
||||
|
||||
% endfor
|
||||
|
|
|
@ -1,27 +1,7 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
|
||||
## TODO: deprecate / remove this
|
||||
## (tried to add deprecation warning here but it didn't seem to work)
|
||||
<%def name="render_buefy_field(field, bfield_kwargs={})">
|
||||
% if form.field_visible(field.name):
|
||||
<% error_messages = form.get_error_messages(field) %>
|
||||
<b-field horizontal
|
||||
label="${form.get_label(field.name)}"
|
||||
## TODO: is this class="file" really needed?
|
||||
% if isinstance(field.schema.typ, deform.FileData):
|
||||
class="file"
|
||||
% endif
|
||||
% if form.has_helptext(field.name):
|
||||
message="${form.render_helptext(field.name)}"
|
||||
% endif
|
||||
% if error_messages:
|
||||
type="is-danger"
|
||||
:message='${form.messages_json(error_messages)|n}'
|
||||
% endif
|
||||
${h.HTML.render_attrs(bfield_kwargs)}
|
||||
>
|
||||
${field.serialize(use_buefy=True)|n}
|
||||
</b-field>
|
||||
% else:
|
||||
## hidden field
|
||||
${field.serialize()|n}
|
||||
% endif
|
||||
${form.render_buefy_field(field.name, bfield_attrs=bfield_kwargs)}
|
||||
</%def>
|
||||
|
|
|
@ -490,6 +490,11 @@ class CustomerView(MasterView):
|
|||
def configure_get_simple_settings(self):
|
||||
return [
|
||||
|
||||
# General
|
||||
{'section': 'rattail',
|
||||
'option': 'customers.choice_uses_dropdown',
|
||||
'type': bool},
|
||||
|
||||
# POS
|
||||
{'section': 'rattail',
|
||||
'option': 'customers.active_in_pos',
|
||||
|
|
Loading…
Reference in a new issue