| 1 | [[TOC]] |
| 2 | |
| 3 | = Joined Resource Controller = |
| 4 | |
| 5 | * Code: [https://code.launchpad.net/~flavour/sahana/sahanapy-trunk Main Trunk] Revision 275+ |
| 6 | |
| 7 | The '''Joined Resource Controller''' provides a RESTful API for joined resources analogous to the generic [wiki:BluePrintRESTImplementation shn_rest_controller]. |
| 8 | |
| 9 | A joined resource request is a request on a resource ("joined resource") in dependence of a join to another table ("primary resource"). |
| 10 | |
| 11 | In this implementation, these joins can be 1:1 or 1:N, and they can be natural joins (same key field in both tables) or primary/foreign key matches, where the primary key is always the 'id' field in the primary table. |
| 12 | |
| 13 | The controller function for this is: |
| 14 | |
| 15 | * '''shn_jr_rest_controller''' (defined in models/01_jr_controller.py) |
| 16 | |
| 17 | == Model == |
| 18 | |
| 19 | Example for definition of a joined resource in the model: |
| 20 | |
| 21 | {{{ |
| 22 | resource = 'image' |
| 23 | table = module + '_' + resource |
| 24 | db.define_table(table, timestamp, uuidstamp, deletion_status, |
| 25 | pr_pe_id, |
| 26 | opt_pr_image_type, |
| 27 | Field('title'), |
| 28 | Field('image', 'upload', autodelete=True), |
| 29 | Field('description'), |
| 30 | Field('comment'), |
| 31 | migrate=migrate) |
| 32 | |
| 33 | # Joined Resource |
| 34 | jrlayer.add_jresource(module, resource, |
| 35 | multiple=True, |
| 36 | joinby='pr_pe_id', |
| 37 | fields = ['id', 'opt_pr_image_type', 'image', 'title','description']) |
| 38 | }}} |
| 39 | |
| 40 | where: |
| 41 | |
| 42 | - '''module''' is the name of the module in which the joined resource is defined (prefix) |
| 43 | - '''resource''' is the name of the joined resource |
| 44 | - '''multiple''' indicates whether this is a 1:N (True) or 1:1 (False) join, defaults to True |
| 45 | - '''joinby''' describes the join keys: |
| 46 | - pass a single field name for natural joins (same key field in both tables) |
| 47 | - pass a dictionary of ''tablename=fieldname'' pairs for primary/foreign key matching, in which: |
| 48 | - ''tablename'' is the name of the respective primary table |
| 49 | - ''fieldname'' the name of the foreign key in the joined table that points to the ''id'' field in the primary table |
| 50 | - '''fields''' is a list of the fields in the joined resource that shall appear in list views: |
| 51 | - if omitted or set to None, all readable fields will be included |
| 52 | |
| 53 | No definitions are required at the primary resource, just define the table as usual. |
| 54 | |
| 55 | - Of course, shn_jr_rest_controller is sensitive to the deletion status |
| 56 | |
| 57 | == Controller == |
| 58 | |
| 59 | shn_jr_rest_controller takes (almost) the same settings and arguments as shn_rest_controller: |
| 60 | |
| 61 | {{{ |
| 62 | def person(): |
| 63 | crud.settings.delete_onvalidation=shn_pentity_ondelete |
| 64 | return shn_jr_rest_controller(module, 'person', main='first_name', extra='last_name', |
| 65 | pheader=shn_pr_pheader, |
| 66 | onvalidation=lambda form: shn_pentity_onvalidation(form, table='pr_person', entity_class=1), |
| 67 | onaccept=None) |
| 68 | }}} |
| 69 | |
| 70 | "Almost" means with exception of the optional '''pheader''' argument: This helps you to display some information about the primary resource record in the view while operating on a joined resource (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 is to be forwarded as ''pheader'' variable to the view. |
| 71 | |
| 72 | If you pass a function or lambda, it has to take 5 arguments: |
| 73 | - '''resource''' = name of the primary resource |
| 74 | - '''record_id''' = id of the primary resource record |
| 75 | - '''representation''' = data format of the request |
| 76 | - '''next=None''' = backlink URL to reproduce the request (with empty method) |
| 77 | - '''same=None''' = backlink URL to reproduce the request (with empty method and containing the string '[id]' instead of the primary resource record id) |
| 78 | |
| 79 | These backlinks can be used to reproduce the original request after doing something on the primary resource (e.g., edit or change the selected record). |
| 80 | |
| 81 | * '''NOTE:''' Callbacks from CRUD settings (like in the example above) as well as onvalidation and onaccept callbacks are only invoked at requests on the main resource, but not at joined requests. |
| 82 | |
| 83 | == Argument Lists == |
| 84 | |
| 85 | URL format: |
| 86 | |
| 87 | {{{ |
| 88 | http://host/application/module/resource/<arguments>?<vars> |
| 89 | }}} |
| 90 | |
| 91 | The argument list is interpreted as follows: |
| 92 | |
| 93 | * '''empty argument list''' is a LIST attempt to the primary resource |
| 94 | * '''<id>''' is a READ attempt on the record #<id> of the primary resource |
| 95 | * '''<method>''' is a <method> attempt on the primary resource |
| 96 | * '''<method>/<id>''' is a <method> attempt on the record #<id> of the primary resource |
| 97 | * '''<id>/<joined_resource>''' is a LIST attempt to the joined resource for the record #<id> of the primary resource |
| 98 | * '''<joined_resource>?id_label=XXX''' is a LIST attempt to the joined resource for the record of the primary resource with that label |
| 99 | * '''<id>/<joined_resource>/<method>''' is a <method> attempt on the joined resource for the record #<id> of the primary resource |
| 100 | * '''<joined_resource>/<method>?id_label=XXX''' analogous. |
| 101 | |
| 102 | You may even pass the record ID of the joined resource at the end of the arguments list to access a particular record - which would produce an error message if these two records do not belong together. |
| 103 | |
| 104 | == Plug-In Resource Actions == |
| 105 | |
| 106 | You may plug in custom resource actions to shn_jr_rest_controller, e.g. if you have a custom search function for a resource. |
| 107 | |
| 108 | Example: |
| 109 | This adds a ''search_simple'' method to the ''person'' resource, which calls the ''shn_pr_person_search_simple'' function: |
| 110 | |
| 111 | {{{ |
| 112 | # Plug into REST controller |
| 113 | jrlayer.set_method(module, 'person', None, None, 'search_simple', shn_pr_person_search_simple ) |
| 114 | }}} |
| 115 | |
| 116 | Arguments of jrlayer.set_method: |
| 117 | |
| 118 | * '''module''' = name of the module of the primary resource |
| 119 | * '''resource''' = name of the primary resource |
| 120 | * '''jmodule''' = name of the module of the joined resource (if any, maybe None) |
| 121 | * '''jresource''' = name of the joined resource (if any, maybe None) |
| 122 | * '''method''' = name of the method |
| 123 | * '''action''' = the function or lambda to invoke for that method (to remove a plug-in action, just pass None here) |
| 124 | |
| 125 | The action method in turn has to take plenty of arguments: |
| 126 | |
| 127 | * '''module''' |
| 128 | * '''resource''' |
| 129 | * '''record_id''' |
| 130 | * '''method''' |
| 131 | * '''jmodule=None''' |
| 132 | * '''jresource=None''' |
| 133 | * '''jrecord_id=None''' |
| 134 | * '''joinby=None''' |
| 135 | * '''multiple=True''' |
| 136 | * '''representation="html"''' |
| 137 | * '''onvalidation=None''' |
| 138 | * '''onaccept=None''' |