CRUD
The Create/Read/Update/Delete (CRUD) API is an experimental interface on top of SQLFORM. It is now deprecated in favor of SQLFORM.grid() and SQLFORM.smartgrid(), but is described here because some applications have been built with it.
CRUD creates an SQLFORM, but it simplifies the coding because it incorporates the creation of the form, the processing of the form, the notification, and the redirection, all in one single function. The first thing to notice is that CRUD differs from the other web2py APIs we have used so far because it is not already exposed. It must be imported. It also must be linked to a specific database. For example:
from gluon.tools import Crud
crud = Crud(db)
The crud
object defined above provides the following API:
.
crud.tables()
returns a list of tables defined in the database.crud.create(db.tablename)
returns a create form for table tablename.crud.read(db.tablename, id)
returns a readonly form for tablename and record id.crud.update(db.tablename, id)
returns an update form for tablename and record id.crud.delete(db.tablename, id)
deletes the record.crud.select(db.tablename, query)
returns a list of records selected from the table.crud.search(db.tablename)
returns a tuple (form, records) where form is a search form and records is a list of records based on the submitted search form.crud()
returns one of the above based on therequest.args()
.
For example, the following action:
def data(): return dict(form=crud())
would expose the following URLs:
http://.../[app]/[controller]/data/tables
http://.../[app]/[controller]/data/create/[tablename]
http://.../[app]/[controller]/data/read/[tablename]/[id]
http://.../[app]/[controller]/data/update/[tablename]/[id]
http://.../[app]/[controller]/data/delete/[tablename]/[id]
http://.../[app]/[controller]/data/select/[tablename]
http://.../[app]/[controller]/data/search/[tablename]
However, the following action:
def create_tablename():
return dict(form=crud.create(db.tablename))
would only expose the create method
http://.../[app]/[controller]/create_tablename
While the following action:
def update_tablename():
return dict(form=crud.update(db.tablename, request.args(0)))
would only expose the update method
http://.../[app]/[controller]/update_tablename/[id]
and so on.
The behavior of CRUD can be customized in two ways: by setting some attributes of the crud
object or by passing extra parameters to each of its methods.
Settings
Here is a complete list of current CRUD attributes, their default values, and meaning:
To enforce authentication on all crud forms:
crud.settings.auth = auth
This is explained in Chapter 9.
To specify the controller that defines the data
function which returns the crud
object
crud.settings.controller = 'default'
To specify the URL to redirect to after a successful “create” record:
crud.settings.create_next = URL('index')
To specify the URL to redirect to after a successful “update” record:
crud.settings.update_next = URL('index')
To specify the URL to redirect to after a successful “delete” record:
crud.settings.delete_next = URL('index')
To specify the URL to be used for linking uploaded files:
crud.settings.download_url = URL('download')
To specify extra functions to be executed after standard validation procedures for crud.create
forms:
crud.settings.create_onvalidation = StorageList()
StorageList
is the same as a Storage
object, they are both defined in the file “gluon/storage.py”, but it defaults to []
as opposed to None
. It allows the following syntax:
crud.settings.create_onvalidation.mytablename.append(lambda form: ...)
To specify extra functions to be executed after standard validation procedures for crud.update
forms:
crud.settings.update_onvalidation = StorageList()
To specify extra functions to be executed after completion of crud.create
forms:
crud.settings.create_onaccept = StorageList()
To specify extra functions to be executed after completion of crud.update
forms:
crud.settings.update_onaccept = StorageList()
To specify extra functions to be executed after completion of crud.update
if record is deleted:
crud.settings.update_ondelete = StorageList()
To specify extra functions to be executed after completion of crud.delete
:
crud.settings.delete_onaccept = StorageList()
To determine whether the “update” forms should have a “delete” button:
crud.settings.update_deletable = True
To determine whether the “update” forms should show the id of the edited record:
crud.settings.showid = False
To determine whether forms should keep the previously inserted values or reset to default after successful submission:
crud.settings.keepvalues = False
Crud always detects whether a record being edited has been modified by a third party in the time between the time when the form is displayed and the time when it is submitted. This behavior is equivalent to
form.process(detect_record_change=True)
and it is set in:
crud.settings.detect_record_change = True
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'
You can set the separator in all crud forms:
crud.settings.label_separator = ':'
captcha
You can add captcha to forms, using the same convention explained for auth.settings
(see Chapter 9), with:
crud.settings.create_captcha = None
crud.settings.update_captcha = None
crud.settings.captcha = None
Messages
Here is a list of customizable messages:
crud.messages.submit_button = 'Submit'
sets the text of the “submit” button for both create and update forms.
crud.messages.delete_label = 'Check to delete'
sets the label of the “delete” checkbox in “update” forms.
crud.messages.record_created = 'Record Created'
sets the flash message on successful record creation.
crud.messages.record_updated = 'Record Updated'
sets the flash message on successful record update.
crud.messages.record_deleted = 'Record Deleted'
sets the flash message on successful record deletion.
crud.messages.update_log = 'Record %(id)s updated'
sets the log message on successful record update.
crud.messages.create_log = 'Record %(id)s created'
sets the log message on successful record creation.
crud.messages.read_log = 'Record %(id)s read'
sets the log message on successful record read access.
crud.messages.delete_log = 'Record %(id)s deleted'
sets the log message on successful record deletion.
Notice that
crud.messages
belongs to the classgluon.storage.Message
which is similar togluon.storage.Storage
but it automatically translates its values, without need for theT
operator.
Log messages are used if and only if CRUD is connected to Auth (as discussed in Chapter 9) and CRUD logger is attached too:
crud.settings.logger = crud.settings.auth = auth
The events are logged in the Auth table auth_event
.
Methods
The behavior of CRUD methods can also be customized on a per call basis. Here are their signatures:
crud.tables()
crud.create(table, next, onvalidation, onaccept, log, message, formname, **attributes)
crud.read(table, record)
crud.update(table, record, next, onvalidation, onaccept, ondelete, log, message, deletable)
crud.delete(table, record_id, next, message)
crud.select(table, query, fields, orderby, limitby, headers, **attr)
crud.search(table, query, queries, query_labels, fields, field_labels, zero, showall, chkall)
table
is a DAL table or a tablename the method should act on.record
andrecord_id
are the id of the record the method should act on.next
is the URL to redirect to after success. If the URL contains the substring “[id]“ this will be replaced by the id of the record currently created/updated (useURL(..., url_encode=False)
to prevent the brackets from being escaped).onvalidation
has the same function as in FORM and SQLFORM (see onvalidation section)onaccept
is a function to be called after the form submission is accepted and acted upon, but before redirection. This will be called with the form passed as argument.log
is the log message. Log messages in CRUD see variables in theform.vars
dictionary such as “%(id)s” (the message actually logged islog % form.vars
).message
is the flash message upon form acceptance.formname
is the name to be used for the genarated SQLFORM, this may be needed when you want multiple form on the same page (see Multiple forms per page section for additional details).**attributes
additionalcrud.create
andcrud.update
keyword arguments to be passed to theSQLFORM
constructor.ondelete
is called in place ofonaccept
when a record is deleted via an “update” form.deletable
determines whether the “update” form should have a delete option.query
is the query to be used to select records.fields
is a list of fields to be selected.orderby
determines the order in which records should be selected (see Chapter 6).limitby
determines the range of selected records that should be displayed (see Chapter 6).headers
is a dictionary mapping field names into header names to be passed to theSQLTABLE
constructor (see Chapter 6).queries
a list like['equals', 'not equal', 'contains']
containing the allowed methods in the search form.query_labels
a dictionary likequery_labels=dict(equals='Equals')
giving names to search methods.fields
a list of fields to be listed in the search widget.field_labels
a dictionary mapping field names into labels.zero
defaults to “choose one” is used as default option for the drop-down in the search widget.showall
set it to True if you want rows returned as per the query in the first call (added after 1.98.2).chkall
set it to True to turn on all the checkboxes in the search form (added after 1.98.2).**attr
additionalcrud.select
keyword arguments to be passed to theSQLTABLE
constructor (see Chapter 6).
Here is an example of usage in a single controller function:
## assuming db.define_table('person', Field('name'))
def people():
form = crud.create(db.person, next=URL('index'),
message=T("record created"))
persons = crud.select(db.person, fields=['name'],
headers={'person.name': 'Name'})
return dict(form=form, persons=persons)
Here is another very generic controller function that lets you search, create and edit any records from any table where the tablename is passed in request.args(0):
def manage():
table = db[request.args(0)]
form = crud.update(table, request.args(1))
table.id.represent = lambda id, row: \
A('edit:', id, _href=URL(args=(request.args(0), id)))
search, rows = crud.search(table)
return dict(form=form, search=search, rows=rows)
Notice the line table.id.represent=...
that tells web2py to change the representation of the id field and display a link instead to the page itself and passes the id as request.args(1) which turns the create page into an update page.
Record versioning
Both SQLFORM and CRUD provides a utility to version database records:
If you have a table (db.mytable) that needs full revision history you can just do:
form = SQLFORM(db.mytable, myrecord).process(onsuccess=auth.archive)
form = crud.update(db.mytable, myrecord, onaccept=auth.archive)
auth.archive
defines a new table called db.mytable_archive (the name is derived from the name of the table to which it refers) and on updating, it stores a copy of the record (as it was before the update) in the created archive table, including a reference to the current record.
Because the record is actually updated (only its previous state is archived), references are never broken.
This is all done under the hood. Should you wish to access the archive table you should define it in a model:
db.define_table('mytable_archive',
Field('current_record', 'reference mytable'),
db.mytable)
Notice the table extends db.mytable
(including all its fields), and adds a reference to the current_record
.
auth.archive
does not timestamp the stored record unless your original table has timestamp fields, for example:
db.define_table('mytable',
Field('created_on', 'datetime',
default=request.now, update=request.now, writable=False),
Field('created_by', 'reference auth_user',
default=auth.user_id, update=auth.user_id, writable=False),
There is nothing special about these fields and you may give them any name you like. They are filled before the record is archived and are archived with each copy of the record. The archive table name and/or reference field name can be changed like this:
db.define_table('myhistory',
Field('parent_record', 'reference mytable'),
db.mytable)
## ...
form = SQLFORM(db.mytable, myrecord)
form.process(onsuccess = lambda form:auth.archive(form,
archive_table=db.myhistory,
current_record='parent_record'))