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


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

--

Legend:

Unmodified
Added
Removed
Modified
  • S3/S3REST/s3_rest_controller

    v2 v3  
    77== Introduction ==
    88
    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.
     9The 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.
    1010
    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''.
     11s3_rest_controller does:
    1212
    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)
    1416
    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 ==
    4418
    4519{{{
    46 http://host/application/module/resource/<arguments>?<vars>
     20output = s3_rest_controller(prefix, resourcename, **attr)
    4721}}}
    4822
    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
    5026
    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
    5529
    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 ===
    5731
    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.
     32Additional named arguments in the s3_rest_controller call control the output dict ''after'' the request has been executed:
    5933
    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
    6137
    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:
     38A typical use-case is rheader:
    8039
    8140{{{
    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)
     41def my_rheader(r):
     42  if r.interactive and r.component:
     43    # Code producing the rheader...
     44    return rheader
     45  else:
     46    return None
    9247
    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"])
     48output = s3_rest_controller(prefix, name, rheader=my_rheader)
    10049}}}
    10150
    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)
     51In this case, the rheader variable is only added to the output when the my_rheader function returns something else than None.
    21952
    22053----