Several disparate changes needed for vendor catalog improvements
- invoke vendor handler where appropriate, e.g. for parsers - reverse "polarity" of dropdown chooser setting; rename it - tweak autocomplete behavior yet again, for dynamic values - auto-select vendor upon parser selection, when possible
This commit is contained in:
		
							parent
							
								
									ab61778d35
								
							
						
					
					
						commit
						88b3279e63
					
				
					 12 changed files with 164 additions and 93 deletions
				
			
		|  | @ -2,7 +2,7 @@ | |||
| ################################################################################ | ||||
| # | ||||
| #  Rattail -- Retail Software Framework | ||||
| #  Copyright © 2010-2021 Lance Edgar | ||||
| #  Copyright © 2010-2022 Lance Edgar | ||||
| # | ||||
| #  This file is part of Rattail. | ||||
| # | ||||
|  | @ -247,6 +247,7 @@ class JQueryAutocompleteWidget(dfwidget.AutocompleteInputWidget): | |||
|     template = 'autocomplete_jquery' | ||||
|     requirements = None | ||||
|     field_display = "" | ||||
|     assigned_label = None | ||||
|     service_url = None | ||||
|     cleared_callback = None | ||||
|     selected_callback = None | ||||
|  | @ -275,6 +276,7 @@ class JQueryAutocompleteWidget(dfwidget.AutocompleteInputWidget): | |||
|         kw['options'] = json.dumps(options) | ||||
|         kw['field_display'] = self.field_display | ||||
|         kw['cleared_callback'] = self.cleared_callback | ||||
|         kw['assigned_label'] = self.assigned_label | ||||
|         kw.setdefault('selected_callback', self.selected_callback) | ||||
|         tmpl_values = self.get_template_values(field, cstruct, kw) | ||||
|         template = readonly and self.readonly_template or self.template | ||||
|  |  | |||
|  | @ -95,6 +95,22 @@ const TailboneAutocomplete = { | |||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     watch: { | ||||
|         // TODO: yikes this feels hacky.  what happens is, when the
 | ||||
|         // caller explicitly assigns a new UUID value to the tailbone
 | ||||
|         // autocomplate component, the underlying buefy autocomplete
 | ||||
|         // component was not getting the new value.  so here we are
 | ||||
|         // explicitly making sure it is in sync.  this issue was
 | ||||
|         // discovered on the "new vendor catalog batch" page
 | ||||
|         value(val) { | ||||
|             this.$nextTick(() => { | ||||
|                 if (this.buefyValue != val) { | ||||
|                     this.buefyValue = val | ||||
|                 } | ||||
|             }) | ||||
|         }, | ||||
|     }, | ||||
| 
 | ||||
|     methods: { | ||||
| 
 | ||||
|         // fetch new search results from the server.  this is invoked
 | ||||
|  |  | |||
|  | @ -64,7 +64,7 @@ | |||
| 
 | ||||
|       <b-autocomplete ref="autocomplete" | ||||
|                       :name="name" | ||||
|                       v-show="!assignedValue && !selected" | ||||
|                       v-show="!value && !selected" | ||||
|                       v-model="buefyValue" | ||||
|                       :placeholder="placeholder" | ||||
|                       :data="data" | ||||
|  | @ -76,7 +76,7 @@ | |||
|         </template> | ||||
|       </b-autocomplete> | ||||
| 
 | ||||
|       <b-button v-if="assignedValue || selected" | ||||
|       <b-button v-if="value || selected" | ||||
|                 style="width: 100%; justify-content: left;" | ||||
|                 @click="clearSelection(true)"> | ||||
|         {{ getDisplayText() }} (click to change) | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| 
 | ||||
| <%def name="extra_javascript()"> | ||||
|   ${parent.extra_javascript()} | ||||
|   % if not use_buefy: | ||||
|   <script type="text/javascript"> | ||||
| 
 | ||||
|     var vendormap = { | ||||
|  | @ -50,6 +51,29 @@ | |||
| 
 | ||||
|     }); | ||||
|   </script> | ||||
|   % endif | ||||
| </%def> | ||||
| 
 | ||||
| <%def name="modify_this_page_vars()"> | ||||
|   ${parent.modify_this_page_vars()} | ||||
|   <script type="text/javascript"> | ||||
| 
 | ||||
|     ${form.component_studly}Data.parsers = ${json.dumps(parsers_data)|n} | ||||
| 
 | ||||
|     ${form.component_studly}Data.vendorName = null | ||||
| 
 | ||||
|     ${form.component_studly}.watch.field_model_parser_key = function(val) { | ||||
|         let parser = this.parsers[val] | ||||
|         if (parser.vendor_uuid) { | ||||
|             if (this.field_model_vendor_uuid != parser.vendor_uuid) { | ||||
|                 this.field_model_vendor_uuid = parser.vendor_uuid | ||||
|                 this.vendorName = parser.vendor_name | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|   </script> | ||||
| </%def> | ||||
| 
 | ||||
| 
 | ||||
| ${parent.body()} | ||||
|  |  | |||
|  | @ -110,7 +110,8 @@ | |||
|     <tailbone-autocomplete name="${name}" | ||||
|                            service-url="${url}" | ||||
|                            v-model="${vmodel}" | ||||
|                            initial-label="${field_display}"> | ||||
|                            initial-label="${field_display}" | ||||
|                            tal:attributes=":assigned-label assigned_label or 'null';"> | ||||
|     </tailbone-autocomplete> | ||||
|   </div> | ||||
| 
 | ||||
|  |  | |||
|  | @ -76,6 +76,8 @@ | |||
|       template: '#${form.component}-template', | ||||
|       components: {}, | ||||
|       props: {}, | ||||
|       watch: {}, | ||||
|       computed: {}, | ||||
|       methods: { | ||||
| 
 | ||||
|           ## TODO: deprecate / remove the latter option here | ||||
|  |  | |||
							
								
								
									
										8
									
								
								tailbone/templates/vendors/configure.mako
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								tailbone/templates/vendors/configure.mako
									
										
									
									
										vendored
									
									
								
							|  | @ -6,11 +6,11 @@ | |||
|   <h3 class="block is-size-3">Display</h3> | ||||
|   <div class="block" style="padding-left: 2rem;"> | ||||
| 
 | ||||
|     <b-field message="If not set, vendor chooser is a dropdown field."> | ||||
|       <b-checkbox name="rattail.vendor.use_autocomplete" | ||||
|                   v-model="simpleSettings['rattail.vendor.use_autocomplete']" | ||||
|     <b-field message="If not set, vendor chooser is an autocomplete field."> | ||||
|       <b-checkbox name="rattail.vendors.choice_uses_dropdown" | ||||
|                   v-model="simpleSettings['rattail.vendors.choice_uses_dropdown']" | ||||
|                   @input="settingsNeedSaved = true"> | ||||
|         Show vendor chooser as autocomplete field | ||||
|         Show vendor chooser as dropdown (select) element | ||||
|       </b-checkbox> | ||||
|     </b-field> | ||||
| 
 | ||||
|  |  | |||
|  | @ -30,15 +30,13 @@ import logging | |||
| 
 | ||||
| import six | ||||
| 
 | ||||
| from rattail.db import model, api | ||||
| from rattail.vendors.catalogs import iter_catalog_parsers | ||||
| from rattail.db import model | ||||
| 
 | ||||
| import colander | ||||
| from deform import widget as dfwidget | ||||
| from webhelpers2.html import tags | ||||
| 
 | ||||
| from tailbone import forms | ||||
| from tailbone.db import Session | ||||
| from tailbone.views.batch import FileBatchMasterView | ||||
| from tailbone.diffs import Diff | ||||
| 
 | ||||
|  | @ -139,13 +137,9 @@ class VendorCatalogView(FileBatchMasterView): | |||
| 
 | ||||
|     def get_parsers(self): | ||||
|         if not hasattr(self, 'parsers'): | ||||
|             parsers = sorted(iter_catalog_parsers(), key=lambda p: p.display) | ||||
|             supported = self.rattail_config.getlist( | ||||
|                 'tailbone', 'batch.vendorcatalog.supported_parsers') | ||||
|             if supported: | ||||
|                 parsers = [parser for parser in parsers | ||||
|                            if parser.key in supported] | ||||
|             self.parsers = parsers | ||||
|             app = self.get_rattail_app() | ||||
|             vendor_handler = app.get_vendor_handler() | ||||
|             self.parsers = vendor_handler.get_supported_catalog_parsers() | ||||
|         return self.parsers | ||||
| 
 | ||||
|     def configure_grid(self, g): | ||||
|  | @ -160,24 +154,8 @@ class VendorCatalogView(FileBatchMasterView): | |||
| 
 | ||||
|     def configure_form(self, f): | ||||
|         super(VendorCatalogView, self).configure_form(f) | ||||
| 
 | ||||
|         # vendor | ||||
|         f.set_renderer('vendor', self.render_vendor) | ||||
|         if self.creating and 'vendor' in f: | ||||
|             f.replace('vendor', 'vendor_uuid') | ||||
|             f.set_node('vendor_uuid', colander.String()) | ||||
|             vendor_display = "" | ||||
|             if self.request.method == 'POST': | ||||
|                 if self.request.POST.get('vendor_uuid'): | ||||
|                     vendor = self.Session.query(model.Vendor).get(self.request.POST['vendor_uuid']) | ||||
|                     if vendor: | ||||
|                         vendor_display = six.text_type(vendor) | ||||
|             vendors_url = self.request.route_url('vendors.autocomplete') | ||||
|             f.set_widget('vendor_uuid', forms.widgets.JQueryAutocompleteWidget( | ||||
|                 field_display=vendor_display, service_url=vendors_url)) | ||||
|             f.set_label('vendor_uuid', "Vendor") | ||||
|         else: | ||||
|             f.set_readonly('vendor') | ||||
|         app = self.get_rattail_app() | ||||
|         vendor_handler = app.get_vendor_handler() | ||||
| 
 | ||||
|         # filename | ||||
|         f.set_label('filename', "Catalog File") | ||||
|  | @ -196,12 +174,75 @@ class VendorCatalogView(FileBatchMasterView): | |||
|             f.set_widget('parser_key', dfwidget.SelectWidget(values=values)) | ||||
|             f.set_label('parser_key', "File Type") | ||||
| 
 | ||||
|         # vendor | ||||
|         f.set_renderer('vendor', self.render_vendor) | ||||
|         if self.creating and 'vendor' in f: | ||||
|             f.replace('vendor', 'vendor_uuid') | ||||
|             f.set_label('vendor_uuid', "Vendor") | ||||
|             use_dropdown = vendor_handler.choice_uses_dropdown() | ||||
|             if use_dropdown: | ||||
|                 vendors = self.Session.query(model.Vendor)\ | ||||
|                                       .order_by(model.Vendor.id) | ||||
|                 vendor_values = [(vendor.uuid, "({}) {}".format(vendor.id, | ||||
|                                                                 vendor.name)) | ||||
|                                  for vendor in vendors] | ||||
|                 f.set_widget('vendor_uuid', | ||||
|                              dfwidget.SelectWidget(values=vendor_values)) | ||||
|             else: | ||||
|                 vendor_display = "" | ||||
|                 if self.request.method == 'POST': | ||||
|                     if self.request.POST.get('vendor_uuid'): | ||||
|                         vendor = self.Session.query(model.Vendor).get( | ||||
|                             self.request.POST['vendor_uuid']) | ||||
|                         if vendor: | ||||
|                             vendor_display = six.text_type(vendor) | ||||
|                 vendors_url = self.request.route_url('vendors.autocomplete') | ||||
|                 f.set_widget('vendor_uuid', forms.widgets.JQueryAutocompleteWidget( | ||||
|                     field_display=vendor_display, service_url=vendors_url, | ||||
|                     assigned_label='vendorName')) | ||||
|         else: | ||||
|             f.set_readonly('vendor') | ||||
| 
 | ||||
|         # effective | ||||
|         if self.creating: | ||||
|             f.remove('effective') | ||||
|         else: | ||||
|             f.set_readonly('effective') | ||||
| 
 | ||||
|     def template_kwargs_create(self, **kwargs): | ||||
|         use_buefy = self.get_use_buefy() | ||||
|         app = self.get_rattail_app() | ||||
|         vendor_handler = app.get_vendor_handler() | ||||
|         parsers = self.get_parsers() | ||||
|         parsers_data = {} | ||||
|         for parser in parsers: | ||||
|             if use_buefy: | ||||
|                 pdata = {'key': parser.key, | ||||
|                          'vendor_key': parser.vendor_key} | ||||
|                 if parser.vendor_key: | ||||
|                     vendor = vendor_handler.get_vendor(self.Session(), | ||||
|                                                        parser.vendor_key) | ||||
|                     if vendor: | ||||
|                         pdata['vendor_uuid'] = vendor.uuid | ||||
|                         pdata['vendor_name'] = vendor.name | ||||
|                 parsers_data[parser.key] = pdata | ||||
|             else: | ||||
|                 if parser.vendor_key: | ||||
|                     vendor = vendor_handler.get_vendor(self.Session(), | ||||
|                                                        parser.vendor_key) | ||||
|                     if vendor: | ||||
|                         parser.vendormap_value = "{{uuid: '{}', name: '{}'}}".format( | ||||
|                             vendor.uuid, vendor.name.replace("'", "\\'")) | ||||
|                     else: | ||||
|                         log.warning("vendor '{}' not found for parser: {}".format( | ||||
|                             parser.vendor_key, parser.key)) | ||||
|                         parser.vendormap_value = 'null' | ||||
|                 else: | ||||
|                     parser.vendormap_value = 'null' | ||||
|         kwargs['parsers'] = parsers | ||||
|         kwargs['parsers_data'] = parsers_data | ||||
|         return kwargs | ||||
| 
 | ||||
|     def get_batch_kwargs(self, batch): | ||||
|         kwargs = super(VendorCatalogView, self).get_batch_kwargs(batch) | ||||
|         kwargs['parser_key'] = batch.parser_key | ||||
|  | @ -275,23 +316,6 @@ class VendorCatalogView(FileBatchMasterView): | |||
| 
 | ||||
|         return kwargs | ||||
| 
 | ||||
|     def template_kwargs_create(self, **kwargs): | ||||
|         parsers = self.get_parsers() | ||||
|         for parser in parsers: | ||||
|             if parser.vendor_key: | ||||
|                 vendor = api.get_vendor(Session(), parser.vendor_key) | ||||
|                 if vendor: | ||||
|                     parser.vendormap_value = "{{uuid: '{}', name: '{}'}}".format( | ||||
|                         vendor.uuid, vendor.name.replace("'", "\\'")) | ||||
|                 else: | ||||
|                     log.warning("vendor '{}' not found for parser: {}".format( | ||||
|                         parser.vendor_key, parser.key)) | ||||
|                     parser.vendormap_value = 'null' | ||||
|             else: | ||||
|                 parser.vendormap_value = 'null' | ||||
|         kwargs['parsers'] = parsers | ||||
|         return kwargs | ||||
| 
 | ||||
| # TODO: deprecate / remove this | ||||
| VendorCatalogsView = VendorCatalogView | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| ################################################################################ | ||||
| # | ||||
| #  Rattail -- Retail Software Framework | ||||
| #  Copyright © 2010-2021 Lance Edgar | ||||
| #  Copyright © 2010-2022 Lance Edgar | ||||
| # | ||||
| #  This file is part of Rattail. | ||||
| # | ||||
|  | @ -29,7 +29,6 @@ from __future__ import unicode_literals, absolute_import | |||
| import six | ||||
| 
 | ||||
| from rattail.db import model, api | ||||
| from rattail.time import localtime | ||||
| 
 | ||||
| import colander | ||||
| from deform import widget as dfwidget | ||||
|  | @ -230,7 +229,8 @@ class PurchasingBatchView(BatchMasterView): | |||
|         super(PurchasingBatchView, self).configure_form(f) | ||||
|         model = self.model | ||||
|         batch = f.model_instance | ||||
|         today = localtime(self.rattail_config).date() | ||||
|         app = self.get_rattail_app() | ||||
|         today = app.localtime().date() | ||||
|         use_buefy = self.get_use_buefy() | ||||
| 
 | ||||
|         # mode | ||||
|  | @ -265,9 +265,15 @@ class PurchasingBatchView(BatchMasterView): | |||
|         if self.creating: | ||||
|             f.replace('vendor', 'vendor_uuid') | ||||
|             f.set_label('vendor_uuid', "Vendor") | ||||
|             use_autocomplete = self.rattail_config.getbool( | ||||
|                 'rattail', 'vendor.use_autocomplete', default=True) | ||||
|             if use_autocomplete: | ||||
|             vendor_handler = app.get_vendor_handler() | ||||
|             use_dropdown = vendor_handler.choice_uses_dropdown() | ||||
|             if use_dropdown: | ||||
|                 vendors = self.Session.query(model.Vendor)\ | ||||
|                                       .order_by(model.Vendor.id) | ||||
|                 vendor_values = [(vendor.uuid, "({}) {}".format(vendor.id, vendor.name)) | ||||
|                                  for vendor in vendors] | ||||
|                 f.set_widget('vendor_uuid', dfwidget.SelectWidget(values=vendor_values)) | ||||
|             else: | ||||
|                 vendor_display = "" | ||||
|                 if self.request.method == 'POST': | ||||
|                     if self.request.POST.get('vendor_uuid'): | ||||
|  | @ -277,12 +283,6 @@ class PurchasingBatchView(BatchMasterView): | |||
|                 vendors_url = self.request.route_url('vendors.autocomplete') | ||||
|                 f.set_widget('vendor_uuid', forms.widgets.JQueryAutocompleteWidget( | ||||
|                     field_display=vendor_display, service_url=vendors_url)) | ||||
|             else: | ||||
|                 vendors = self.Session.query(model.Vendor)\ | ||||
|                                       .order_by(model.Vendor.id) | ||||
|                 vendor_values = [(vendor.uuid, "({}) {}".format(vendor.id, vendor.name)) | ||||
|                                  for vendor in vendors] | ||||
|                 f.set_widget('vendor_uuid', dfwidget.SelectWidget(values=vendor_values)) | ||||
|         elif self.editing: | ||||
|             f.set_readonly('vendor') | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| ################################################################################ | ||||
| # | ||||
| #  Rattail -- Retail Software Framework | ||||
| #  Copyright © 2010-2021 Lance Edgar | ||||
| #  Copyright © 2010-2022 Lance Edgar | ||||
| # | ||||
| #  This file is part of Rattail. | ||||
| # | ||||
|  | @ -200,9 +200,19 @@ class CostingBatchView(PurchasingBatchView): | |||
|             form.set_default('workflow', valid_workflows[0]) | ||||
| 
 | ||||
|         # configure vendor field | ||||
|         use_autocomplete = self.rattail_config.getbool( | ||||
|             'rattail', 'vendor.use_autocomplete', default=True) | ||||
|         if use_autocomplete: | ||||
|         app = self.get_rattail_app() | ||||
|         vendor_handler = app.get_vendor_handler() | ||||
|         use_dropdown = vendor_handler.choice_uses_dropdown() | ||||
|         if use_dropdown: | ||||
|             vendors = self.Session.query(model.Vendor)\ | ||||
|                                   .order_by(model.Vendor.id) | ||||
|             vendor_values = [(vendor.uuid, "({}) {}".format(vendor.id, vendor.name)) | ||||
|                              for vendor in vendors] | ||||
|             if use_buefy: | ||||
|                 form.set_widget('vendor', dfwidget.SelectWidget(values=vendor_values)) | ||||
|             else: | ||||
|                 form.set_widget('vendor', forms.widgets.JQuerySelectWidget(values=vendor_values)) | ||||
|         else: | ||||
|             vendor_display = "" | ||||
|             if self.request.method == 'POST': | ||||
|                 if self.request.POST.get('vendor'): | ||||
|  | @ -212,15 +222,6 @@ class CostingBatchView(PurchasingBatchView): | |||
|             vendors_url = self.request.route_url('vendors.autocomplete') | ||||
|             form.set_widget('vendor', forms.widgets.JQueryAutocompleteWidget( | ||||
|                 field_display=vendor_display, service_url=vendors_url)) | ||||
|         else: | ||||
|             vendors = self.Session.query(model.Vendor)\ | ||||
|                                   .order_by(model.Vendor.id) | ||||
|             vendor_values = [(vendor.uuid, "({}) {}".format(vendor.id, vendor.name)) | ||||
|                              for vendor in vendors] | ||||
|             if use_buefy: | ||||
|                 form.set_widget('vendor', dfwidget.SelectWidget(values=vendor_values)) | ||||
|             else: | ||||
|                 form.set_widget('vendor', forms.widgets.JQuerySelectWidget(values=vendor_values)) | ||||
| 
 | ||||
|         # configure workflow field | ||||
|         values = [(workflow['workflow_key'], workflow['display']) | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| ################################################################################ | ||||
| # | ||||
| #  Rattail -- Retail Software Framework | ||||
| #  Copyright © 2010-2021 Lance Edgar | ||||
| #  Copyright © 2010-2022 Lance Edgar | ||||
| # | ||||
| #  This file is part of Rattail. | ||||
| # | ||||
|  | @ -294,9 +294,19 @@ class ReceivingBatchView(PurchasingBatchView): | |||
|                           use_buefy=use_buefy) | ||||
| 
 | ||||
|         # configure vendor field | ||||
|         use_autocomplete = self.rattail_config.getbool( | ||||
|             'rattail', 'vendor.use_autocomplete', default=True) | ||||
|         if use_autocomplete: | ||||
|         app = self.get_rattail_app() | ||||
|         vendor_handler = app.get_vendor_handler() | ||||
|         use_dropdown = vendor_handler.choice_uses_dropdown() | ||||
|         if use_dropdown: | ||||
|             vendors = self.Session.query(model.Vendor)\ | ||||
|                                   .order_by(model.Vendor.id) | ||||
|             vendor_values = [(vendor.uuid, "({}) {}".format(vendor.id, vendor.name)) | ||||
|                              for vendor in vendors] | ||||
|             if use_buefy: | ||||
|                 form.set_widget('vendor', dfwidget.SelectWidget(values=vendor_values)) | ||||
|             else: | ||||
|                 form.set_widget('vendor', forms.widgets.JQuerySelectWidget(values=vendor_values)) | ||||
|         else: | ||||
|             vendor_display = "" | ||||
|             if self.request.method == 'POST': | ||||
|                 if self.request.POST.get('vendor'): | ||||
|  | @ -306,15 +316,6 @@ class ReceivingBatchView(PurchasingBatchView): | |||
|             vendors_url = self.request.route_url('vendors.autocomplete') | ||||
|             form.set_widget('vendor', forms.widgets.JQueryAutocompleteWidget( | ||||
|                 field_display=vendor_display, service_url=vendors_url)) | ||||
|         else: | ||||
|             vendors = self.Session.query(model.Vendor)\ | ||||
|                                   .order_by(model.Vendor.id) | ||||
|             vendor_values = [(vendor.uuid, "({}) {}".format(vendor.id, vendor.name)) | ||||
|                              for vendor in vendors] | ||||
|             if use_buefy: | ||||
|                 form.set_widget('vendor', dfwidget.SelectWidget(values=vendor_values)) | ||||
|             else: | ||||
|                 form.set_widget('vendor', forms.widgets.JQuerySelectWidget(values=vendor_values)) | ||||
| 
 | ||||
|         # configure workflow field | ||||
|         values = [(workflow['workflow_key'], workflow['display']) | ||||
|  |  | |||
							
								
								
									
										4
									
								
								tailbone/views/vendors/core.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								tailbone/views/vendors/core.py
									
										
									
									
										vendored
									
									
								
							|  | @ -2,7 +2,7 @@ | |||
| ################################################################################ | ||||
| # | ||||
| #  Rattail -- Retail Software Framework | ||||
| #  Copyright © 2010-2021 Lance Edgar | ||||
| #  Copyright © 2010-2022 Lance Edgar | ||||
| # | ||||
| #  This file is part of Rattail. | ||||
| # | ||||
|  | @ -175,7 +175,7 @@ class VendorView(MasterView): | |||
| 
 | ||||
|             # display | ||||
|             {'section': 'rattail', | ||||
|              'option': 'vendor.use_autocomplete', | ||||
|              'option': 'vendors.choice_uses_dropdown', | ||||
|              'type': bool}, | ||||
|         ] | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar