|Version 32 (modified by 12 years ago) ( diff ),|
Available since: Main Trunk Revision 358+
S3REST provides a generic RESTful API (Representational State Transfer) for SahanaPy database resources, i.e. it can map HTTP requests to data resources and function calls. In particular, it maps resource addresses (=URL's) and HTTP methods (=GET, PUT, DELETE etc.) to CRUD actions, and then invokes so called method handlers (provided by the controller) to execute those actions.
The so called REST Controller (function shn_rest_controller()) is wrapper function for S3REST, providing generic CRUD method handlers. These method handlers as well as the REST controller itself are implemented in models/01_crud.py.
The method handlers support a number of representation formats:
- HTML (including autogenerated Create/Update forms)
- PDF (export only)
- XLS (export only)
- XML using in-line XSLT transformation to support various XML formats
- JSON as light-weight alternative for AJAX-style resource access
Resources are data objects of the application, which can have attributes and methods. Attribute values are stored in the data store of the application (usually the local database), while methods are provided by the controller function of the particular resource.
Attributes can be simple (=atomic types) or complex (=object types), e.g. in a "person" resource the "first_name" attribute is of an atomic type (=string), while the "address" attribute is of an object type (=pr_address). Complex attributes in S3 are also called Components.
Components are stored in separate database tables which reference the primary resource record.
Resources as well as their components and methods can be addressed by URL's.
URL Argument Lists
General URL format:
The argument list is interpreted as follows:
- empty argument list = access to all/any records of the resource
- <id> = access to record #<id> of the resource
- <method> = method access to all/any records of the resource
- <method>/<id> = method access to the record #<id> of the resource
- <id>/<component> = access to all/any component records of the record #<id> of the resource
- <id>/<component>/<method> = method access to all/any component records for the record #<id> of the resource
If <id> is omitted from the URL while a <method> or a <component> is specified, then the last used <id> for this resource will be recalled. This feature can be used for cascading or returning UI actions. The <id> memory can be explicitly cleared by sending a request to the primary resource with clear as method. At login/logout as well as after a delete action, the <id> memory is automatically cleared, of course.
You may pass a record ID for the component at the end of the arguments list to access a particular component record - which would produce an error message if these two records do not belong together. Component record ID's are not remembered.
The data format of the request can be passed:
- as extension to the resource, e.g. http://localhost:8000/sahana/pr/person.xml/1
- as extension to any of the arguments, e.g. http://localhost:8000/sahana/pr/person/1/address/create.xml
- as explicit variable "format", e.g. http://localhost:8000/sahana/pr/person/1/address?format=xml
If multiple extensions are specified, the rightmost extension applies. Where ?format= is specified, it overrides any extensions.
Example for definition of a component table in the model:
resource = 'image' table = module + '_' + resource db.define_table(table, timestamp, uuidstamp, deletion_status, pr_pe_id, opt_pr_image_type, Field('title'), Field('image', 'upload', autodelete=True), Field('description'), Field('comment'), migrate=migrate) # Component s3xrc.model.add_component(module, resource, multiple=True, joinby='pr_pe_id', deletable=True, editable=True, list_fields = ['id', 'opt_pr_image_type', 'image', 'title', 'description'])
- module is the prefix of the module in which the component is defined
- resource is the name of the component (without the module prefix)
- multiple indicates whether this is a N:1 (True) or 1:1 (False) join, defaults to True
- joinby describes the join keys:
- pass a single field name for natural joins (same key field in both tables)
- pass a dictionary of tablename=fieldname pairs for primary/foreign key matching, in which:
- tablename is the name of the respective primary table
- fieldname the name of the foreign key in the component table that points to the id field in the primary table
joinby = dict(pr_person='person_id')
- fields is a list of the fields in the component that shall appear in list views:
- if omitted or set to None, all readable fields will be included
No definitions are required at the primary resource, just define the table as usual.
Define a controller action per resource to invoke the controller:
def person(): crud.settings.delete_onvalidation = shn_pentity_ondelete return shn_rest_controller(module, 'person', main='first_name', extra='last_name', pheader=shn_pr_pheader, onvalidation=lambda form: shn_pentity_onvalidation(form, table='pr_person', entity_class=1), onaccept=None)
The optional pheader argument helps you to display some information about the primary resource record in the view while operating on a component (e.g. the person's name and ID, when displaying a list of available images for this person). You may pass static content, or a function or lambda to produce content, which will be forwarded as pheader variable to the view.
If you pass a function or lambda as pheader, then it has to take 5 arguments:
- resource = name of the primary resource
- record_id = id of the primary resource record
- representation = data format of the request
- next=None = backlink URL to reproduce the request (with empty method)
- same=None = backlink URL to reproduce the request (with empty method and containing the string '[id]' instead of the primary resource record id)
The backlinks can be used to reproduce the original request after doing something on the primary resource (e.g., edit or change the selected record).
- NOTE: Callbacks from CRUD settings (defined as in the example above) as well as onvalidation and onaccept callbacks are only invoked at requests on the main resource, but not at joined requests. To get callbacks invoked on Joining Requests, you need to set the onvalidation and onaccept attributes in the
s3xrc.model.add_component()definition, or you set them later using
There are some options which can be set before invoking the REST controller:
def kit(): "RESTlike CRUD controller" response.s3.pdf = URL(r=request, f='kit_export_pdf') response.s3.xls = URL(r=request, f='kit_export_xls') if len(request.args) == 2: crud.settings.update_next = URL(r=request, f='kit_item', args=request.args) return shn_rest_controller(module, 'kit', main='code', onaccept=lambda form: kit_total(form))
response.s3.xls provide the
view/formats.html with an alternate URL to provide a customised version of the PDF/XLS output available when clicking on the icon (
response.s3.rss is also available).
def report_overdue(): "Report on Overdue Invoices - those unpaid 30 days after receipt" response.title = T('Overdue Invoices') overdue = datetime.date.today() - timedelta(days=30) response.s3.filter = (db.fin_invoice.date_out==None) & (db.fin_invoice.date_in < overdue) s3.crud_strings.fin_invoice.title_list = response.title s3.crud_strings.fin_invoice.msg_list_empty = T('No Invoices currently overdue') return shn_rest_controller(module, 'invoice', deletable=False, listadd=False)
response.s3.filter provides a filter which is used in the list view to show the desired subset of records (note that the s3.crud_strings can also be customised - when done in the Controller like this, they are good for just this request).
Plug-In Resource Actions
You may plug in custom resource actions to shn_rest_controller, e.g. if you have a custom search function for a resource.
Example: This adds a search_simple method to the person resource, which calls the shn_pr_person_search_simple function:
# Plug into REST controller s3xrc.model.set_method(module, 'person', method='search_simple', action=shn_pr_person_search_simple )
- module = prefix of the module of the primary resource
- resource = name of the primary resource (without module prefix)
- component=None = tablename of the component (if this method applies to a component)
- method=None = name of the method
- action=None = the function or lambda to invoke for that method (to remove a plug-in action, just pass None here)
The action function has to take the following arguments:
- jr = an instance of XRequest containing all data about the request
- onvalidation=None = function to be called at create/update directly before writing to the database
- onaccept=None = function to be called at create/update directly after writing to the database
The onvalidation/onaccept callback functions in turn take a dictionary as argument that contains a
vars with fieldname=value pairs. They are to be called on create/update actions on the primary resource, while they must not be invoked when operating on component records, instead use
s3xml.model.get_attr(component_tablename, "onvalidation") to retrieve the respective callbacks for the component table.