If you benefit from web2py hope you feel encouraged to pay it forward by contributing back to society in whatever form you choose!

The basic idea is to use represent to show a field in the grid as an INPUT tag, to add a submit button to SQLFORM.grid and to trap the data.

This can be done using existing web2py functionality. This may be a bit hacky, but it seems to work ok.

 

For example, set up this model in db.py

db.define_table('person',
                Field('first_name','string',length=60),
                Field ('second_name','string',length=60),
                Field('is_happy','boolean),
F
                Field('home_state',requires=IS_EMPTY_OR(IS_IN_SET(['vic','nsw','qld'])))
                )

We are going to make the home_state field inline editable using a drop-down menu provided by web2py's option widget, and use text widgets for the other two fields.

So in the controller function, we create the option field using web2py's widget (using the represent attribute). This provides a pop-up menu for the field, showing the valid fields. We tag the name of each field with a rowid so we know which record to update.

We need a submit button so the grid is submittable. SQLFORM.grid does that via selectable, although in this case I get rid of the checkboxes per row provided by selectable, leaving me with a submit button. The selectable redirect preserves the grid's settings so that after the edit, the user is returned to the same page of the grid, with the same sort order etc.

At the top of the controller function we update the database if a submit was made. This is a bit slow since my code updates for every field submitted, not just for those which are changed. The example code assumes the grid refers to only one table. Easy to modify if required.

The code

First, a couple of helpers which make the HTML entry widgets. We use web2py to do this.

def options_widget(field,value,**kwargs):
    """ Use web2py's intelligence to set up the right HTML for the select field
     the widgets knows about the database model """
    w = SQLFORM.widgets.options.widget
    xml = w(field,value,**kwargs)
    return xml

# there is no need to be so verbose. This works as well.
def options_widget(field,value,**kwargs):
    return SQLFORM.widgets.options.widget(field,value,**kwargs)

def string_widget(field,value,**kwargs):
    return SQLFORM.widgets.string.widget(field,value,**kwargs)

def boolean_widget(field,value,**kwargs):
#be careful using this; checkboxes on forms are tricky. see notes below.
    return SQLFORM.widgets.boolean.widget(field,value,**kwargs)

Checkboxes and booleans

Note that booleans require special treatment. The boolean widget provides a checkbox, but this causes a problem. HTML form post_vars don't include checkboxes which are empty. So you don't get a submission if the user changes a checkbox to unticked, which means representing checkboxes with a boolean widget won't work.

Notice how the code below sets up the boolean database field to use the options widget.
 

And now the controller function

# editable_grid is the controller function

#the data entry widgets are given HTML name attributes of the form fieldName_row_#id

e.g. if a field is called first_name, then the name attribute may be first_name_row_11

so '_row_' is a hard-coded string to separate table names and ids.

def editable_grid():
    #process submitted form
    if len(request.post_vars) > 0:
        for key, value in request.post_vars.iteritems():   
            (field_name,sep,row_id) = key.partition('_row_') #name looks like home_state_row_99
            if row_id:
                db(db.person.id == row_id).update(**{field_name:value})

    # the name attribute is the method we know which row is involved

    db.person.home_state.represent = lambda value,row:  options_widget(db.person.home_state,value,
                     **{'_name':'home_state_row_%s' % row.id})
    
    db.person.first_name.represent = lambda value,row:  string_widget(db.person.first_name,value,
                     **{'_name':'first_name_row_%s' % row.id})
    
    db.person.second_name.represent = lambda value,row:  string_widget(db.person.second_name,value,
                     **{'_name':'second_name_row_%s' % row.id})

    #look out for the special way we deal with booleans: we provide a requires
    db.person.is_happy.requires = IS_IN_SET(['True','False'])
    db.person.is_happy.represent = lambda value,row:  options_widget(db.person.is_happy,value,
                     **{'_name':'is_happy_row_%s' % row.id})

    grid = SQLFORM.grid(db.person,
                        selectable= lambda ids : redirect(URL('default','editable_grid',vars=request._get_vars)),
                        )  #preserving _get_vars means user goes back to same grid page, same sort options etc
    grid.elements(_type='checkbox',_name='records',replace=None)  #remove selectable's checkboxes
    return dict(grid=grid)

 

Some things to note: boolean widgets get a value of "on" when selected.

How this translates to your backend is possibly an interesting question

Related slices

Comments (1)

  • Login to post



  • 0
    steven-zhou-11231 4 years ago

    Wierd thing is on my web2py application, each time user refreshes page with SQLFORM.grid on, the columns get rehashed, i.e. the order of columns is different every time. not seeing any switch on SQLFORM.grid to turn this on and off, is this a bug on web2py?

     

    Many thanks for any advise.


Hosting graciously provided by:
Python Anywhere