Rapid Prototyping REST API

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"
        }
    ]
}

Blocking non JSON requests

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.

Getting Relationships

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"
        }
    ]
}

Leveraging your REST API on AngularJS

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>

Limiting API Results

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.

Custom Queries when fetching data

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)