The ``onvalidation`` argument can be ``None`` or can be a function that takes the form and returns nothing. Such a function would be called and passed the form, immediately after validation (if validation passes) and before anything else happens. This function has multiple purposes: for example, to perform additional checks on the form and eventually add errors to the form, or to compute the values of some fields based on the values of other fields, or to trigger some action (like sending an email) before a record is created/updated.
Here is an example:
``
db.define_table('numbers',
Field('a', 'integer'),
Field('b', 'integer'),
Field('c', 'integer', readable=False, writable=False))
def my_form_processing(form):
c = form.vars.a * form.vars.b
if c < 0:
form.errors.b = 'a*b cannot be negative'
else:
form.vars.c = c
def insert_numbers():
form = SQLFORM(db.numbers)
if form.process(onvalidation=my_form_processing).accepted:
session.flash = 'record inserted'
redirect(URL())
In the new controller, you do not need to build a ``FORM``, since the ``SQLFORM`
</tr>
<tr id="submit_record__row">
<td></td>
<td><input value="Submit" type="submit" /></td>
<td></td>
</tr>
</table>
<input value="9038845529" type="hidden" name="_formkey" />
<input value="person" type="hidden" name="_formname" />
</form>
``:code
The automatically generated form is more complex than the previous low-level form. First of all, it contains a table of rows, and each row has three columns. The first column contains the field labels (as determined from the ``db.person``), the second column contains the input fields (and eventually error messages), and the third column is optional and therefore empty (it can be populated with the fields in the ``SQLFORM`` constructor).
All tags in the form have names derived from the table and field name. This allows easy customization of the form using CSS and JavaScript. This capability is discussed in more detail in Chapter 11.
More important is that now the ``accepts`` method does a lot more work for you. As in the previous case, it performs validation of the input, but additionally, if the input passes validation, it also performs a database insert of the new record and stores in ``form.vars.id`` the unique "id" of the new record.
A ``SQLFORM`` object also deals automatically with "upload" fields by saving uploaded files in the "uploads" folder (after having them renamed safely to avoid conflicts and prevent directory traversal attacks) and stores their names (their new names) into the appropriate field in the database. After the form has been processed, the new filename is available in ``form.vars.fieldname`` (i.e., it replaces the ``cgi.FieldStorage`` object in ``request.vars.fieldname``), so you can easily reference the new name right after upload.
A ``SQLFORM`` displays "boolean" values with checkboxes, "text" values with textareas, values required to be in a definite set or a database with drop-downs, and "upload" fields with links that allow users to download the uploaded files. It hides "blob" fields, since they are supposed to be handled differently, as discussed later.
For example, consider the following model:
``
db.define_table('person',
Field('name', requires=IS_NOT_EMPTY()),
Field('married', 'boolean'),
Field('gender', requires=IS_IN_SET(['Male', 'Female', 'Other'])),
Field('profile', 'text'),
Field('image', 'upload'))
``:code
In this case, ``SQLFORM(db.person)`` generates the form shown below:
[[image @///image/en6200.png center 300px]]
The ``SQLFORM`` constructor allows various customizations, such as displaying only a subset of the fields, changing the labels, adding values to the optional third column, or creating UPDATE and DELETE forms, as opposed to INSERT forms like the current one.
``SQLFORM`` is the single biggest time-saver object in web2py.
The class ``SQLFORM`` is defined in "gluon/sqlhtml.py". It can be easily extended by overriding its ``xml`` method, the method that serializes the objects, to change its output.
col3 = {'name':A('what is this?',
For example, adding a URL-based back-button (for a multi-page form) and a renamed submit button:
``
buttons = [TAG.button('Back',_type="button",_onClick = "parent.location='%s' " % URL(...),
TAG.button('Next',_type="submit")]
``:code
- ``separator``:inxx ``separator`` sets the string that separates form labels from form input fields.
- Optional ``attributes`` are arguments starting with underscore that you want to pass to the ``FORM`` tag that renders the ``SQLFORM`` object. Examples are:
``
_action = '.'
_method = 'POST'
``:code
There is a special ``hidden`` attribute. When a dictionary is passed as ``hidden``, its items are translated into "hidden" INPUT fields (see the example for the ``FORM`` helper in Chapter 5).
``
form = SQLFORM(....,hidden=...)
``:code
causes the hidden fields to be passed with the submission, no more, no less.
``form.accepts(...)`` is not
intended to read the received hidden fields and move them into
form.vars. The reason is security. Hidden fields can be tampered with.
So you have to do explicitly move hidden fields from the request to the form:
``
form.vars.a = request.vars.a
form = SQLFORM(..., hidden=dict(a='b'))
``:code
#### ``SQLFORM`` and ``insert``/``update``/``delete``
``SQLFORM`` creates a new db record when the form is accepted. Assuming ``form=SQLFORM(db.test)``:code, then the id of the last-created record will be accessible in ``myform.vars.id``.
``delete record``:inxx
If you pass a record as the optional second argument to the ``SQLFORM`` constructor, the form becomes an UPDATE form for that record. This means that when the form is submitted the existing record is updated and no new record is inserted. If you set the argument ``deletable=True``, the UPDATE form displays a "check to delete" checkbox. If checked, the record is deleted.
------
If a form is submitted and the delete checkbox is checked the attribute
``form.deleted`` is set to ``True``.
------
You can modify the controller of the previous example so that when we pass an additional integer argument in the URL path, as in:
Normally uploaded files are stored into "app/uploads" but you can specify an alt
Field('image', 'upload', uploadfolder='...')
``
In most operating system, accessing the file system can become slow when there are many files in the same folder. If you plan to upload more than 1000 files you can ask web2py to organize the uploads in subfolders:
``
Field('image', 'upload', uploadseparate=True)
``
#### Storing the original filename
web2py automatically stores the original filename inside the new UUID filename and retrieves it when the file is downloaded. Upon download, the original filename is stored in the content-disposition header of the HTTP response. This is all done transparently without the need for programming.
Occasionally you may want to store the original filename in a database field. In this case, you need to modify the model and add a field to store it in:
``
db.define_table('person',
Field('name', requires=IS_NOT_EMPTY()),
Field('image_filename'),
Field('image', 'upload'))
``:code
+Then you need to modify the controller to handle it:
+
``
def display_form():
record = db.person(request.args(0)) or redirect(URL('index'))
url = URL('download')
form = SQLFORM(db.person, record, deletable=True,
upload=url, fields=['name', 'image'])
if request.vars.image!=None:
form.vars.image_filename = request.vars.image.filename
if form.process().accepted:
response.flash = 'form accepted'
elif form.errors:
response.flash = 'form has errors'
return dict(form=form)
``:code
Notice that the ``SQLFORM`` does not display the "image_filename" field.
The "display_form" action moves the filename of the ``request.vars.image``
into the ``form.vars.image_filename``, so that it gets processed by ``accepts`` and
stored in the database. The download function, before serving the file, checks in the database for the original filename and uses it in the content-disposition header.
form.process(detect_record_change=True)``
and it is set in:
``
crud.settings.detect_record_change = True
``:code
and it can be changed/disabled by setting the variable to ``False``.
You can change the form style by
``
crud.settings.formstyle = 'table3cols' or 'table2cols' or 'divs' or 'ul'
``:code
You can set the separator in all crud forms:
``
crud.settings.label_separator = ':'
``:code
+#### captcha
+
You can add captcha to forms, using the same convention explained for auth, with:
``
crud.settings.create_captcha = None
crud.settings.update_captcha = None
crud.settings.captcha = None
``:code
#### Messages
Here is a list of customizable messages:
``
crud.messages.submit_button = 'Submit'
``:code
sets the text of the "submit" button for both create and update forms.
``
crud.messages.delete_label = 'Check to delete:'
``:code
import datetime
now = datetime.datetime.today()
db.define_table('person',
Field('name'),
Field('registration_stamp', 'datetime', default=now))
recent = db(db.person.registration_stamp>now-datetime.timedelta(10))
db.person.name.requires = IS_NOT_IN_DB(recent, 'person.name')
``:code
##### ``IS_IN_DB``
``IS_IN_DB``:inxx
Consider the following tables and requirement:
``
db.define_table('person', Field('name', unique=True))
db.define_table('dog', Field('name'), Field('owner', db.person)
db.dog.owner.requires = IS_IN_DB(db, 'person.id', '%(name)s',
zero=T('choose one'))
``:code
It is enforced at the level of dog INSERT/UPDATE/DELETE forms. It requires that a ``dog.owner`` be a valid id in the field ``person.id`` in the database ``db``. Because of this validator, the ``dog.owner`` field is represented as a drop-down list. The third argument of the validator is a string that describes the elements in the drop-down list. In the example you want to see the person ``%(name)s`` instead of the person ``%(id)s``. ``%(...)s`` is replaced by the value of the field in brackets for each record.
The ``zero`` option works very much like for the ``IS_IN_SET`` validator.
The first argument of the validator can be a database connection or a DAL Set, as in ``IS_NOT_IN_DB``. This can be useful for example when wishing to limit the records in the drop-down list. In this example, we use ``IS_IN_DB`` in a controller to limit the records dynamically each time the controller is called:
``
def index():
(...)
query = (db.table.field == 'xyz') #in practice 'xyz' would be a variable
db.table.field.requires=IS_IN_DB(db(query),....)
form=SQLFORM(...)
if form.process().accepted: ...
(...)
``:code
If you want the field validated, but you do not want a drop-down, you must put the validator in a list.
``
db.dog.owner.requires = [IS_IN_DB(db, 'person.id', '%(name)s')]
``:code
``_and``:inxx
Occasionally you want the drop-down (so you do not want to use the list syntax above) yet you want to use additional validators. For this purpose the ``IS_IN_DB`` validator takes an extra argument ``_and`` that can point to a list of other validators applied if the validated value passes the ``IS_IN_DB`` validation. For example to validate all dog owners in db that are not in a subset:
``
subset=db(db.person.id>100)
db.dog.owner.requires = IS_IN_DB(db, 'person.id', '%(name)s',
_and=IS_NOT_IN_DB(subset,'person.id'))
``:code
``IS_IN_DB`` has a boolean ``distinct`` argument which defaults to ``False``. When set to ``True`` it prevents repeated values in the drop-down.
``IS_IN_DB`` also takes a ``cache`` argument that works like the ``cache`` argument of select.
##### ``IS_IN_DB`` and Tagging
``tags``:inxx
``multiple``:inxx
The ``IS_IN_DB`` validator has an optional attribute ``multiple=False``. If set to ``True`` multiple values can be stored in one field. This field should be of type ``list:reference`` as discussed in Chapter 6. An explicit example of tagging is discussed there. ``multiple`` references are handled automatically in create and update forms, but they are transparent to the DAL. We strongly suggest using the jQuery multiselect plugin to render multiple fields.
#### Custom validators
``custom validator``:inxx
All validators follow the prototype below:
``
class sample_validator:
def __init__(self, *a, error_message='error'):
self.a = a
self.e = error_message
def __call__(self, value):
The first case is easy:
db.define_table('category',Field('name'))
db.define_table('product',Field('name'),Field('category'))
db.product.category.widget = SQLFORM.widgets.autocomplete(
request, db.category.name, limitby=(0,10), min_length=2)
``:code
Where ``limitby`` instructs the widget to display no more than 10 suggestions at the time, and ``min_length`` instructs the widget to perform an Ajax callback to fetch suggestions only after the user has typed at least 2 characters in the search box.
The second case is more complex:
``
db.define_table('category',Field('name'))
db.define_table('product',Field('name'),Field('category'))
db.product.category.widget = SQLFORM.widgets.autocomplete(
request, db.category.name, id_field=db.category.id)
``:code
In this case the value of ``id_field`` tells the widget that even if the value to be autocompleted is a ``db.category.name``, the value to be stored is the corresponding ``db.category.id``. An optional parameter is ``orderby`` that instructs the widget on how to sort the suggestions (alphabetical by default).
This widget works via Ajax. Where is the Ajax callback? Some magic is going on in this widget. The callback is a method of the widget object itself. How is it exposed? In web2py any piece of code can generate a response by raising an HTTP exception. This widget exploits this possibility in the following way: the widget sends the Ajax call to the same URL that generated the widget in the first place and puts a special token in the request.vars. Should the widget get instantiated again, it finds the token and raises an HTTP exception that responds to the request. All of this is done under the hood and hidden to the developer.
## ``SQLFORM.grid`` and ``SQLFORM.smartgrid``
-------
Attention: grid and smartgrid were experimental prior web2py version 2.0 and were vulnerable to information leakage. The grid and smartgrid are no longer experimental, but we are still not promising backward compatibility of the presentation layer of the grid, only of its APIs.
-------
These are two high level objects that create complex CRUD controls. They provide pagination, the ability to browse, search, sort, create, update and delete records from a single object.
Because web2py's HTML objects build on the underlying, simpler objects, the grids create SQLFORMs for viewing, editing and creating its rows. Many of the arguments to the grids are passed through to this SQLFORM. This means the documentation for SQLFORM (and FORM) is relevant. For example, the grid takes an ``onvalidation`` callback. The processing logic of the grid ultimately passes this through to the underlying process() method of FORM, which means you should consult the documentation of ``onvalidation`` for FORMs.
As the grid passes through different states, such as editing a row, a new request is generated. request.args has information about which state the grid is in.
### ``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()
``:code
which produces the following page:
[[image @///image/en6700.png center 480px]]
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 URL 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()
``:code
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 different ``formname`` 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()
``:code
the ``args`` argument of the ``grid`` specifies which ``request.args`` should be passed along and ignored by the object. 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 object.
+#### 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,
ui = dict(
- ``exportclasses`` takes a dictionary of tuples: by default it's defined as
``
csv_with_hidden_cols=(ExporterCSV, 'CSV (hidden cols)'),
csv=(ExporterCSV, 'CSV'),
xml=(ExporterXML, 'XML'),
html=(ExporterHTML, 'HTML'),
tsv_with_hidden_cols=(ExporterTSV, 'TSV (Excel compatible, hidden cols)'),
tsv=(ExporterTSV, 'TSV (Excel compatible)'))
``:code
ExporterCSV, ExporterXML, ExporterHTML 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, while ``createargs``,``editargs`` and ``viewargs`` are passed only to the specific create, edit and details SQLFORMs
- ``formname``, ``ignore_rw`` and ``formstyle`` are passed to the SQLFORM objects used by the grid for create/update forms.
- ``buttons_placement`` and ``links_placement`` both take a parameter ('right', 'left', 'both') that will affect where on the row the buttons (or the links) will be placed
------
``deletable``, ``editable`` and ``details`` are usually boolean values but they can be functions which take the row object and decide whether to display the corresponding button or not.
-----
#### 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=lamba row:row.vfield),...] )
``:code
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 database 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('virtual1', lambda row: row.t1.field1 + row.t1.field2),
...)
``:code
+### 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'))
``:code
With SQLFORM.grid you can list all parents:
``
SQLFORM.grid(db.parent)
``:code
all children:
``
SQLFORM.grid(db.child)
``:code
and all parents and children in one table:
``
Also notice that when clicking on the "children" link for a given parent one onl
@auth.requires_login():
def manage():
db.child.parent.writable = False
grid = SQLFORM.smartgrid(db.parent,linked_tables=['child'])
return locals()
``:code
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()
``:code
+#### 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 a 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 a 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`` and ``user_signatures`` can be dictionaries as explained below.
Consider the previous grid:
``
grid = SQLFORM.smartgrid(db.parent,linked_tables=['child'])
``:code
It allows one to access both a ``db.parent`` and a ``db.child``. Apart for navigation controls, for each one table, a smarttable 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)
``:code
For a smartgrid we would pass a dictionary of booleans:
``
grid = SQLFORM.smartgrid(db.parent,linked_tables=['child'],
searchable= dict(parent=True, child=False))
``:code
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'))
``:code
or
``
grid = SQLFORM.grid(db.auth_user,
editable = auth.has_permission('edit','auth_user'),
deletable = auth.has_permission('delete','auth_user'))
``:code
+### smartgrid plurals
The ``smartgrid`` is the only object in web2py that displays the table name and it need 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 the explicitly: