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

*UPDATE* This module is no longer maintained. Please use SQLFORM.grid instead.

Add the webgrid.py module to your modules folder (download at bottom)

In your model:

webgrid = local_import('webgrid')

In your controller:

def index():
    grid = webgrid.WebGrid(crud)
    grid.datasource = db(db.things.id>0)
    grid.pagesize = 10
    return dict(grid=grid()) #notice the ()

The datasource can be a Set, Rows, Table, or list of Table. Joins are also supported.

grid.datasource = db(db.things.id>0) #Set
grid.datasource = db(db.things.id>0).select() #Rows
grid.datasource = db.things #Table
grid.datasource = [db.things,db.others] #list of Table
grid.datasource = db(db.things.id==db.others.thing)# join

The main row components of the WebGrid are header, filter, datarow, pager, page_total, footer

You can link to crud functions using action_links. Just tell it where crud is exposed:

grid.crud_function = 'data'

You can turn rows on and off:

grid.enabled_rows = ['header','filter', 'pager','totals','footer','add_links']

You can control the fields and field headers:

grid.fields = ['things.name','things.location','things.amount']
grid.field_headers = ['Name','Location','Amount']

You can control the action links (links to crud actions) and action headers:

grid.action_links = ['view','edit','delete']
grid.action_headers = ['view','edit','delete']

You will want to modify crud.settings.[action]_next so that it redirects to your WebGrid page after completing:

if request.controller == 'default' and request.function == 'data':
    if request.args:
        crud.settings[request.args(0)+'_next'] = URL(r=request,f='index')

You can get page totals for numeric fields:

grid.totals = ['things.amount']

You can set filters on columns:

grid.filters = ['things.name','things.created']

You can modify the Query that filters use (not available if your datasource is a Rows object, use rows.find):

grid.filter_query = lambda f,v: f==v

You can control which request vars are allowed to override the grid settings:

grid.allowed_vars = ['pagesize','pagenum','sortby','ascending','groupby','totals']

The WebGrid will use a field's represent function if present when rendering the cell. If you need more control, you can completely override the way a row is rendered.

The functions that render each row can be replaced with your own lambda or function:

grid.view_link = lambda row: ...
grid.edit_link = lambda row: ...
grid.delete_link = lambda row: ...
grid.header = lambda fields: ...
grid.datarow = lambda row: ...
grid.footer = lambda fields: ...
grid.pager = lambda pagecount: ...
grid.page_total = lambda:

Here are some useful variables for building your own rows:

grid.joined # tells you if your datasource is a join
grid.css_prefix # used for css
grid.response # the datasource result
grid.colnames # column names of datasource result
grid.total # the count of datasource result

For example, let's customize the footer:

grid.footer = lambda fields : TFOOT(TD("This is my footer" , 
                                               _class=grid.css_prefix + '-webgrid footer')

You can also customize messages:

grid.messages.confirm_delete = 'Are you sure?'
grid.messages.no_records = 'No records'
grid.messages.add_link = '[add %s]'
grid.messages.page_total = "Total:"

You can also also use the row_created event to modify the row when it is created. Let's add a column to the header:

def on_row_created(row,rowtype,record):
    if rowtype=='header':
        row.components.append(TH(' '))

grid.row_created = on_row_created

Let's move the action links to the right side:

def links_right(tablerow,rowtype,rowdata):
    if rowtype != 'pager':
        links = tablerow.components[:3]
        del tablerow.components[:3]

grid.row_created = links_right

alt text

If you are using multiple grids on the same page, they must have unique names.

Download webgrid.py

Download demo App

Related slices

Comments (112)

  • Login to post

  • 0
    hillmanov 8 years ago
    My table shows up on WebGrid, but the columns are much too wide to be useful. What is the best way to display 'thin' columns of integers. db.define_table('Commodity_Risk', Field('name','string'), Field('capacity','string'), Field('volatility','integer'), Field('bid_spread','integer'), Field('ask_spread','integer'), Field('low_limit','integer'), Field('high_limit','integer'), Field('volatility_surface','upload'), format = '%(name)s')

  • 0
    dymsza 9 years ago
    Tool is cool ;) but i have question is there any why to add owne action ? example: i have list of users and i want to have link to send email to user (next to edit link)

  • 0
    mrfreeze 9 years ago
    There are a couple ways. The easiest is to probably just modify the row when it's created:
    def add_mail_link(tablerow,rowtype,rowdata):
        if rowtype == 'datarow':
            mail_link = A(...)
            tablerow.components.insert(2, mail_link)
    grid.row_created = add_mail_link

  • 0
    iiit123 9 years ago
    will webgrid work for the following table too ? t=TABLE() t.append(TR(TD(value1),TD(value2),TD(value3),TD(value4))) ... ... ... grid.datasource=t thanks in advance...

  • 0
    mrfreeze 9 years ago
    The datasource must be a Set or Rows object like:
    db(db.things.id>0) #best way
    db(db.things.id>0).select() #slower way 

  • 0
    ammz 9 years ago
    Excelent Can you do a treeview for web2py ? best regards, António

  • 0
    mrfreeze 9 years ago
    @paulgerrard - If you can send me your model I can give you examples.

  • 0
    paulgerrard 9 years ago
    Hi again. Maybe being dim - but I'm not sure what your last comment means. Making filter_items_query the same as the datasource doesn't seem to have any effect on the values in the filter drop downs. Your comment "The filter_items_query isn't a subset of datasource." - that's what you are saying I guess. You've said the filter_query is for the results, not the values in the filter - so that's probably not a solution. So I'm thinking it is not possible to restrict the values in the filter drop downs to values that appear in the corresponding results column? Is that correct? The problem I have is one of my filters is names of people in the user's organisation, but this is a shared system with users from other organisations. The datasource only shows data from the current user's organisation so I need to limit the filter values to maintain confidentiality across organisations. If I can't limit the values, I can't use webgrid :O(

  • 0
    mrfreeze 9 years ago
    Fixed. Thanks!

  • 0
    toan75 9 years ago
    Thanks for this usefully module. In this code: grid.datasource = db(db.things.category==db.category.id) grid.fields = ['things.id','things.category','things.name', 'things.owner'] I can't change 'things.category' by 'category.name' ? (add other field of 'category' table) For many page, i had tiny changed: "for x in xrange(1, pagecount + 1)", instead: p1 = 1 if (self.pagenum<5) else self.pagenum-4 p2 = pagecount + 1 if (self.pagenum > pagecount -4) else self.pagenum + 6 if (p2<11) & (pagecount >10): p2 = 11 for x in xrange(p1, p2):

  • 0
    paulgerrard 9 years ago
    I have a complicated query as a data source and the filter drop downs contain items that aren't in the records displayed in the grid. I understand I should use the 'grid.filter_items_query' field and ave done - but it seems to make no difference. I get many more entries in the drop down than are in the grid. Here's the two lines of code that are relevant. Any ideas?
    grid.datasource = db((db.stories.priority_id==db.storypriority.id) & (db.stories.assigned_id==db.auth_user.id) & \
            (db.stories.status_id==db.storystatus.id) & (db.stories.app_id==db.applications.id) & \
        grid.filter_items_query = lambda field: (db.stories.app_id==db.applications.id) & (db.applications.org_id==session.org_id)

  • 0
    mrfreeze 9 years ago
    The filter_items_query isn't a subset of datasource. It's counter-intuitive but more flexible that way. You may need to modify the filter_query also.

  • 0
    ammz 9 years ago
    Now, it's works. Thank's a lot.

  • 0
    ammz 9 years ago
    Sometimes we have armazon1 or lente1 or lc4 But with this code: db.notas.vendedor.represent = lambda vendedor: auth_user.first_name db.notas.optometrista.represent = lambda optometrista: auth_user.first_name db.notas.armazon1.represent = lambda eyewear: [eyewear.marca,' ',eyewear.modelo,' ',eyewear.caract1] db.notas.lente1.represent = lambda lentes: [lentes.material,' ',lentes.tipo,' ',lentes.tecnoVisual,' ',lentes.tratamiento] db.notas.lc4.represent = lambda lc: [lc.tecnoGradua,' ',lc.marca,' ',lc.duracion,' ',lc.oftalmico,' ',lc.cosmetico] grid = webgrid.WebGrid(crud) grid.datasource = db((db.notas.entregado==False)&(db.notas.vendedor==db.auth_user.id)) grid.pagesize = 20 grid.enabled_rows = ['header','filter','pager','footer'] grid.fields = ['notas.nota','notas.armazon1','notas.lente1','notas.lc4','notas.total','notas.anticipo','notas.pagos','notas.saldo','notas.vendedor','notas.optometrista','notas.created_on'] grid.field_headers = ['Nota','Armazon1','Lente1','L/C1','Total','Antic.','Pagos','Saldo','Vendedor','Optomet.','Del'] grid.totals = ['notas.total','notas.anticipo','notas.pagos','notas.saldo'] grid.crud_function = 'data' grid.action_links = ['view'] grid.action_headers = ['Ver'] grid.filters = ['notas.nota'] grid.filter_items_query = lambda field: (db.notas.entregado==False)&(db.notas.vendedor==db.auth_user.id) grid.view_link= lambda row: A('view', _href= crud.url(f='show_nota', args=[row['id']])) grid.messages.page_info = 'Pagina %(pagenum)s de %(pagecount)s, existen un total de %(total)s registros' grid.messages.view_link = 'Ver' grid.messages.delete_link = 'Eliminar' grid.messages.edit_link = 'Editar' grid.messages.file_link = 'Archivo' grid.messages.previous_page = 'Anterior ' grid.messages.next_page = ' Siguiente' grid.messages.pagesize = ' Num. registros x pagina ' grid.messages.clear_filter = 'Limpiar filtros' grid.sortby = 'notas.nota' crud.settings.controller = 'ventas' I have this error: Traceback (most recent call last): File "/home/drayco/web2py/gluon/restricted.py", line 188, in restricted exec ccode in environment File "/home/drayco/web2py/applications/opticaluz/controllers/ventas.py", line 489, in File "/home/drayco/web2py/gluon/globals.py", line 96, in self._caller = lambda f: f() File "/home/drayco/web2py/gluon/tools.py", line 2277, in f return action(*a, **b) File "/home/drayco/web2py/applications/opticaluz/controllers/ventas.py", line 40, in leer_ventas return dict(gridnot=grid()) File "/home/drayco/web2py/applications/opticaluz/modules/webgrid.py", line 480, in __call__ r = field.represent(r) File "/home/drayco/web2py/applications/opticaluz/controllers/ventas.py", line 10, in db.notas.lc4.represent = lambda lc: [lc.tecnoGradua,' ',lc.marca,' ',lc.duracion,' ',lc.oftalmico,' ',lc.cosmetico] AttributeError: 'NoneType' object has no attribute 'tecnoGradua' Any advice? Or how can I avoid this error?

  • 0
    mrfreeze 9 years ago
    For some reason lc4 is null when it calls the represent function. I'm not sure if this is related to WebGrid. Can you try changing the represent for lc4 to this:
    db.notas.lc4.represent = lambda lc: [lc.tecnoGradua,' ',lc.marca,' ',lc.duracion,' ',lc.oftalmico,' ',lc.cosmetico] if lc else '' 
show more comments

Hosting graciously provided by:
Python Anywhere