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
7 changed files with 183 additions and 27 deletions
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue