SQLFORM.grid
The simplest of the two is SQLFORM.grid
. Here is an example of usage:
@auth.requires_login()
def manage_users():
grid = SQLFORM.grid(db.auth_user)
return locals()
which produces the following page:
The first argument of SQLFORM.grid
can be a table or a query. The grid object will provide access to records matching the query.
Before we dive into the long list of arguments of the grid object we need to understand how it works. The object looks at request.args
in order to decide what to do (browse, search, create, update, delete, etc.). Each button created by the object links the same function (manage_users
in the above case) but passes different request.args
.
login required by default for data updates
By default all the URLs generated by the grid are digitally signed and verified. This means one cannot perform certain actions (create, update, delete) without being logged-in. These restrictions can be relaxed:
def manage_users():
grid = SQLFORM.grid(db.auth_user, user_signature=False)
return locals()
but we do not recommend it.
Multiple grids per controller function
Because of the way grid works one can only have one grid per controller function, unless they are embedded as components via
LOAD
. To make the default search grid work in more than one LOADed grid, please use a differentformname
for each one.
Using requests.args safely
Because the controller function that contains the grid may itself manipulate the URL arguments (known in web2py as response.args and response.vars), the grid needs to know which args should be handled by the grid and which not. Here is an example of code that allows one to manage any table:
@auth.requires_login()
def manage():
table = request.args(0)
if not table in db.tables(): redirect(URL('error'))
grid = SQLFORM.grid(db[table], args=request.args[:1])
return locals()
the args
argument of the grid
specifies which request.args
should be passed along and ignored by the grid
. In our case request.args[:1]
is the name of the table we want to manage and it is handled by the manage
function itself, not by the grid
. So, args=request.args[:1]
tells the grid to preserve the first URL argument in any links that it generates, appending any grid-specific arguments after that first argument.
SQLFORM.grid signature
The complete signature for the grid is the following:
SQLFORM.grid(
query,
fields=None,
field_id=None,
left=None,
headers={},
orderby=None,
groupby=None,
searchable=True,
sortable=True,
paginate=20,
deletable=True,
editable=True,
details=True,
selectable=None,
create=True,
csv=True,
links=None,
links_in_grid=True,
upload='<default>',
args=[],
user_signature=True,
maxtextlengths={},
maxtextlength=20,
onvalidation=None,
onfailure=None,
oncreate=None,
onupdate=None,
ondelete=None,
sorter_icons=(XML('▲'), XML('▼')),
ui = 'web2py',
showbuttontext=True,
_class="web2py_grid",
formname='web2py_grid',
search_widget='default',
advanced_search=True,
ignore_rw = False,
formstyle = None,
exportclasses = None,
formargs={},
createargs={},
editargs={},
viewargs={},
selectable_submit_button='Submit',
buttons_placement = 'right',
links_placement = 'right',
noconfirm=False,
cache_count=None,
client_side_delete=False,
ignore_common_filters=None,
auto_pagination=True,
use_cursor=False,
represent_none=None,
showblobs=False
)
fields
is a list of fields to be fetched from the database. It is also used to determine which fields to be shown in the grid view. However, it doesn’t control what is displayed in the separate form used to edit rows. For that, use the readable and writable attribute of the database fields. For example, in a editable grid, suppress updating of a field like this: before creating the SQLFORM.grid, setdb.my_table.a_field.writable = False
db.my_table.a_field.readable = False
field_id
must be the field of the table to be used as ID, for exampledb.mytable.id
. This is useful when the grid query is a join of several tables. Any action button on the grid (add record, view, edit, delete) will work over db.mytable.left
is an optional left join expressions used to build...select(left=...)
.headers
is a dictionary that maps ‘tablename.fieldname’ into the corresponding header label, e.g.{'auth_user.email' : 'Email Address'}
orderby
is used as default ordering for the rows. See Chapter 6 (multiple fields are possible).groupby
is used to group the set. Use the same syntax as you were passing in a simpleselect(groupby=...)
.searchable
,sortable
,deletable
,editable
,details
,create
determine whether one can search, sort, delete, edit, view details, and create new records respectively.deletable
,editable
anddetails
are usually boolean values but they can be functions which take the row object and decide whether to display the corresponding button or not.selectable
can be used to call a custom function on multiple records (a checkbox will be inserted for every row) e.g.selectable = lambda ids : redirect(URL('default', 'mapping_multiple', vars=dict(id=ids)))
a submit button will be added in the checkboxes column with the label passed in
selectable_submit_button
; or for multiple action buttons, use a list of tuples:selectable = [('button label1', lambda...), ('button label2', lambda ...)]
paginate
sets the max number of rows per page.csv
if set to True allows to download the grid in various format (see alsoexportclasses
below).links
is used to display new columns which can be links to other pages. Thelinks
argument must be a list ofdict(header='name', body=lambda row: A(...))
whereheader
is the header of the new column andbody
is a function that takes a row and returns a value. In the example, the value is aA(...)
helper.links_in_grid
if set to False, links will only be displayed in the “details” and “edit” page (so, not on the main grid)upload
same as SQLFORM’s one. web2py uses the action at that URL to download the filemaxtextlength
sets the maximum length of text to be displayed for each field value, in the grid view. This value can be overwritten for each field usingmaxtextlengths
, a dictionary of ‘tablename.fieldname’:length e.g.{'auth_user.email' : 50}
onvalidation
,onfailure
,oncreate
,onupdate
andondelete
are callback functions. All butondelete
take a form object as input, ondelete takes the table and the record id. Because the edit/create form is an SQLFORM which extends FORM, these callbacks are essentially used in the same way as documented in the sections for FORM and SQLFORM. Here is skeleton code:def myonvalidation(form):
print "In onvalidation callback"
print form.vars
form.errors= True #this prevents the submission from completing
#...or to add messages to specific elements on the form
form.errors.first_name = "Do not name your child after prominent deities"
form.errors.last_name = "Last names must start with a letter"
response.flash = "I don't like your submission"
def myonfailure(form):
print 'edit/create validation problem'
print form.errors
def myoncreate(form):
print 'create!'
print form.vars
def myonupdate(form):
print 'update!'
print form.vars
def myondelete(table, id):
print 'delete!'
print table, id
onupdate
andoncreate
callbacks are passed toSQLFORM.process(onsuccess=...)
.sorter_icons
is a list of two strings (or helpers) that will be used to represent the up and down sorting options for each field.ui
can be set equal to'web2py'
(default) and will generate web2py friendly class names, can be set equal to'jquery-ui'
and will generate jquery UI friendly class names, but it can also be its own set of class names for the various grid components:ui = dict(
widget='',
header='',
content='',
default='',
cornerall='',
cornertop='',
cornerbottom='',
button='button',
buttontext='buttontext button',
buttonadd='icon plus',
buttonback='icon leftarrow',
buttonexport='icon downarrow',
buttondelete='icon trash',
buttonedit='icon pen',
buttontable='icon rightarrow',
buttonview='icon magnifier')
search_widget
allows to override the default search widget and we refer the reader the source code in gluon/sqlhtml.py for details.advanced_search
if set to False, advanced search capabilities are disabled on search widgetshowbuttontext
allows buttons without text (there will effectively be only icons)_class
is the class for the grid container.exportclasses
takes a dictionary of tuples: by default it’s defined asdict(
csv_with_hidden_cols=(ExporterCSV_hidden, 'CSV (hidden cols)', T(...)),
csv=(ExporterCSV, 'CSV', T(...)),
xml=(ExporterXML, 'XML', T('XML export of columns shown')),
html=(ExporterHTML, 'HTML', T('HTML export of visible columns')),
json=(ExporterJSON, 'JSON', T('JSON export of visible columns')),
tsv_with_hidden_cols=
(ExporterTSV, 'TSV (Spreadsheets, hidden cols)', T(...)),
tsv=(ExporterTSV, 'TSV (Spreadsheets)', T(...)))
ExporterCSV_hidden, ExporterCSV, ExporterXML, ExporterHTML, ExporterJSON and ExporterTSV are all defined in gluon/sqlhtml.py. Take a look at those for creating your own exporter. If you pass a dict like
dict(xml=False, html=False)
you will disable the xml and html export formats.formargs
is passed to all SQLFORM objects used by the grid, whilecreateargs
,editargs
andviewargs
are passed only to the specific create, edit and details SQLFORMsformname
,ignore_rw
andformstyle
are passed to the SQLFORM objects used by the grid for create/update forms.buttons_placement
andlinks_placement
both take a parameter (‘right’, ‘left’, ‘both’) that will affect where on the row the buttons (or the links) will be placednoconfirm
if set to True do not requires user confirmation upon record delete.cache_count
is used to speed up the grid rows counting, it works either caching the selecting query (in this case you pass a tuple as for thecache
argument ofselect
, see Chapter 6), or directly providing the number of rows (i.e. an integer or a callable returning the integer count).client_side_delete
if set to True requires that the grid gets reloaded on delete on Ajax (i.e. the value ofclient_side_delete
is passed toclient_side
argument ofredirect
, see also Chapter 4).ignore_common_filters
can be set to True for common filters suppression upon grid db set definition (i.e. while evaluating query). Common filters are described in Chapter 6.auto_pagination
if set to True enforce always an ordering over records to avoid pagination issues.use_cursor
is specific to Google NoSQL (Datastore) only, if set to True allows use of cursor for pagination.represent_none
is an optional value to be used instead of None when showing records in grid/details view.showblobs
if set to True tell the grid to show fields ofblob
type, which by default are not shown.
Virtual fields in SQLFORM.grid and smartgrid
In versions of web2py after 2.6, virtual fields are shown in grids like normal fields: either shown alongside all other fields by default, or by including them in the fields
argument. However, virtual fields are not sortable.
In older web2py versions, showing virtual fields in a grid requires use of the links
argument. This is still supported for more recent versions. If table db.t1 has a field called t1.vfield which is based on the values of t1.field1 and t1.field2, do this:
grid = SQLFORM.grid(db.t1, ..., fields = [t1.field1, t1.field2, ...],
links = [dict(header='Virtual Field 1', body=lambda row:row.vfield), ...] )
In all cases, because t1.vfield depends on t1.field1 and t1.field2, these fields must be present in the row. In the example above, this is guaranteed by including t1.field1 and t1.field2 in the fields
argument. Alternatively, showing all fields will also work. You can suppress a field from displaying by setting the readable
attribute to False.
Note that when defining the virtual field, the lambda function must qualify fields with the table name, but in the links argument, this is not necessary. So for the example above, the virtual field may be defined like:
db.define_table('t1', Field('field1', 'string'),
Field('field2', 'string'),
Field.Virtual('vfield', lambda row: row.t1.field1 + row.t1.field2),
...)
SQLFORM.smartgrid
A SQLFORM.smartgrid
looks a lot like a grid
, in fact it contains a grid but it is designed to take as input not a query but only one table and to browse said table and selected referencing tables.
For example consider the following table structure:
db.define_table('parent', Field('name'))
db.define_table('child', Field('name'), Field('parent', 'reference parent'))
With SQLFORM.grid you can list all parents:
SQLFORM.grid(db.parent)
all children:
SQLFORM.grid(db.child)
and all parents and children in one table:
SQLFORM.grid(db.parent, left=db.child.on(db.child.parent==db.parent.id))
With SQLFORM.smartgrid you can put all the data in one object that spawns both tables:
@auth.requires_login()
def manage():
grid = SQLFORM.smartgrid(db.parent, linked_tables=['child'])
return locals()
which looks like this:
Notice the extra “children” links. One could create the extra links
using a regular grid
but they would point to a different action. With a smartgrid
they are created automatically and handled by the same object.
Also notice that when clicking on the “children” link for a given parent one only gets the list of children for that parent (and that is obvious) but also notice that if one now tried to add a new child, the parent value for the new child is automatically set to the selected parent (displayed in the breadcrumbs associated to the object). The value of this field can be overwritten. We can prevent this by making it readonly:
@auth.requires_login()
def manage():
db.child.parent.writable = False
grid = SQLFORM.smartgrid(db.parent, linked_tables=['child'])
return locals()
If the linked_tables
argument is not specified all referencing tables are automatically linked. Anyway, to avoid accidentally exposing data we recommend explicitly listing tables that should be linked.
The following code creates a very powerful management interface for all tables in the system:
@auth.requires_membership('managers')
def manage():
table = request.args(0) or 'auth_user'
if not table in db.tables(): redirect(URL('error'))
grid = SQLFORM.smartgrid(db[table], args=request.args[:1])
return locals()
smartgrid signature
The smartgrid
takes the same arguments as a grid
and some more with some caveats:
- The first argument is a table, not a query
- There is an extra argument
constraints
which is a dictionary of ‘tablename’:query which can be used to further restrict access to the records displayed in the ‘tablename’ grid. - There is an extra argument
linked_tables
which is a list of tablenames of tables that should be accessible via the smartgrid. divider
allows to specify a character to use in the breadcrumb navigator,breadcrumbs_class
will apply the class to the breadcrumb element- All the arguments but the table,
args
,linked_tables
,links_in_grid
, anduser_signature
can be dictionaries as explained below.
Consider the previous grid:
grid = SQLFORM.smartgrid(db.parent, linked_tables=['child'])
It allows one to access both a db.parent
and a db.child
. Apart for navigation controls, for each one table, a smartgrid is nothing but a grid. This means that, in this case, one smartgrid can create a grid for parent and one grid for child. We may want to pass different sets of parameters to these grids. For example different sets of searchable
parameters.
While for a grid we would pass a boolean:
grid = SQLFORM.grid(db.parent, searchable=True)
For a smartgrid we would pass a dictionary of booleans:
grid = SQLFORM.smartgrid(db.parent, linked_tables=['child'],
searchable= dict(parent=True, child=False))
In this way we made parents searchable but children for each parent not searchable (there should not be that many to need the search widget).
grid and smartgrid access control
grid
and smartgrid
do not automatically enforce access control like crud does but you can integrate it with auth
using explicit permission checking:
grid = SQLFORM.grid(db.auth_user,
editable = auth.has_membership('managers'),
deletable = auth.has_membership('managers'))
or
grid = SQLFORM.grid(db.auth_user,
editable = auth.has_permission('edit', 'auth_user'),
deletable = auth.has_permission('delete', 'auth_user'))
smartgrid plurals
The smartgrid
is the only object in web2py that displays the table name and it needs both the singular and the plural. For example one parent can have one “Child” or many “Children”. Therefore a table object needs to know its own singular and plural names. web2py normally guesses them but you can set them explicitly:
db.define_table('child', ..., singular="Child", plural="Children")
or with:
db.define_table('child', ...)
db.child._singular = "Child"
db.child._plural = "Children"
They should also be internationalized using the T
operator.
The plural and singular values are then used by smartgrid
to provide correct names for headers and links.