[[TOC]] = REST Controller = The so called '''REST Controller''' (function {{{shn_rest_controller()}}}) is a wrapper function for the RESTful API of the [wiki:S3XRC_S3Resource S3Resource] class. The [wiki:S3XRC_S3Resource S3Resource] class implements a generic '''RESTful API''' ([http://en.wikipedia.org/wiki/Representational_State_Transfer Representational State Transfer]) for Sahana Eden database resources, i.e. they can map HTTP requests to data resources and function calls. In particular, they map resource addresses (=URL's) and HTTP methods (=GET, PUT, DELETE etc.) to CRUD actions, and then invoke so called ''method handlers'' (provided by the calling controller via hooks) to execute those actions. The function {{{shn_rest_controller()}}} provides by default a number of generic CRUD method handlers. These method handlers as well as {{{shn_rest_controller()}}} itself are implemented in: * {{{models/01_crud.py}}}. The method handlers support a number of data representation formats: * HTML (including autogenerated Create/Update forms) * PDF (export only) * XLS (export only) * CSV See also: [wiki:S3XRC S3XRC] A good tool for testing the REST functionality is the [http://code.google.com/p/rest-client/ RESTClient] == Resources == Resources are data objects on the server. Resources 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 individually by URL's. == URL Argument Lists == General URL format: {{{ http://host/application/module/resource/? }}} The argument list is interpreted as follows: * '''empty argument list''' = all records of the resource * '''''' = a particular record of the resource * '''/''' = all records of a component of a particular record of the resource * '''/''' = a particular record of a component of the resource If a '''''' argument is added at the end of the argument list, the addressed resource(s) will be accessed with that method. If no ''''''s are specified in the URL while a or a is used, then the last used for this resource will be recalled. This feature can be used for cascading or returning UI actions. The 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 memory is automatically cleared, of course. Component record ID's are not remembered. You may pass both an for the resource record and an for a component record within the same URL (however, you don't have to) - which would produce an error message if these two records do not belong together. The '''data exchange format''' of the request can be passed: * as extension to the resource, e.g. http://localhost:8000/eden/pr/person.xml/1 * as extension to any of the arguments, e.g. http://localhost:8000/eden/pr/person/1/address/create.xml * as explicit variable "format", e.g. http://localhost:8000/eden/pr/person/1/address?format=xml If multiple extensions are specified, the rightmost extension applies. Where ''?format='' is specified, it overrides any extensions. Can now use UID record addressing instead of IDs: * http://localhost:8000/eden/pr/person/address?address.uid=12345678-9012-3456-7890 To come: * Multiple record select: http://localhost:8000/eden/pr/person?person.uid=12345678-9012-3456-7890,65434567-8901-2345-67890123 * Filter by characteristic: http://localhost:8000/eden/pr/person?person.gender=male == Model == 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"]) }}} where: - '''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 - e.g. {{{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. == Controller == 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", rheader=lambda jr: shn_pr_rheader(jr, tabs = [(T("Basic Details"), None), (T("Images"), "image"), (T("Identity"), "identity"), (T("Address"), "address"), (T("Contact Data"), "pe_contact"), (T("Memberships"), "group_membership"), (T("Presence Log"), "presence"), (T("Subscriptions"), "pe_subscription") ]), sticky=True) }}} The optional '''rheader''' 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 ''rheader'' variable to the view. If you pass a function or lambda as rheader, 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). === Options === There are some options which can be set before invoking the REST controller: {{{ def kit(): "REST CRUD controller" response.s3.formats.pdf = URL(r=request, f="kit_export_pdf") response.s3.formats.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[1]) return shn_rest_controller(module, "kit", main="code", onaccept=lambda form: kit_total(form)) }}} The {{{response.s3.formats.pdf}}} & {{{response.s3.formats.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.formats.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) }}} The {{{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 ) }}} Example: The following disables the create method for a resource {{{ def restricted_method(jr, **attr): """Handy function for restricting access to certain methods """ session.error = T("Restricted method") redirect(URL(r=request)) # Note the s3xrc.model.set_method usage below. def inbox(): " RESTlike CRUD controller for inbox" person = db(db.pr_person.uuid == auth.user.person_uuid).select(db.pr_person.id) response.s3.filer = (db.msg_inbox.person_id == person[0].id) s3xrc.model.set_method(module, "inbox", method="create", action = restricted_method) return shn_rest_controller(module, "inbox", listadd=False, editable = False) }}} Arguments of {{{set_method}}}: * '''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 ''S3RESTRequest'' containing all data about the request * '''**attr''' = a dictionary of named arguments (same named args as passed to the shn_rest_controller) ---- DeveloperGuidelines