Okay, here's the core of the code. First, this is the function that creates the form (in a component and submitted via ajax). It's a generic form for editing db records based on a url arg supplying the table name. You'll notice that I have to use a hack at the moment to get the values from my custom form (AjaxSelect) to submit. The widget code is below:
def editform(self, rargs=None, rvars=None): """ """ db = current.db flash = '' rjs = '' duplink = '' default_vars = {} if rargs is not None: tablename = rargs[0] showid = rvars['showid'] or True dbio = False if 'dbio' in rvars.keys() and rvars['dbio'] == 'False' else True formstyle = rvars['formstyle'] or 'ul' deletable = rvars['deletable'] or True copylabel = rvars['copylabel'] or SPAN(_class='glyphicon glyphicon-file') orderby = rvars['orderby'] or 'id' restrictor = rvars['restrictor'] or None collation = rvars['collation'] or None postprocess = rvars['postprocess'] or None if len(rargs) > 1: # editing specific item rowid = rargs[1] formname = '{}/{}'.format(tablename, rowid) formargs = [db[tablename], rowid] # create a link for adding a new row to the table duplink = A(copylabel, _href=URL('plugin_listandedit', 'dupAndEdit.load', args=[tablename, rowid], vars=rvars), _class='plugin_listandedit_duplicate', cid='viewpane') elif len(rargs) == 1: # creating new item formname = '{}/create'.format(tablename) default_vars = {k: v for k, v in rvars.iteritems() if hasattr(db[tablename], k)} formargs = [db[tablename]] form = self._myform(formargs, deletable=deletable, showid=showid, formstyle=formstyle) # print {'default_vars': default_vars} # for k in default_vars: form.vars.setitem(k, default_vars[k]) for k in default_vars: form.vars[k] = default_vars[k] # FIXME: ajaxselect field values have to be added manually # FIXME: this check will fail if ajaxselect widget is for field indx[1] if db[tablename].fields[1] in rvars.keys(): extras = [f for f in db[tablename].fields if f not in form.vars.keys()] for e in extras: form.vars[e] = rvars[e] if e in rvars.keys() \ else '' if 'id' in form.vars.keys() and form.vars['id'] in (None, '' ): del(form.vars['id']) else: pass # print 'form vars in editform ---------------------------------' # pprint(form.vars) if form.process(formname=formname, dbio=dbio).accepted: flash = '' if postprocess: flash += '{} '.format(self._post_process(form.vars, postprocess)) if dbio: flash += 'The changes were recorded successfully.' # either redirect or refresh the list pane if 'redirect' in rvars and 'True' == rvars['redirect']: redirect(URL(rvars['redirect_c'], rvars['redirect_a'])) else: the_url = URL('plugin_listandedit', 'itemlist.load', args=[tablename], vars={'orderby': orderby , 'restrictor': restrictor, 'collation': collation}) rjs = "window.setTimeout(web2py_component('{}', " \ "'listpane'), 500);".format(the_url) elif form.errors: print '\n\nlistandedit form errors:' pprint({k: v for k, v in form.errors.iteritems()}) print '\n\nlistandedit form vars' pprint({k: v for k, v in form.vars.iteritems()}) print '\n\nlistandedit request vars' pprint({k: v for k, v in rvars.iteritems()}) flash = 'Sorry, there was an error processing ' \ 'the form. The changes have not been recorded.' else: pass else: flash = 'Sorry, you need to specify a type of record before' \ 'I can list the records.' form = None return form, duplink, flash, rjs Now here's the first bit of the custom widget class. The thing to understand here is that the widget is refreshable (via ajax) without affecting the rest of the form. So you can update the select options via ajax from the database while you're in the midst of filling in the form. I do this by creating a web2py component to hold the form. So the AjaxSelect.widget() method below sets up the component, and the set_widget() controller function calls back to AjaxSelect.widget_content() to actually create the widget instance. (The widget also does other things like display a sortable list of selected options from a multi-select and dynamically add new db entries to the linked table.) If you want to see the full plugin code for the widget it's on github here: https://github.com/monotasker/plugin_ajaxselect I'm wondering whether putting the widget in a component is what creates the problem. If so I'd really like to find a way around that. It's a very powerful and configurable widget that (if I can get the last kinks worked out) I'd like to share with the community. class AjaxSelect(object): """ """ def __init__(self, field, value, indx=0, refresher=None, adder=True, restricted=None, restrictor=None, multi=True, lister=False, rval=None, sortable=False, orderby=None): # raw args self.field = field self.indx = indx self.refresher = refresher self.adder = adder # isolate setting of param for easy overriding in subclasses self.restricted = self.restrict(restricted) self.restrictor = restrictor self.multi = multi self.lister = lister self.rval = rval self.sortable = sortable self.orderby = orderby # find table referenced by widget self.fieldset = str(field).split('.') self.linktable = get_linktable(field) # processed variables self.wrappername = self.get_wrappername(self.fieldset) self.form_name = '%s_adder_form' % self.linktable # for referenced table form # get the field value (choosing db or session here) self.value = self.choose_val(value) try: if value and len(value) > 0: self.clean_val = ','.join(map(str, value)) else: self.clean_val = value except TypeError: self.clean_val = value # args for add and refresh urls self.uargs = self.fieldset # vars for add and refresh urls self.uvars = {'wrappername': self.wrappername, 'refresher': refresher, 'adder': self.adder, 'restrictor': self.restrictor, 'multi': self.multi, 'lister': self.lister, 'restricted': self.restricted, 'sortable': self.sortable, 'orderby': self.orderby, 'indx': self.indx} def widget(self): """ Place initial load container for controller to fill. """ # prepare classes for widget wrapper wclasses = self.get_classes(self.linktable, self.restricted, self.restrictor, self.lister, self. sortable) uvars = self.uvars uvars.update({self.fieldset[1]: self.value}) # create SPAN to wrap widget wrapper = SPAN(_id=self.wrappername, _class=wclasses) wrapper.append(LOAD('plugin_ajaxselect', 'set_widget.load', args=self.uargs, vars=uvars, target=self.wrappername, ajax=False)) return wrapper def widget_contents(self): """ Main method to create the ajaxselect widget. Calls helper methods and returns the wrapper element containing all associated elements """ #session = current.session #request = current.request wrapper = CAT() # create and add content of SPAN widget = self.create_widget() refreshlink = self.make_refresher(self.wrappername, self.linktable, self.uargs, self.uvars) adder, modal = self.make_adder(self.wrappername, self.linktable) wrapper.components.extend([widget, refreshlink, adder]) # create and add tags/links if multiple select widget if self.multi and (self.lister == 'simple'): taglist = self.make_taglist() elif self.multi and (self.lister == 'editlinks'): taglist = self.make_linklist() else: taglist = '' wrapper.append(taglist) return wrapper, modal def create_widget(self): """ create either a single select widget or multiselect widget """ if not self.multi in [None, False, 'False']: if self.orderby: w = FilteredMultipleOptionsWidget.widget(self.field, self. value, orderby=self.orderby, multiple='multiple') else: w = MultipleOptionsWidget.widget(self.field, self.value) #place selected items at end of sortable select widget if self.sortable: try: for v in self.value: opt = w.element(_value=v) i = w.elements().index(opt) w.append(opt) del w[i - 1] except AttributeError, e: if type(v) == 'IntType': opt = w.element(_value=self.value) i = w.elements().index(opt) w.append(opt) del w[i - 1] else: print e except Exception, e: print e, type(e) else: if self.orderby: w = FilteredOptionsWidget.widget(self.field, self.value, orderby=self.orderby) else: w = OptionsWidget.widget(self.field, self.value) w['_id'] = '{}_{}'.format(self.fieldset[0], self.fieldset[1]) w['_name'] = self.fieldset[1] return w On Thursday, February 18, 2016 at 8:33:23 PM UTC-5, Anthony wrote: > > When you pass a record to SQLFORM, it retains the changed values by > passing the values in request.post_vars to the respective field widgets. > Can't say what's going on in your case without the code. > > Anthony > > On Thursday, February 18, 2016 at 4:13:23 PM UTC-5, Ian W. Scott wrote: >> >> The code is very extensive (it's a complex widget) so first I'm just >> trying to understand what happens in a normal form. You say that other >> widgets aren't preserving values from the db. So where do the values come >> from if you have keepvalues=True? >> >> Also, this *is* a situation where an existing record is loaded into the >> form, then modified on form submission. So how does the form present to me >> the values I just changed if it's not coming from the db? I think if I can >> understand how that works I'll be able to solve the problem in my code >> myself. >> >> Thanks, >> >> Ian >> >> On Thursday, February 18, 2016 at 4:06:06 PM UTC-5, Anthony wrote: >>> >>> On Thursday, February 18, 2016 at 3:54:43 PM UTC-5, Ian W. Scott wrote: >>>> >>>> Okay, that explains the behaviour. How, then, are values usually >>>> preserved on an ajax form for the built-in widgets? When I submit the form >>>> all of the other fields (not using my widget) preserve the newly submitted >>>> values. >>>> >>> >>> Again, hard to say what's going on in your case without seeing any code, >>> but the other widgets are not presenting values from the database (unless a >>> pre-existing record has been loaded). >>> >>> Anthony >>> >> -- Resources: - http://web2py.com - http://web2py.com/book (Documentation) - http://github.com/web2py/web2py (Source code) - https://code.google.com/p/web2py/issues/list (Report Issues) --- You received this message because you are subscribed to the Google Groups "web2py-users" group. To unsubscribe from this group and stop receiving emails from it, send an email to web2py+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.