Changes between Version 57 and Version 58 of S3REST


Ignore:
Timestamp:
08/22/10 17:51:29 (11 years ago)
Author:
Dominic König
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • S3REST

    v57 v58  
    33
    44  - see also: [wiki:RESTController] and [wiki:S3XRC]
    5 == S3RESTController ==
    6 
    7 The S3RESTController class implements a generic RESTful interface for database resources in S3.
    8 
    9 This class is instantiated as the global callable '''s3rest''' object, which will be used by the ''shn_rest_controller()'' wrapper function.
    10 
    11 '''s3rest''' can be configured dynamically, and can easily be used to build RESTful controllers throughout the application.
    12 
    13 Important: an s3rest instance is created per request and must not be stored or referenced by static objects to avoid memory leaks!
    14 
    15 The default method handlers of '''s3rest''' (provided by the ''shn_rest_controller()'' wrapper) implement CRUD (create, read, update, delete) in all major representations, including the capabilities of [wiki:S3XRC S3XRC] to use in-line XSLT for export and import of XML and JSON formats.
    16 
    17 [[Image(s3rest.png)]]
    18 
    19 Instead of the web2py ''request'' object, '''s3rest''' uses the ''[#S3RESTRequest S3RESTRequest]'' class (described below) to represent the current REST request. Unlike web2py's request, the S3RESTRequest of the current call is not a global object, but gets passed as argument when calling REST method handlers.
    20 
    21 === REST Methods ===
    22 
    23 It is critical to understand how RESTful requests work.
    24 
    25 A RESTful request has two components:
    26 
    27   - the '''URL''' addresses the resource
    28   - the '''HTTP method''' determines the action to be taken with that resource, which is either:
    29     - GET, PUT, POST or DELETE
    30 
    31 A resource in this terminology is either:
    32 
    33   - a database table or one or more records in that table
    34   - a combination of tables or records (so called "compound" or "joined" resources)
    35   - a function in the application related to one of the above resource types
    36 
    37 ''Functions'' as resources in RESTful requests are to be understood and addressed as "sub"-resources of their respective '''primary resource'''.
    38 
    39 The finally executed response action depends on the HTTP/method and on the type of resource addressed by the URL, e.g.
    40 
    41   - if the method is "GET" and the resource addressed is a database table, then the response will be a list of the records in that table
    42   - if the method is "PUT" and the resource addressed is a record in a table, then the response will be an update of that record with the data from the request body
    43 
    44 === CRUD Method Handlers ===
    45 
    46 The default method handlers for each of CRUD+S are implemented in models/01_crud.py:
    47 
    48   - shn_read(jr, **attr)
    49   - shn_list(jr, **attr)
    50   - shn_create(jr, **attr)
    51   - shn_update(jr, **attr)
    52   - shn_delete(jr, **attr)
    53   - shn_search(jr, **attr)
    54   - shn_options(jr, **attr)
    55   - import_xml(jr, **attr)
    56   - import_json(jr, **attr)
    57 
    58 where ''jr'' is the current ''S3RESTRequest'', and ''**attr'' contains all further arguments passed to the REST controller.
    59 
    60 You can override these defaults at any time by
    61 {{{
    62 s3rest.set_handler(action, handler)
    63 }}}
    64 where:
    65 
    66   - '''action''' is the name of the action, i.e. one of 'read', 'list', 'create', 'update', 'delete', 'search', 'options', 'import_xml' or 'import_json'
    67   - '''handler''' is the handler function/lambda
    68 
    69 === Custom Methods ===
    70 
    71 You can also add resource-specific custom methods by:
    72 {{{
    73   s3xrc.model.set_method(module, resource, method, action)
    74 }}}
    75 where:
    76 
    77   - '''module''' is the module prefix of a resource
    78   - '''resource''' is the name of the resource (without prefix)
    79   - '''method''' is a string representing the name of the method (e.g. "search_simple")
    80   - '''action''' is the method handler function/lambda
    81 
    82 The ''action'' method has to take the same arguments as the default handlers: ''jr'' (S3RESTRequest) and ''**attr''.
    83 
    84 === Pre- and Post-Hooks ===
    85 
    86 You can hook in a '''preprocessing''' function into the REST controller (as response.s3.prep) which will be called ''after'' the controller has parsed the request, but ''before'' it gets actually executed - with the current S3RESTRequest as argument (which includes the primary resource record, if any).
    87 
    88 This allows you to easily make changes to resource settings (e.g. access control, list fields etc.), or even to the REST controller configuration (e.g. custom methods) depending on the request type, its parameters or the addressed resource, without having to parse the web2py request manually. You can even bypass the execution of the request and thus hook in your own REST controller - with the advantage that you don't need to parse the request anymore.
    89 
    90 '''NB: Hooks should be added at the top of your controller function, especially pre-processing hooks.'''
    91 
    92 A simple example:
    93 
    94 Original code fragment:
    95 {{{
    96     if len(request.args) == 0:
    97         # List View - reduce fields to declutter
    98         table.message.readable = False
    99         table.categories.readable = False
    100         table.verified_details.readable = False
    101         table.actioned_details.readable = False
    102 
    103     response.s3.pagination = True #enable SSPag here!
    104 
    105     return shn_rest_controller(module, resource, listadd=False)
    106 }}}
    107 
    108 Using the pre-processor hook instead:
    109 {{{
    110     def log_prep(jr):
    111         if jr.representation == "html" and \
    112            jr.method is None and \
    113            jr.component is None:
    114             # List View - reduce fields to declutter
    115             table.message.readable = False
    116             table.categories.readable = False
    117             table.verified_details.readable = False
    118             table.actioned_details.readable = False
    119         return True
    120 
    121     response.s3.prep = log_prep
    122     response.s3.pagination = True #enable SSPag here!
    123 
    124     return shn_rest_controller(module, resource, listadd=False)
    125 }}}
    126 
    127 The return value of the preprocessor function can simply be True, in which case the REST request will be executed as usual. Returning False would lead to a HTTP400 "Invalid Request" exception being raised.
    128 
    129 The return value of the preprocessor function can also be a dict for more granular control - containing the following elements (all optional):
    130 
    131  - '''success''': boolean (default: True)
    132  - '''output''': dict (default: None)
    133  - '''bypass''': boolean (default: False)
    134 
    135 If ''bypass'' is True, then the REST controller does not execute the request (the post-hook is executed, though). ''output'' must not be None in this case - it will be returned from the REST controller.
    136 
    137 If ''success'' is False, and ''output'' is not None, then the REST controller does not execute the request, but just returns "output" (post-hook will ''not'' be executed in this case).
    138 
    139 If ''success'' is False and ''output'' is None, a HTTP400 "Invalid Request" will be raised instead.
    140 
    141 Examples:
    142 
    143 In most cases, you will just return "True" - in some cases you might want to raise an error, e.g.:
    144 {{{
    145     response.error = "This request cannot be executed"
    146     return dict(
    147         success=False,
    148         output=dict(title="My Pagetitle", item="Sorry, no data..."))
    149 }}}
    150 
    151 There is also a '''post-processing hook''' (response.s3.postp) that allows you to execute something directly after the REST request has been executed, but before the shn_rest_controller returns. The post-hook function will be called with the current S3RESTRequest and the output dict of its execution as arguments.
    152 
    153 PostP Examples:
    154 {{{
    155 def user_postp(jr, output):
    156     # Replace the ID column in List views with 'Action Buttons'
    157     shn_action_buttons(jr)
    158     return output
    159 response.s3.postp = user_postp
    160 }}}
    161 
    162 {{{
    163 def postp(r, output):
    164    # Redirect to read/edit view after create rather than to list view
    165    if r.representation == "html" and r.method == "create":
    166        r.next = r.other(method="", record_id=s3xrc.get_session(session,
    167 module, resource))
    168    return output
    169 response.s3.postp = postp
    170 }}}
    171 ==== Passing information between main Controller & Prep ====
    172 Scope normally means that these 2 sections can only talk to each other via globals or the Request object.
    173 
    174 If you need to pass data between them, you can use this trick:
    175 {{{
    176 vars = {} # the surrounding dict
    177 def prep(r, vars):
    178     vars.update(x=y) # the actual variable to pass is x
    179     return True
    180 
    181 response.s3.prep = lambda r, vars=vars: prep(r, vars)
    182 
    183 output = shn_rest_controller(module, resource)
    184 
    185 x = vars.get(x, None)
    186 }}}
    187 
    188 An example usage is in {{{controllers/gis.py}}} for location()
    189 == S3RESTRequest ==
    190 
    191 Important: S3RESTRequest instances are generated per request and must not be stored or referenced by static or global objects to avoid memory leaks!
    192 === Attributes of the S3RESTRequest ===
    193 
    194 The following attributes are set during initialisation of an S3RESTRequest object, no further call is required.
    195 
    196 ==== Controller Attributes ====
    197 
    198 ||'''rc'''||the resource controller object (''S3XRC'')||
    199 ||'''request'''||the original web2py request (''Storage'')||
    200 ||'''session'''||the current session (''Storage'')||
    201 
    202 ==== Request Attributes ====
    203 
    204 ||'''representation'''||the current representation of this request (''string'', lowercase)||
    205 ||'''http'''||the HTTP method of this request (''string'', always uppercase!)||
    206 ||'''extension'''||the extension found in the original request (''string'', lowercase)||
    207 ||'''method'''||the method of the request if not HTTP (''string'', always lowercase)||
    208 ||'''custom_action'''||the custom method handler for the request (''function'' or ''lambda'')||
    209 
    210 ==== Primary Resource Attributes ====
    211 
    212 ||'''prefix'''||the prefix (=module name) of the requested resource (''string'')||
    213 ||'''name'''||the name of the requested resource, without prefix (''string'')||
    214 ||'''tablename'''||the name of the primary table (''string'')||
    215 ||'''table'''||the primary table (''Table'')||
    216 ||'''id'''||the ID of the primary record (''int'')||
    217 ||'''record'''||the primary record (''Row'')||
    218 
    219 ==== Component Resource Attributes ====
    220 
    221 ||'''component'''||the requested component, if any (''!ObjectComponent'')||
    222 ||'''pkey'''||the primary key of the Resource/Component join (''string'')||
    223 ||'''fkey'''||the foreign key of the Resource/Component join (''string'')||
    224 ||'''component_name'''||the name of the component without prefix (''string'')||
    225 ||'''component_id'''||the ID of the component record as of the request, if any (''int'')||
    226 ||'''multiple'''||Flag indicating that multiple component records are allowed (''boolean'')||
    227 
    228 ==== Error Indicators ====
    229 
    230 ||'''error'''||the last error message (''string'')||
    231 ||'''invalid'''||Flag indicating this request as invalid (''boolean'')||
    232 ||'''badmethod'''||Flag indicating a bad method error (''boolean'')||
    233 ||'''badrecord'''||Flag indicating a invalid record ID error (''boolean'')||
    234 ||'''badrequest'''||Flag indicating an unqualified request error (''boolean'')||
    235 
    236 '''!ObjectComponent''' contains:
    237   - '''prefix''', '''name''', '''tablename''', '''table''' and '''attr''' of the component
    238   - '''attr''' contains:
    239     - '''multiple''' Multiple-flag
    240     - '''editable''', '''deletable''' and '''listadd'''
    241     - '''list_fields''', '''rss''', '''main''' and '''extra'''
    242     - '''onaccept''', '''onvalidation''', '''delete_onaccept''' and '''delete_onvalidation'''
    243   - methods: '''set_attr()''' and '''get_attr()'''
    244 
    245 === Methods of the S3RESTRequest ===
    246 
    247 ==== Magic URLs ====
    248 
    249 '''here(representation=None)'''
    250   - returns the URL of the current request
    251 
    252 '''there(representation=None)'''
    253   - returns the URL of a HTTP GET request for the same resource
    254 
    255 '''same(representation=None)'''
    256   - returns the URL of the current request with the primary record ID replaced by the string literal '[id]'
    257 
    258 '''other(method=None, record_id=None, representation=None)'''
    259   - returns the URL of a request with another method and record ID, but for the same resource
    260 
    261 ==== Other functions ====
    262 
    263 '''target()'''
    264   - returns the target table of the current request as tuple of (prefix, name, table, tablename)
    265 
    266 '''export_xml(permit=None, audit=None, template=None, filterby=None, pretty_print=False)'''
    267   - exports the requested resources as XML
    268 
    269 '''export_json(permit=None, audit=None, template=None, filterby=None, pretty_print=False)'''
    270   - exports the requested resources as JSON
    271 
    272 '''import_xml(tree, permit=None, audit=None, onvalidation=None, onaccept=None)'''
    273   - import the requested resources from the given !ElementTree
    274 
    275 '''options_xml(pretty_print=False)'''
    276   - exports the options of a field (or all fields) in the resource as XML
    277 
    278 '''options_json(pretty_print=False)'''
    279   - exports the options of a field (or all fields) in the resource as JSON
    280 
    281 ----
    282 DeveloperGuidelines