Mercurial > p > roundup > code
comparison doc/rest.txt @ 5695:3e1b66c4e1e2
Update docs. Correct errors reported by setup.py build_docs. Add rest
interface and link to rest doc to features page. Add link to xmlrpc
doc to features page. Add rest doc to index. Update rest doc,
hopefully clarify confusing use of parameters in patch action
section. Fix code examples in "Adding new rest endpoints" section. Fix
example adding import of exception.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Sun, 07 Apr 2019 20:17:52 -0400 |
| parents | 1b9ef04b9528 |
| children | c7dd1cae3416 |
comparison
equal
deleted
inserted
replaced
| 5694:c3ffa1ef6b7f | 5695:3e1b66c4e1e2 |
|---|---|
| 34 from relative REST-API links for brevety. | 34 from relative REST-API links for brevety. |
| 35 | 35 |
| 36 Summary | 36 Summary |
| 37 ======= | 37 ======= |
| 38 | 38 |
| 39 A Summary page can be reached via ``/data/summary`` via the ``GET`` method. | 39 A Summary page can be reached via ``/summary`` via the ``GET`` method. |
| 40 This is currently hard-coded for the standard tracker schema shipped | 40 This is currently hard-coded for the standard tracker schema shipped |
| 41 with roundup and will display a summary of open issues. | 41 with roundup and will display a summary of open issues. |
| 42 | 42 |
| 43 Data | 43 Data |
| 44 ==== | 44 ==== |
| 134 list. The same effect can be achieved with a ``PUT`` request and an | 134 list. The same effect can be achieved with a ``PUT`` request and an |
| 135 empty new value. | 135 empty new value. |
| 136 | 136 |
| 137 Finally the ``PATCH`` method can be applied to individual items, e.g., | 137 Finally the ``PATCH`` method can be applied to individual items, e.g., |
| 138 ``/data/issue/42`` and to properties, e.g., ``/data/issue/42/title``. | 138 ``/data/issue/42`` and to properties, e.g., ``/data/issue/42/title``. |
| 139 This method gets an operator ``@op=<method>`` where ``<method`` is one | 139 This method gets an operator ``@op=<method>`` where ``<method>`` is one |
| 140 of ``add``, ``replace``, ``remove``, only for an item (not for a | 140 of ``add``, ``replace``, ``remove``, only for an item (not for a |
| 141 property) an additional operator ``action`` is supported. If no operator | 141 property) an additional operator ``action`` is supported. If no operator |
| 142 is specified, the default is ``replace``. The first three operators are | 142 is specified, the default is ``replace``. The first three operators are |
| 143 self explanatory. For an ``action`` operator an ``@action_name`` and | 143 self explanatory. For an ``action`` operator an ``@action_name`` and |
| 144 optional ``@action_argsXXX`` parameters have to be supplied. Currently | 144 optional ``@action_argsXXX`` parameters have to be supplied. Currently |
| 145 there are only two actions without parameters, namely ``retire`` and | 145 there are only two actions, neither has args, namely ``retire`` and |
| 146 ``restore``. The ``retire`` action on an item is the same as a | 146 ``restore``. The ``retire`` action on an item is the same as a |
| 147 ``DELETE`` method, it retires the item. The ``restore`` action is the | 147 ``DELETE`` method, it retires the item. The ``restore`` action is the |
| 148 inverse of ``retire``, the item is again visible. | 148 inverse of ``retire``, the item is again visible. |
| 149 On success the returned value is the same as the respective ``GET`` | 149 On success the returned value is the same as the respective ``GET`` |
| 150 method. | 150 method. |
| 196 ========================= | 196 ========================= |
| 197 | 197 |
| 198 Add or edit the file interfaces.py at the root of the tracker | 198 Add or edit the file interfaces.py at the root of the tracker |
| 199 directory. | 199 directory. |
| 200 | 200 |
| 201 In that file add (remove indentation): | 201 In that file add:: |
| 202 | 202 |
| 203 from roundup.rest import Routing, RestfulInstance, _data_decorator | 203 from roundup.rest import Routing, RestfulInstance, _data_decorator |
| 204 from roundup.exceptions import Unauthorised | |
| 204 | 205 |
| 205 class RestfulInstance: | 206 class RestfulInstance: |
| 206 | 207 |
| 207 @Routing.route("/summary2") | 208 @Routing.route("/summary2") |
| 208 @_data_decorator | 209 @_data_decorator |
| 209 def summary2(self, input): | 210 def summary2(self, input): |
| 210 result = { "hello": "world" } | 211 result = { "hello": "world" } |
| 211 return 200, result | 212 return 200, result |
| 212 | 213 |
| 213 will make a new endpoint .../rest/summary2 that you can test with: | 214 will make a new endpoint .../rest/summary2 that you can test with:: |
| 214 | 215 |
| 215 $ curl -X GET .../rest/summary2 | 216 $ curl -X GET .../rest/summary2 |
| 216 { | 217 { |
| 217 "data": { | 218 "data": { |
| 218 "hello": "world" | 219 "hello": "world" |
| 219 } | 220 } |
| 220 } | 221 } |
| 221 | 222 |
| 222 Similarly appending this to interfaces.py after summary2: | 223 Similarly appending this to interfaces.py after summary2:: |
| 223 | 224 |
| 224 @Routing.route("/data/<:class_name>/@schema", 'GET') | 225 # handle more endpoints |
| 225 def get_element_schema(self, class_name, input): | 226 @Routing.route("/data/<:class_name>/@schema", 'GET') |
| 226 result = { "schema": {} } | 227 def get_element_schema(self, class_name, input): |
| 227 uid = self.db.getuid () | 228 result = { "schema": {} } |
| 228 if not self.db.security.hasPermission('View', uid, class_name) : | 229 uid = self.db.getuid () |
| 229 raise Unauthorised('Permission to view %s denied' % class_name) | 230 if not self.db.security.hasPermission('View', uid, class_name) : |
| 230 | 231 raise Unauthorised('Permission to view %s denied' % class_name) |
| 231 class_obj = self.db.getclass(class_name) | 232 |
| 232 props = class_obj.getprops(protected=False) | 233 class_obj = self.db.getclass(class_name) |
| 233 schema = result['schema'] | 234 props = class_obj.getprops(protected=False) |
| 234 | 235 schema = result['schema'] |
| 235 for prop in props: | 236 |
| 236 schema[prop] = { "type": repr(class_obj.properties[prop]) } | 237 for prop in props: |
| 237 | 238 schema[prop] = { "type": repr(class_obj.properties[prop]) } |
| 238 return result | 239 |
| 239 | 240 return result |
| 240 returns some data about the class | 241 |
| 242 .. | |
| 243 the # comment in the example is needed to preserve indention under Class. | |
| 244 | |
| 245 returns some data about the class:: | |
| 241 | 246 |
| 242 $ curl -X GET .../rest/data/issue/@schema | 247 $ curl -X GET .../rest/data/issue/@schema |
| 243 { | 248 { |
| 244 "schema": { | 249 "schema": { |
| 245 "keyword": { | 250 "keyword": { |
| 256 }, ... | 261 }, ... |
| 257 } | 262 } |
| 258 } | 263 } |
| 259 | 264 |
| 260 | 265 |
| 266 Adding other endpoints (e.g. to allow an OPTIONS query against | |
| 267 ``/data/issue/@schema``) is left as an exercise for the reader. | |
| 268 | |
| 261 Searches and selection | 269 Searches and selection |
| 262 ====================== | 270 ====================== |
| 263 | 271 |
| 264 One difficult interface issue is selection of items from a long list. | 272 One difficult interface issue is selection of items from a long list. |
| 265 Using multi-item selects requires loading a lot of data (e.g. consider | 273 Using multi-item selects requires loading a lot of data (e.g. consider |
| 269 This can be made easier using javascript selection tools like select2, | 277 This can be made easier using javascript selection tools like select2, |
| 270 selectize.js, chosen etc. These tools can query a remote data provider | 278 selectize.js, chosen etc. These tools can query a remote data provider |
| 271 to get a list of items for the user to select from. | 279 to get a list of items for the user to select from. |
| 272 | 280 |
| 273 Consider a multi-select box for the superseder property. Using | 281 Consider a multi-select box for the superseder property. Using |
| 274 selectize.js (and jquery) code similar to: | 282 selectize.js (and jquery) code similar to:: |
| 275 | 283 |
| 276 $('#superseder').selectize({ | 284 $('#superseder').selectize({ |
| 277 valueField: 'id', | 285 valueField: 'id', |
| 278 labelField: 'title', | 286 labelField: 'title', |
| 279 searchField: 'title', ... | 287 searchField: 'title', ... |
| 280 load: function(query, callback) { | 288 load: function(query, callback) { |
| 281 if (!query.length) return callback(); | 289 if (!query.length) return callback(); |
| 287 success: function(res) { | 295 success: function(res) { |
| 288 callback(res.data.collection);} | 296 callback(res.data.collection);} |
| 289 | 297 |
| 290 Sets up a box that a user can type the word "request" into. Then | 298 Sets up a box that a user can type the word "request" into. Then |
| 291 selectize.js will use that word to generate an ajax request with the | 299 selectize.js will use that word to generate an ajax request with the |
| 292 url: .../rest/data/issue?@verbose=2&title=request | 300 url: ``.../rest/data/issue?@verbose=2&title=request`` |
| 293 | 301 |
| 294 This will return data like: | 302 This will return data like:: |
| 295 | 303 |
| 296 { | 304 { |
| 297 "data": { | 305 "data": { |
| 298 "@total_size": 440, | 306 "@total_size": 440, |
| 299 "collection": [ | 307 "collection": [ |
| 305 { | 313 { |
| 306 "link": ".../rest/data/issue/27", | 314 "link": ".../rest/data/issue/27", |
| 307 "id": "27", | 315 "id": "27", |
| 308 "title": "Request for foo" | 316 "title": "Request for foo" |
| 309 }, | 317 }, |
| 318 ... | |
| 310 | 319 |
| 311 selectize.js will look at these objects (as passed to | 320 selectize.js will look at these objects (as passed to |
| 312 callback(res.data.collection)) and create a select list from the each | 321 callback(res.data.collection)) and create a select list from the each |
| 313 object showing the user the labelField (title) for each object and | 322 object showing the user the labelField (title) for each object and |
| 314 associating each title with the corresponding valueField (id). The | 323 associating each title with the corresponding valueField (id). The |
| 315 example above has 440 issues returned from a total of 2000 | 324 example above has 440 issues returned from a total of 2000 |
| 316 issues. Only 440 had the word "request" somewhere in the title greatly | 325 issues. Only 440 had the word "request" somewhere in the title greatly |
| 317 reducing the amount of data that needed to be transferred. | 326 reducing the amount of data that needed to be transferred. |
| 318 | 327 |
| 319 Similar things can be set up to search a large list of keywords using | 328 Similar code can be set up to search a large list of keywords using:: |
| 320 | 329 |
| 321 .../rest/data/keyword?@verbose=2&name=some | 330 .../rest/data/keyword?@verbose=2&name=some |
| 322 | 331 |
| 323 which would return: "some keyword" "awesome" "somebody" making | 332 which would return: "some keyword" "awesome" "somebody" making |
| 324 selections for links and multilinks much easier. | 333 selections for links and multilinks much easier. |
| 325 | 334 |
| 326 Hopefully future enhancements will allow get on a collection to | 335 Hopefully future enhancements will allow get on a collection to |
| 327 include other fields. Why do we want this? Selectize.js can set up | 336 include other fields. Why do we want this? Selectize.js can set up |
| 328 option groups (optgroups) in the select pulldown. So by including | 337 option groups (optgroups) in the select pulldown. So by including |
| 329 status in the returned data: | 338 status in the returned data:: |
| 330 | 339 |
| 331 { | 340 { |
| 332 "link": ".../rest/data/issue/27", | 341 "link": ".../rest/data/issue/27", |
| 333 "id": "27", | 342 "id": "27", |
| 334 "title": "Request for foo", | 343 "title": "Request for foo", |
| 335 'status": "open" | 344 'status": "open" |
| 336 }, | 345 }, |
| 337 | 346 |
| 338 a select widget like: | 347 a select widget like:: |
| 339 | 348 |
| 340 === New === | 349 === New === |
| 341 A request | 350 A request |
| 342 === Open === | 351 === Open === |
| 343 Request for bar | 352 Request for bar |
