CRUD 215 1 def data: return dict(form=crud()) would expose the following URLs: 1 http:// /[app]/[controller]/data/tables 2 http:// /[app]/[controller]/data/create/[tablename] 3 http:// /[app]/[controller]/data/read/[tablename]/[id] 4 http:// /[app]/[controller]/data/delete/[tablename] 5 http:// /[app]/[controller]/data/select/[tablename] However, the following action: 1 def create_tablename: 2 return dict(form=crud.create(db.tablename)) would only expose the create method 1 http:// /[app]/[controller]/create_tablename While the following action: 1 def update_tablename: 2 return dict(form=crud.update(db.tablename, request.args(0))) would only expose the update method 1 http:// /[app]/[controller]/update_tablename 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. Attributes Here is a complete list of current CRUD attributes, their default values, and meaning: 1 crud.settings.create_next = request.url specifies the URL to redirect to after a successful "create" record. 1 crud.settings.update_next = request.url specifies the URL to redirect to after a successful "update" record. 1 crud.settings.delete_next = request.url specifies the URL to redirect to after a successful "delete" record. 1 crud.settings.download_url = URL(r=request, f='download') specifies the URL to be used for linking uploaded files. 1 crud.settings.create_onvalidation = lambda form: None 216 FORMS AND VALIDATORS is an optional function to be called onvalidation of "create" forms (see SQL- FORM onvalidation) 1 crud.settings.update_onvalidation = lambda form: None is an optional function to be called onvalidation of "update" forms (see SQLFORM onvalidation) 1 crud.settings.create_onaccept = lambda form: None is an optional function to be called before redirect after successful "create" record. This function takes the form as its only argument. 1 crud.settings.update_onaccept = lambda form: None is an optional function to be called before redirect after successful "update" record. This function takes the form as its only argument. 1 crud.settings.update_ondelete = lambda form: None is an optional function to be called before redirect after successfully deleting a record using an "update" form. This function takes the form as its only argument. 1 crud.settings.delete_onaccept = lambda record: None is an optional function to be called before redirect after successfully deleting a record using the "delete" method. This function takes the form as its only argument. 1 crud.settings.update_deletable = True determines whether the "update" forms should have a "delete" button. 1 crud.settings.showid = False determines whether the "update" forms should show the id of the edited record. 1 crud.settings.keepvalues = False determines whether forms should keep the previously inserted values or reset to default after successful submission. Messages Here is a list of customizable messages: 1 crud.messages.submit_button = 'Submit' sets the text of the "submit" button for both create and update forms. 1 crud.messages.delete_label = 'Check to delete:' sets the label of the "delete" button in "update" forms. CRUD 217 1 crud.messages.record_created = 'Record Created' sets the flash message on successful record creation. 1 crud.messages.record_updated = 'Record Updated' sets the flash message on successful record update. 1 crud.messages.record_deleted = 'Record Deleted' sets the flash message on successful record deletion. 1 crud.messages.update_log = 'Record %(id)s updated' sets the log message on successful record update. 1 crud.messages.create_log = 'Record %(id)s created' sets the log message on successful record creation. 1 crud.messages.read_log = 'Record %(id)s read' sets the log message on successful record read access. 1 crud.messages.delete_log = 'Record %(id)s deleted' sets the log message on successful record deletion. Notice that crud.messages belongs to the class gluon.storage.Message which is similar to gluon.storage.Storage but it automatically translates its values, without need for the T operator. Log messages are used if and only if CRUD is connected to Auth as discussed in Chapter 8. The events are logged in the Auth table "auth events". Methods The behavior of CRUD methods can also be customized on a per call basis. Here are their signatures: 1 crud.tables() 2 crud.create(table, next, onvalidation, onaccept, log, message) 3 crud.read(table, record) 4 crud.update(table, record, next, onvalidation, onaccept, ondelete, log, message, deletable) 5 crud.delete(table, record_id, next, message) 6 crud.select(table, query, fields, orderby, limitby, headers, ** attr) • table is a DAL table or a tablename the method should act on. • record and record 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. 218 FORMS AND VALIDATORS • onvalidation has the same function as SQLFORM( , onvalidation) • onaccept is a function to be called after the form submission is accepted and acted upon, but before redirection. • log is the log message. Log messages in CRUD see variables in the form.vars dictionary such as "%(id)s". • message is the flash message upon form acceptance. • ondelete is called in place of onaccept 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 dis- played (see Chapter 6). • headers is a dictionary with the table header names. Here is an example of usage in a single controller function: 1 # assuming db.define_table('person', Field('name')) 2 def people(): 3 form = crud.create(db.person, next=request.url, 4 message=T("record created")) 5 persons = crud.select(db.person, fields=['name'], 6 headers={'person.name', 'Name'}) 7 return dict(form=form, persons=persons) 7.7 Custom form If a form is created with SQLFORM, SQLFORM.factory or CRUD, there are multiple ways it can be embedded in a view allowing multiple degrees of customization. Consider for example the following model: 1 db.define_table('image', 2 Field('name'), 3 Field('file', 'upload')) CUSTOM FORM 219 and upload action 1 def upload_image(): 2 return dict(form=crud.create(db.image)) The simplest way to embed the form in the view for upload image is 1 {{=form}} This results in a standard table layout. If you wish to use a different layout, you can break the form into components 1 {{=form.custom.begin}} 2 Image name: <div>{{=form.custom.widget.name}}</div> 3 Image file: <div>{{=form.custom.widget.file}}</div> 4 Click here to upload: {{=form.custom.submit}} 5 {{=form.custom.end}} Where form.custom.widget[fieldname] gets serialized into the proper wid- get for the field. If the form is submitted and it contains errors, they are appended below the widgets, as usual. The above sample form is show in the image below. If you do not wish to use the widgets serialized by web2py, you can replace them with HTML. There are some variables that will be useful for this: • form.custom.labels[fieldname] contains the label for the field. • form.custom.dspval[fieldname] form-typeandfield-typedependent dis- play representation of the field. • form.custom.inpval[fieldname] form-typeandfield-typedependent val- ues to be used in field code. It is important to follow the conventions described below. 220 FORMS AND VALIDATORS CSS Conventions Tags in forms generated by SQLFORM, SQLFORM.factory and CRUD fol- low a strict CSS naming convention that can be used to further customize the forms. Given a table "mytable", a field "myfield" of type "string", it is rendered by default by a 1 SQLFORM.widgets.string.widget that looks like this: 1 <input type="text" name="myfield" id="mytable_myfield" 2 class="string" /> Notice that: • the class of the INPUT tag is the same as the type of the field. This is very important for the jQuery code in "web2py ajax.html" to work. It makes sure that you can only have numbers in "integer" and "double" fields, and that "time", "date" and "datetime" fields display the popup calendar. • the id is the name of the class plus the name of the field, joined by one underscore. This allows you to uniquely refer to the field via jQuery(’#mytable myfield’) andmanipulate,forexample,the stylesheet of the field or bind actions associated to the field events (focus, blur, keyup, etc.). • the name is, as you would expect, the field name. Switch off errors Occasionally, you may want to disable the automatic error placement and display form error messages in some place other than the default. That can be done in two steps: • display the error messages where desired • form.error.clear() before the form is rendered so that error messages are not displayed in the default locations. Here is an example where the errors are displayed above the form and not in the form. 1 {{if form.errors:}} 2 Your submitted form contains the following errors: CUSTOM FORM 221 3 <ul> 4 {{for fieldname in form.errors:}} 5 <li>{{=fieldname}} error: {{=form.errors[fieldname]}}</li> 6 {{pass}} 7 </ul> 8 {{form.errors.clear()}} 9 {{pass}} 10 {{=form}} The errors will displayed as in the image shown below. . The permissions to performcertainoperationsareassigned tospecific roles. Mem- bers of staff (or other system users) are assigned particular roles, and through WEB2 PY: Enterprise Web Framework / 2nd. for this: • form.custom.labels[fieldname] contains the label for the field. • form.custom.dspval[fieldname] form-typeandfield-typedependent dis- play representation of the field. • form.custom.inpval[fieldname]. multiple degrees of customization. Consider for example the following model: 1 db.define_table('image', 2 Field('name'), 3 Field('file', 'upload')) CUSTOM