Changes between Version 2 and Version 3 of S3/S3REST/s3_rest_controller
- Timestamp:
- 10/22/10 12:11:39 (14 years ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
S3/S3REST/s3_rest_controller
v2 v3 7 7 == Introduction == 8 8 9 The so called '''REST Controller''' (function {{{shn_rest_controller()}}}) is a wrapper function for the RESTful API of the [wiki:S3XRC_S3Resource S3Resource] class.9 The so-called '''REST Controller''' (function s3_rest_controller) is a helper function to easily apply the RESTful API of the S3Resource class to your controller. 10 10 11 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, which maps HTTP requests (i.e., URLs and HTTP methods like GET, PUT, DELETE etc.) to internal data stores (e.g. database resources) and methods (e.g. CRUD), and applies the methods by calling so called ''method handlers''. 11 s3_rest_controller does: 12 12 13 Method handlers are to be provided by the calling controller via hooks. The function {{{shn_rest_controller()}}} simplifies the use of the REST interface and provides a number of pre-configured standard CRUD method handlers. 13 - parse and execute the incoming HTTP request on the specified resource 14 - populate and hand-over additional view components 15 - choose and set the response view template (response.view) 14 16 15 These default method handlers as well as {{{shn_rest_controller()}}} itself are implemented in: 16 17 * {{{models/01_crud.py}}}. 18 19 The method handlers of {{{01_crud.py}}} support a number of data representation formats: 20 21 * HTML (including autogenerated Create/Update forms) 22 * PDF (export only) 23 * XLS (export only) 24 * CSV 25 26 Note: Method handlers for XML and JSON formats are integrated in the backend (S3Resource). 27 28 A good tool for testing the REST functionality is the [http://code.google.com/p/rest-client/ RESTClient] 29 == Resources == 30 31 Resources are data objects on the server. 32 33 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. 34 35 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''. 36 37 ''Components are stored in separate database tables which reference the primary resource record.'' 38 39 Resources as well as their components and methods can be addressed individually by URL's. 40 41 == URL Argument Lists == 42 43 General URL format: 17 == Syntax == 44 18 45 19 {{{ 46 http://host/application/module/resource/<arguments>?<vars> 20 output = s3_rest_controller(prefix, resourcename, **attr) 47 21 }}} 48 22 49 The argument list is interpreted as follows: 23 - '''prefix''' is the application prefix of the resource 24 - '''resourcename''' is the name of the resource (without prefix) 25 - '''**attr''' are any additional view components 50 26 51 * '''empty argument list''' = all records of the resource 52 * '''<id>''' = a particular record of the resource 53 * '''<id>/<component>''' = all records of a component of a particular record of the resource 54 * '''<component>/<id>''' = a particular record of a component of the resource 27 - output contains the result of the request and can be returned from the controller as-is 28 - in interactive view formats, this is a dict of variables for the view 55 29 56 If a '''<method>''' argument is added at the end of the argument list, the addressed resource(s) will be accessed with that method. 30 === Additional View Components === 57 31 58 If no '''<id>'''s are specified in the URL while a <method> or a <component> is used, 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. Component record ID's are not remembered. 32 Additional named arguments in the s3_rest_controller call control the output dict ''after'' the request has been executed: 59 33 60 You may pass both an <id> for the resource record and an <id> 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. 34 - any callable argument will be invoked with the S3Request as first and only argument, and its return value will be added to the output dict instead 35 - any non-callable argument which is not None is added to the output dict as-is 36 - any argument that gives None will remove this key from the output dict 61 37 62 The '''data exchange format''' of the request can be passed: 63 64 * as extension to the resource, e.g. http://localhost:8000/eden/pr/person.xml/1 65 * as extension to any of the arguments, e.g. http://localhost:8000/eden/pr/person/1/address/create.xml 66 * as explicit variable "format", e.g. http://localhost:8000/eden/pr/person/1/address?format=xml 67 68 If multiple extensions are specified, the rightmost extension applies. Where ''?format='' is specified, it overrides any extensions. 69 70 71 Can now use UID record addressing instead of IDs: 72 * http://localhost:8000/eden/pr/person/address?address.uid=12345678-9012-3456-7890 73 74 To come: 75 * Multiple record select: http://localhost:8000/eden/pr/person?person.uid=12345678-9012-3456-7890,65434567-8901-2345-67890123 76 * Filter by characteristic: http://localhost:8000/eden/pr/person?person.gender=male 77 == Model == 78 79 Example for definition of a component table in the model: 38 A typical use-case is rheader: 80 39 81 40 {{{ 82 resource = "image" 83 table = module + "_" + resource 84 db.define_table(table, timestamp, uuidstamp, deletion_status, 85 pr_pe_id, 86 opt_pr_image_type, 87 Field("title"), 88 Field("image", "upload", autodelete=True), 89 Field("description"), 90 Field("comment"), 91 migrate=migrate) 41 def my_rheader(r): 42 if r.interactive and r.component: 43 # Code producing the rheader... 44 return rheader 45 else: 46 return None 92 47 93 # Component 94 s3xrc.model.add_component(module, resource, 95 multiple=True, 96 joinby="pr_pe_id", 97 deletable=True, 98 editable=True, 99 list_fields = ["id", "opt_pr_image_type", "image", "title", "description"]) 48 output = s3_rest_controller(prefix, name, rheader=my_rheader) 100 49 }}} 101 50 102 where: 103 104 - '''module''' is the prefix of the module in which the component is defined 105 - '''resource''' is the name of the component (without the module prefix) 106 - '''multiple''' indicates whether this is a N:1 (True) or 1:1 (False) join, defaults to True 107 - '''joinby''' describes the join keys: 108 - pass a single field name for natural joins (same key field in both tables) 109 - pass a dictionary of ''tablename=fieldname'' pairs for primary/foreign key matching, in which: 110 - ''tablename'' is the name of the respective primary table 111 - ''fieldname'' the name of the foreign key in the component table that points to the ''id'' field in the primary table 112 - e.g. {{{joinby = dict(pr_person="person_id")}}} 113 - '''fields''' is a list of the fields in the component that shall appear in list views: 114 - if omitted or set to None, all readable fields will be included 115 116 No definitions are required at the primary resource, just define the table as usual. 117 118 == Controller == 119 120 Define a controller action per resource to invoke the controller: 121 122 {{{ 123 def person(): 124 crud.settings.delete_onvalidation = shn_pentity_ondelete 125 return shn_rest_controller(module, "person", 126 main="first_name", 127 extra="last_name", 128 rheader=lambda r: shn_pr_rheader(r, 129 tabs = [(T("Basic Details"), None), 130 (T("Images"), "image"), 131 (T("Identity"), "identity"), 132 (T("Address"), "address"), 133 (T("Contact Data"), "pe_contact"), 134 (T("Memberships"), "group_membership"), 135 (T("Presence Log"), "presence"), 136 (T("Subscriptions"), "pe_subscription") 137 ]), 138 sticky=True) 139 }}} 140 141 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. 142 143 If for rheader a function or lambda is specified, then it will be called with the current S3Request ("r") as the only argument. Otherwise, rheader is passed to the view as-is. 144 145 The tabs functionality as in the example is to be implemented by the respective rheader function. This can make use of the helper function shn_rheader_tabs(), which renders tabs into the rheader to enable easy navigation through the resource and its components. 146 147 The '''sticky''' tag is to specify whether a create/update form of a primary record (=not a component) automatically returns to the list view or not: 148 - If rheader is specified, it is assumed True, i.e. the create/update form of the primary record will not return to the list view but redirect to the update form again (=it is "sticky"). You can override this behavior by setting sticky=False - and if you do so, no rheader (and no tabs) will be rendered in the views of the primary record either. 149 - If no rheader is specified, sticky will be assumed False unless you specify otherwise, hence the create/update forms will return to the list view. An rheader will not be rendered anyway (since not specified). 150 === Options === 151 There are some options which can be set before invoking the REST controller: 152 {{{ 153 def kit(): 154 "REST CRUD controller" 155 response.s3.formats.pdf = URL(r=request, f="kit_export_pdf") 156 response.s3.formats.xls = URL(r=request, f="kit_export_xls") 157 if len(request.args) == 2: 158 crud.settings.update_next = URL(r=request, f="kit_item", args=request.args[1]) 159 return shn_rest_controller(module, "kit", main="code", onaccept=lambda form: kit_total(form)) 160 }}} 161 162 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). 163 164 {{{ 165 def report_overdue(): 166 "Report on Overdue Invoices - those unpaid 30 days after receipt" 167 response.title = T("Overdue Invoices") 168 overdue = datetime.date.today() - timedelta(days=30) 169 response.s3.filter = (db.fin_invoice.date_out==None) & (db.fin_invoice.date_in < overdue) 170 s3.crud_strings.fin_invoice.title_list = response.title 171 s3.crud_strings.fin_invoice.msg_list_empty = T("No Invoices currently overdue") 172 return shn_rest_controller(module, "invoice", deletable=False, listadd=False) 173 }}} 174 175 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). 176 177 == Plug-In Resource Actions == 178 179 You may plug in custom resource actions to shn_rest_controller, e.g. if you have a custom search function for a resource. 180 181 Example: 182 This adds a ''search_simple'' method to the ''person'' resource, which calls the ''shn_pr_person_search_simple'' function: 183 184 {{{ 185 # Plug into REST controller 186 s3xrc.model.set_method(module, "person", method="search_simple", action=shn_pr_person_search_simple ) 187 }}} 188 189 Example: 190 The following disables the create method for a resource 191 {{{ 192 def restricted_method(jr, **attr): 193 """Handy function for restricting access to certain methods """ 194 session.error = T("Restricted method") 195 redirect(URL(r=request)) 196 197 # Note the s3xrc.model.set_method usage below. 198 def inbox(): 199 " RESTlike CRUD controller for inbox" 200 person = db(db.pr_person.uuid == auth.user.person_uuid).select(db.pr_person.id) 201 response.s3.filer = (db.msg_inbox.person_id == person[0].id) 202 s3xrc.model.set_method(module, "inbox", method="create", action = restricted_method) 203 return shn_rest_controller(module, "inbox", listadd=False, editable = False) 204 205 }}} 206 207 Arguments of {{{set_method}}}: 208 209 * '''module''' = prefix of the module of the primary resource 210 * '''resource''' = name of the primary resource (without module prefix) 211 * '''component=None''' = tablename of the component (if this method applies to a component) 212 * '''method=None''' = name of the method 213 * '''action=None''' = the function or lambda to invoke for that method (to remove a plug-in action, just pass None here) 214 215 The action function has to take the following arguments: 216 217 * '''jr''' = an instance of ''S3RESTRequest'' containing all data about the request 218 * '''**attr''' = a dictionary of named arguments (same named args as passed to the shn_rest_controller) 51 In this case, the rheader variable is only added to the output when the my_rheader function returns something else than None. 219 52 220 53 ----