Changes between Version 1 and Version 2 of S3/S3REST/s3_rest_controller


Ignore:
Timestamp:
10/22/10 11:18:03 (11 years ago)
Author:
Dominic König
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • S3/S3REST/s3_rest_controller

    v1 v2  
     1[[TOC]]
    12= s3_rest_controller =
    23
     
    45  - [wiki:S3XRC/RESTfulAPI S3 RESTful API]
    56
     7== Introduction ==
     8
     9The so called '''REST Controller''' (function {{{shn_rest_controller()}}}) is a wrapper function for the RESTful API of the [wiki:S3XRC_S3Resource S3Resource] class.
     10
     11The [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''.
     12
     13Method 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.
     14
     15These default method handlers as well as {{{shn_rest_controller()}}} itself are implemented in:
     16
     17  * {{{models/01_crud.py}}}.
     18
     19The 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
     26Note: Method handlers for XML and JSON formats are integrated in the backend (S3Resource).
     27
     28A good tool for testing the REST functionality is the [http://code.google.com/p/rest-client/ RESTClient]
     29== Resources ==
     30
     31Resources are data objects on the server.
     32
     33Resources 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
     35Attributes 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
     39Resources as well as their components and methods can be addressed individually by URL's.
     40
     41== URL Argument Lists ==
     42
     43General URL format:
     44
     45{{{
     46http://host/application/module/resource/<arguments>?<vars>
     47}}}
     48
     49The argument list is interpreted as follows:
     50
     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
     55
     56If a '''<method>''' argument is added at the end of the argument list, the addressed resource(s) will be accessed with that method.
     57
     58If 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.
     59
     60You 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.
     61
     62The '''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
     68If multiple extensions are specified, the rightmost extension applies. Where ''?format='' is specified, it overrides any extensions.
     69
     70
     71Can now use UID record addressing instead of IDs:
     72 * http://localhost:8000/eden/pr/person/address?address.uid=12345678-9012-3456-7890
     73
     74To 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
     79Example for definition of a component table in the model:
     80
     81{{{
     82resource = "image"
     83table = module + "_" + resource
     84db.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)
     92
     93# Component
     94s3xrc.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"])
     100}}}
     101
     102where:
     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
     116No definitions are required at the primary resource, just define the table as usual.
     117
     118== Controller ==
     119
     120Define a controller action per resource to invoke the controller:
     121
     122{{{
     123def 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
     141The 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
     143If 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
     145The 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
     147The '''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 ===
     151There are some options which can be set before invoking the REST controller:
     152{{{
     153def 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
     162The {{{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{{{
     165def 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
     175The {{{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
     179You may plug in custom resource actions to shn_rest_controller, e.g. if you have a custom search function for a resource.
     180
     181Example:
     182This 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
     186s3xrc.model.set_method(module, "person", method="search_simple", action=shn_pr_person_search_simple )
     187}}}
     188
     189Example:
     190The following disables the create method for a resource
     191{{{
     192def 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.
     198def 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
     207Arguments 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
     215The 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)
     219
    6220----
    7221