lunes, enero 07, 2013

AngularJS, Turbogears 2.2 y RestController

Ahora que AngularJS está en producción, me dispongo a aprovechar esa nueva herramienta, con el respaldo de Turbogears.

En el homepage de AngularJS existe un demo ('Wire up a Backend') que está respaldado por MondoDB de MongoLabs y aunque funciona excelente y la plataforma de MongoDB parece muy sólida, yo quise experimentar con otras herramientas.

El modelo es bastante simple, declarado en SQLAlchemy queda así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# -*- coding: utf-8 -*-
""" Project model module."""

from sqlalchemy import Column
from sqlalchemy.types import Integer, Unicode
#from sqlalchemy.orm import relation, backref

from angulartests.model import DeclarativeBase, DBSession


class Project(DeclarativeBase):
    __tablename__ = 'project'
    
    #{ Columns
    
    id = Column(Integer, primary_key=True)
    
    name = Column(Unicode(255), nullable=False)
    
    site = Column(Unicode(255), nullable=False)
    
    description = Column(Unicode(255), nullable=True)
    
    #}

    #{ Helpers
    
    @property
    def toJSON(self):
        return ("""{"id": %s, "name": "%s", "site": "%s", "description": "%s"}""" %
            self.id, self.name, self.site, self.description).encode('utf-8')
    
    @property
    def toDict(self):
        return dict(
                id=self.id,
                name=self.name,
                site=self.site,
                description=self.description)
    
    @classmethod
    def by_id(cls, project_id):
        """ Returns object whose id is ```project_id``` """
        return DBSession.query(cls)\
            .filter(cls.id == project_id).first()
    
    @classmethod
    def getAll(cls):
        """ Returns all objects """
        return DBSession.query(cls).all()

    #}

La parte del controlador me costó más trabajo ya que hay que definir un controlador con REST capabilities. TG ofrece un RestController que podemos usar.

El controllador

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# -*- coding: utf-8 -*-
"""Sample REST controller module"""

# turbogears imports
from tg import expose, request
from tg.controllers import RestController
from tg.decorators import with_trailing_slash

from angulartests import model

import json

import logging
log = logging.getLogger(__name__)


class WireUpResourcesController(RestController):
    """ Backend controller for AngularJS test """
    
    @with_trailing_slash
    @expose('json')
    def get(self, **kw):
        log.debug("(GET) kw: %s", kw)
        id = kw.get('id', None)
        
        if id:
            project = model.Project.by_id(id)
            return json.dumps(project.toDict if project else {})

        data = [o.toDict for o in model.Project.getAll()]
        return json.dumps(data)

    @with_trailing_slash
    @expose('json')
    def put(self, id, *args, **kw):
        # where are my parameters?
        kw = json.loads(request.body)

        if id and id != 'undefined':
            project = model.Project.by_id(id)
        else:
            # No id? new project then
            project = model.Project()

        project.name = kw.get('name', None)
        project.site = kw.get('site', None)
        project.description = kw.get('description', None)

        model.DBSession.add(project)

        return json.dumps(project.toDict)

    @with_trailing_slash
    @expose('json')
    def post(self, **kw):
        log.debug("(POST) kw: %s", kw)
        id = kw.get('id', None)

        # where are my parameters?
        kw = json.loads(request.body)

        if id:
            project = model.Project.by_id(id)
        else:
            # No id? new project then
            project = model.Project()

        project.name = kw.get('name', None)
        project.site = kw.get('site', None)
        project.description = kw.get('description', None)

        model.DBSession.add(project)
 # need an id to return, so we flush here
        model.DBSession.flush()

        log.debug("returning: %s", project.toDict)
        return json.dumps(project.toDict)
        
    @with_trailing_slash
    @expose('json')
    def delete(self, **kw):
        log.debug("(DELETE) kw: %s", kw)
        id = kw.get('id', None)

        if id:
            # No confirmation delete object
            project = model.Project.by_id(id)

            if project:
                model.DBSession.delete(project)
                model.DBSession.flush()

        return

Notar que tengo que reconstruir mi mapa de parámetros en las líneas 37 y 60 debido a que llegan como contenido y no propiamente como parámetros del request. Esa es la única parte diferente a lo que haría con un controlador estándard

También hay que modificar un poco el módulo de angular para usar este backend, quedando projec.js así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
'use strict';

angular.module('project', ['project_resource']).
  config(function($routeProvider) {
    $routeProvider.
      when('/', {controller:ListCtrl, templateUrl:'/app/partials/wire_up_list.html'}).
      when('/edit/:projectId', {controller:EditCtrl, templateUrl:'/app/partials/wire_up_detail.html'}).
      when('/new', {controller:CreateCtrl, templateUrl:'/app/partials/wire_up_detail.html'}).
      otherwise({redirectTo:'/'});
  });
 
 
function ListCtrl($scope, Project) {
  $scope.projects = Project.query();
}
 
 
function CreateCtrl($scope, $location, Project) {
  $scope.save = function() {
    Project.save($scope.project, function(project) {
      $location.path('/edit/' + project.id);
    });
  }
}
 
 
function EditCtrl($scope, $location, $routeParams, Project) {
  var self = this;
 
  Project.get({id: $routeParams.projectId}, function(project) {
    self.original = project;
    $scope.project = new Project(self.original);
  });
 
  $scope.isClean = function() {
    return angular.equals(self.original, $scope.project);
  }
 
  $scope.destroy = function() {
    self.original.destroy(function() {
      $location.path('/list');
    });
  };
 
  $scope.save = function() {
    $scope.project.update(function() {
      $location.path('/');
    });
  };
}

y project-resource.js así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
'use strict';

angular.module('project_resource', ['ngResource']).
    factory('Project', function($resource) {
      var Project = $resource('/wire_up_resources',
          {}, 
          {
            update: { method: 'PUT' }
          }
      );
 
      Project.prototype.update = function(cb) {
        return Project.update({id: this.id},
            angular.extend({}, this, {id:undefined}), cb);
      };
 
      Project.prototype.destroy = function(cb) {
        return Project.remove({id: this.id}, cb);
      };
 
      return Project;
    });

Funciona bastante bien, ahora la pregunta sería ¿Vale la pena? ... hmm yo creo que si, a simple vista los requests al servidor disminuyen considerablemente agrega un poco de complejidad para crear el árbol de desarrollo...

├── config
├── controllers
├── i18n
├── lib
├── model
├── public
│   ├── app      <-- La 'app' de Angularjs
│   │   ├── css
│   │   ├── img
│   │   ├── js
│   │   ├── lib
│   │   │   └── angular
│   │   └── partials
│   ├── config
│   ├── css
│   ├── images
│   ├── javascript
│   ├── logs
│   ├── scripts
│   └── test    <-- Los tests de Angularjs
│       ├── e2e
│       ├── lib
│       │   └── angular
│       └── unit
├── templates
├── tests
│   ├── functional
│   └── models
└── websetup

Simplemente expandí el angular-seed dentro del directorio public de mi proyecto de Turbogears. Espero que le sirva a alguien

No hay comentarios.: