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. | ||||
|  |  | |||
|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar