| 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. |