TurboGears provides a great way to rapidly prototype rest APIS using the EasyCrudRestController.
Defining a basic ready to use API is as simple as:
from tgext.crud import EasyCrudRestController
class APIController(EasyCrudRestController):
pagination = False
model = model.Permission
class RootController(BaseController):
permissions = APIController(model.DBSession)
Accessing the /permissions.json
URL you should get:
{
value_list: [
{
permission_id: 1,
description: "This permission give an administrative right to the bearer",
permission_name: "manage"
}
]
}
The first side effect is that accessing the url without the .json
extension you will get the CRUD page. This is usually something you
don’t want when exposing an API and can be easily prevented by blocking
requests which are not in JSON format:
from tgext.crud import EasyCrudRestController
from tg import abort
class APIController(EasyCrudRestController):
pagination = False
model = model.Permission
def _before(self, *args, **kw):
if request.response_type != 'application/json':
abort(406, 'Only JSON requests are supported')
super(APIController, self)._before(*args, **kw)
Accessing the HTML page will now report a Not Acceptable error while JSON output will still be accepted.
You probably noticed that even though our permissions are related to groups we didn’t get the list of groups the permission is related to.
This is due to the fact that for performance reasons the EasyCrudRestController
doesn’t automatically resolve relationships when providing responses for APIs.
To enable this behavior it is sufficient to turn on the json_dictify
option:
class APIController(EasyCrudRestController):
pagination = False
json_dictify = True
model = model.Permission
def _before(self, *args, **kw):
if request.response_type != 'application/json':
abort(406, 'Only JSON requests are supported')
super(APIController, self)._before(*args, **kw)
Reloading the /permissions.json
page will now provide the list of groups
each Permission is related to with a response like:
{
value_list: [
{
permission_id: 1,
description: "This permission give an administrative right to the bearer",
groups: [
1
],
permission_name: "manage"
}
]
}
One of the main reasons to rapidly prototype a REST api is to join it with a frontend framework to perform logic and templating on client side.
The following is an example of a working AngularJS application
that permits creation and deletion of Permission
objects through
the previously created API:
Note
Please note the custom $resource to adapted tgext.crud responses to the style expected by AngularJS
Note
Pay attention to the double $$ required to escape the dollar sign on Kajiki
<html py:extends="master.xhtml" py:strip="True">
<head py:block="head" py:strip="True">
<title py:block="master_title">AngularTG</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.12/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.12/angular-resource.min.js"></script>
<style>
.ng-cloak {
display: none !important;
}
</style>
</head>
<body py:block="body" py:strip="True">
<div class="row">
<div class="col-md-12">
<div ng-app="myApp" class="ng-cloak">
<div ng-controller="PermissionsCtrl">
<h1>Permissions</h1>
<form ng-submit="addPermission(newPerm)">
<input placeholder="Create Permission" ng-model="newPerm.permission_name" autofocus="true"/>
</form>
<div ng-repeat="perm in permissions">
<span ng-click="delPermission($$index)">X</span>
{{perm.permission_name}}
<p>{{perm.description}}</p>
</div>
</div>
</div>
</div>
</div>
<script>
//<![CDATA[
var myApp = angular.module('myApp', ['ngResource']);
myApp.factory('Permissions', ['$$resource', function($$resource) {
return $$resource("${tg.url('/permissions/:id.json')}", {'id': "@permission_id"}, {
query: {
method: 'GET',
isArray: true,
transformResponse: function (data) {
var data = angular.fromJson(data);
return data.value_list;
}
},
save: {
method: 'POST',
transformResponse: function(data) {
var data = angular.fromJson(data);
return data.value;
}
}
});
}]);
myApp.controller('PermissionsCtrl', ['$$scope', 'Permissions',
function ($$scope, Permissions) {
$$scope.permissions = Permissions.query();
$$scope.addPermission = function(permData) {
var perm = new Permissions(permData);
permData.permission_name = "";
perm.$$save(function(data) {
$$scope.permissions.push(new Permissions(data));
});
}
$$scope.delPermission = function(index) {
perm = $$scope.permissions[index];
perm.$$delete(function(data) {
$$scope.permissions.splice(index, 1);
});
}
}]);
//]]>
</script>
</body>
</html>
The previous API returns all the Permissions available, which is the most simple case but not always what you are looking for. It is often needed to filter the results by a constraint, for example it is common to get only the objects for a specific user.
While this can be easily achieved by passing any filter to the API itself
when it is called: /permissions.json?groups=1
. It is common to need to
perform this on server side.
If we want to permanently only get the permissions for the manage groups
we can make it by extending the get_all
method:
from tgext.crud import EasyCrudRestController
from tg import abort
class APIController(EasyCrudRestController):
pagination = False
json_dictify = True
model = model.Permission
def _before(self, *args, **kw):
if request.response_type != 'application/json':
abort(406, 'Only JSON requests are supported')
super(APIController, self)._before(*args, **kw)
@expose(inherit=True)
def get_all(self, *args, **kw):
kw['groups'] = 1
return super(APIController, self).get_all(*args, **kw)
If you point your browser to /permissions.json
and had multiple
permissions you will se that only those for the managers
group
are now reported.
Now if you tried to use the filtered controller with the previously created AngularJS application your probably noticed that the new permissions you create are not listed back when you reload the page. This is because they are actually created without a group, so they don’t match our managers group filter.
To avoid this we can also force the group on creation by extending
also the post
method:
from tgext.crud import EasyCrudRestController
from tg import abort
class APIController(EasyCrudRestController):
pagination = False
json_dictify = True
model = model.Permission
def _before(self, *args, **kw):
if request.response_type != 'application/json':
abort(406, 'Only JSON requests are supported')
super(APIController, self)._before(*args, **kw)
@expose(inherit=True)
def get_all(self, *args, **kw):
kw['groups'] = 1
return super(APIController, self).get_all(*args, **kw)
@expose(inherit=True)
def post(self, *args, **kw):
kw['groups'] = [1]
return super(APIController, self).post(*args, **kw)
This will now correctly create all the new permissions for the managers group.
While extending the get_all
method is quick and easy, you are limited
to the filtering possibilities that sprox exposes you.
For more advanced filtering or even custom queries it is possible to declare your own TableFiller with a totally custom query:
from tgext.crud import EasyCrudRestController
from sprox.fillerbase import TableFiller
from tg import abort
class APIController(EasyCrudRestController):
pagination = False
json_dictify = True
model = model.Permission
class table_filler_type(TableFiller):
__entity__ = model.Permission
def _do_get_provider_count_and_objs(self, **kw):
manager_group = model.DBSession.query(model.Group).filter_by(group_name='managers').first()
results = model.DBSession.query(model.Permission).filter(model.Permission.groups.contains(manager_group)).all()
return len(results), results
def _before(self, *args, **kw):
if request.response_type != 'application/json':
abort(406, 'Only JSON requests are supported')
super(APIController, self)._before(*args, **kw)