modularisation
authorThomas NOEL <thomas@new-dev.(none)>
Fri, 4 Jul 2008 14:09:44 +0000 (10:09 -0400)
committerThomas NOEL <thomas.noel@auf.org>
Fri, 4 Jul 2008 14:09:44 +0000 (10:09 -0400)
document-index.html [new file with mode: 0644]
document-index.rss
document.py [new file with mode: 0644]
objetsql.py [new file with mode: 0644]
rest.wsgi
utils.py [new file with mode: 0644]

diff --git a/document-index.html b/document-index.html
new file mode 100644 (file)
index 0000000..5f07ff3
--- /dev/null
@@ -0,0 +1,2 @@
+<pre>{{ debug()|e }}</pre>
+
index 63ac208..0ed51ee 100644 (file)
@@ -12,7 +12,7 @@
      <title>[{{d.statut}}] {{d.code }} {{d.numero | int}} de {{d.demandeur}} pour {{d.approbateur}} : {{d.total}} {{d.devise}} sur {{ d.code_service }}</title>
      <link>http://bacasable.auf/rest/{{d.code_rest}}/{{d.numero | int}}.txt</link>
      <guid>http://bacasable.auf/rest/{{d.code_rest}}/{{d.numero | int}}</guid>
-     <description>fournisseur {{ d.fournisseur }}</description>
+     <description>Fournisseur http://bacasable.auf/rest/fournisseur/{{ d.fournisseur }}</description>
      <pubDate>{{ d.date_modif }}</pubDate>
 </item>
 {% endfor %}
diff --git a/document.py b/document.py
new file mode 100644 (file)
index 0000000..1c0a189
--- /dev/null
@@ -0,0 +1,140 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+from objetsql import *
+from utils import *
+
+class document(objetsql):
+    """objet document CODA (demlog, comlog, demdep... ils ont tous le même format)"""
+    accept_like_filters = [ 'code', 'demandeur', 'approbateur', 'code_service', 'statut', 'fournisseur', 'statut' ]
+    def __init__(self, environ, code_document='%', prefix_template='document'):
+        super(document, self).__init__(environ)
+        self.code_document = code_document
+        self.prefix_template = prefix_template
+       # filtrage : on extrait la liste des filtres "acceptes" pour en faire des where
+        sqlwhere = []
+       for filter in self.accept_like_filters:
+            if filter in self.filters:
+               or_list = []
+               for v in self.filters[filter]:
+                   or_list.append("%s like '%s'" % (filter,v))
+               sqlwhere.append('(' + ' or '.join(or_list) + ')')
+       # TODO : ajouter filtres min_*/max_*, avant/apres dans ce sqlwhere
+       self.sqlwhere = ''
+       for f in sqlwhere:
+           self.sqlwhere += ' and ' + f
+       # filtrage : valeur en cas de limite ("top n" en ODBC), 50 maxi
+       if 'limite' in self.filters:
+           self.sqllimit = min( int(self.filters['limite'][0]), 50 )
+       else:
+           self.sqllimit = 15
+       # filtrage : si aucun statut n'est spécifié, on prend "statut like non%"
+       if not 'statut' in self.filters:
+           self.sqlwhere += " and (statut like 'non%') "
+       # TODO : ajouter filtres tri_asc/tri_desc
+       self.sqlorder = ''
+
+    def _get_index(self, code):
+        """renvoie une liste de documents"""
+       # connexion a la base de données
+       cursor = self.cursor()
+       # extraction des données
+       cursor.execute("select top %d * from auf_v_acces_demcom where (code like '%s') %s order by date_modif desc" % ( self.sqllimit, code, self.sqlwhere ) )
+       items = []
+       while 1:
+           item = dict_fetchone( cursor )
+           if item == None: break
+           item['code_rest'] = coda2rest(item['code'])
+           items.append(item)
+       index = {}
+       index['code'] = code
+       index['filtres'] = self.filters
+       index['documents'] = items
+       return index
+
+    def _get_details(self,code,id):
+        """renvoie la liste des détails pour un document"""
+        details = []
+       cursor = self.cursor()
+        cursor.execute("select * from auf_v_acces_dtls_demcom where (code like '%s') and (numero = %d)" % (code, id))
+        while 1:
+            detail = dict_fetchone(cursor)
+            if detail == None: break
+            details.append(detail)
+        return details
+
+    def _get_document(self,code,id):
+        """renvoie un document"""
+       cursor = self.cursor()
+        cursor.execute("select top 1 * from auf_v_acces_demcom where (code like '%s') and (numero = %d) %s" % (code, id, self.sqlwhere))
+        document = dict_fetchone(cursor)
+       if document == None:
+           raise "document inexistant"
+       document['code_rest'] = coda2rest( document['code'] )
+        document['details'] = self._get_details( code,id )
+       return document
+
+    def index(self):
+        """renvoie une liste de documents formatée"""
+        template = self.template('%s-index' % self.prefix_template) # préparation du modèle
+       index = self._get_index( self.code_document )               # extraction des documents concernés
+       output = template.render( index )                           # formattage via le modèle
+       return self.outputformat, output
+
+    def show(self):
+        """renvoie un document formaté"""
+        # return 'txt', '%s' % self.environ # pour debug
+        id = int(self.environ['wsgiorg.routing_args'][1]['id'])
+        template = self.template( self.prefix_template )
+        document = self._get_document( self.code_document, id )
+        output = template.render( document )
+       return self.outputformat, output
+
+class dem(document):
+    def __init__(self, environ):
+        super(dem, self).__init__(environ, code_document = 'DEM-%')
+
+class com(document):
+    def __init__(self, environ):
+        super(com, self).__init__(environ, code_document = 'COM-%')
+
+class demlog(document):
+    def __init__(self, environ):
+        super(demlog, self).__init__(environ, code_document = 'DEM-LOG-AUF')
+
+class comlog(document):
+    def __init__(self, environ):
+        super(comlog, self).__init__(environ, code_document = 'COM-LOG-AUF')
+
+class demdep(document):
+    def __init__(self, environ):
+        super(demdep, self).__init__(environ, code_document = 'DEM-DEP-AUF')
+
+class comdep(document):
+    def __init__(self, environ):
+        super(comdep, self).__init__(environ, code_document = 'COM-DEP-AUF')
+
+class dempub(document):
+    def __init__(self, environ):
+        super(dempub, self).__init__(environ, code_document = 'DEM-PUB-AUF')
+
+class compub(document):
+    def __init__(self, environ):
+        super(compub, self).__init__(environ, code_document = 'COM-PUB-AUF')
+
+class comsre(document):
+    def __init__(self, environ):
+        # on distingue comsre et comsrexxx (avec xxx = implantation)
+        impl = environ['wsgiorg.routing_args'][1].get('impl','%')
+        super(comsre, self).__init__(environ, code_document = 'COM-SRE-%s' % impl)
+
+class comare(document):
+    def __init__(self, environ):
+        impl = environ['wsgiorg.routing_args'][1].get('impl','%')
+        super(comare, self).__init__(environ, code_document = 'COM-ARE-%s' % impl)
+
+class comxre(document):
+    def __init__(self, environ):
+        impl = environ['wsgiorg.routing_args'][1].get('impl','%')
+        super(comxre, self).__init__(environ, code_document = 'COM-%%RE-%s' % impl)
+
diff --git a/objetsql.py b/objetsql.py
new file mode 100644 (file)
index 0000000..987637e
--- /dev/null
@@ -0,0 +1,34 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+from jinja import Environment, FileSystemLoader
+ejinja = Environment(loader=FileSystemLoader('/home/thomas/public_html/'))
+
+from pymssql import connect
+
+from paste.request import parse_formvars
+
+import sys
+sys.path.append('/home/thomas/public_html/')
+import rest_config
+
+class objetsql(object):
+    """objet de base : dispose d'un accès à MS-SQL (lire les données) et d'un accès à jinja (rendu des données)"""
+    def __init__(self, environ):
+       self.environ = environ
+        parse_formvars(environ)                                            # extraction des variables de query_string et POST
+       self.filters = environ['paste.parsed_formvars'][0].dict_of_lists() # puis traduction en dictionnaire {var1:[val1,val2], ...}
+    def template(self,template):
+        self.outputformat = self.environ['wsgiorg.routing_args'][1].get('format','xml')
+       return ejinja.get_template('%s.%s' % (template,self.outputformat))
+    def cursor(self):
+        if not hasattr(self,'db_connect'):
+            self.db_connect = connect(host=rest_config.host,user=rest_config.user,password=rest_config.password,database=rest_config.database)
+       if not hasattr(self,'db_cursor'):
+           self.db_cursor = self.db_connect.cursor()
+       return self.db_cursor
+    def __del__(self):
+        # coupe la connexion à MSSQL à la mort de l'objet (normalement ça se fait tout seul mais j'en suis pas sûr sûr)
+        if hasattr(self,'db_connect'):
+           db_connect.close()
+
index eeef891..0c3caf6 100644 (file)
--- a/rest.wsgi
+++ b/rest.wsgi
@@ -10,25 +10,17 @@ import rest_config
 # pour savoir quel objet interroger : Routes
 from routes import Mapper
 from routes.middleware import RoutesMiddleware
-# et analyse des paramètres (POST et query_string) pour filtrage
-from paste.request import parse_formvars
 
-# pour chercher les données sur MS-SQL
-from pymssql import connect
+# Gestion des erreurs
+from jinja.exceptions import TemplateNotFound
 
-# pour afficher le résultat : jinja
-from jinja import Environment, FileSystemLoader
-from jinja.filters import stringfilter
-import jinja.exceptions
-# on prepare un environnement jinja toujours disponible
-ejinja = Environment(loader=FileSystemLoader('/home/thomas/public_html/'))
-
-# TODO systeme de cache : beaker
+# TODO : ajouter un middleware de cache (beaker, basé sur REQUEST_URI quand la methode est GET)
 # from beaker.middleware import CacheMiddleware
 
 # formats de sortie autorisés, et content-type correspondant
 # formats = { 'xml': 'application/xml', 'html': 'text/html', 'txt': 'text/plain', 'json': 'application/json', 'rss': 'application/rss+xml' }
-formats = { 'xml': 'application/xml', 'html': 'text/html', 'txt': 'text/plain', 'json': 'text/plain', 'rss': 'application/rss+xml' }
+formats = { 'xml': 'application/xml', 'html': 'text/html', 'txt': 'text/plain', 'json': 'text/plain', 'rss': 'application/rss+xml' } # pour debug : json en text/plain
+
 
 # les routes RESTful (cf http://routes.groovie.org/manual.html#restful-services)
 mapper = Mapper()
@@ -47,208 +39,35 @@ mapper.resource('comxrei','comxre:(impl)',controller='comxre')
 mapper.resource('dem','dem')
 mapper.resource('com','com')
 
-
-class objetsql(object):
-    """objet de base : dispose d'un accès à MS-SQL (lire les données) et d'un accès à jinja (rendu des données)"""
-    def __init__(self, environ):
-       self.environ = environ
-        parse_formvars(environ)                                            # extraction des variables de query_string et POST
-       self.filters = environ['paste.parsed_formvars'][0].dict_of_lists() # puis traduction en dictionnaire {var1:[val1,val2], ...}
-    def template(self,template):
-        self.outputformat = self.environ['wsgiorg.routing_args'][1].get('format','xml')
-       return ejinja.get_template('%s.%s' % (template,self.outputformat))
-    def cursor(self):
-        if not hasattr(self,'db_connect'):
-            self.db_connect = connect(host=rest_config.host,user=rest_config.user,password=rest_config.password,database=rest_config.database)
-       if not hasattr(self,'db_cursor'):
-           self.db_cursor = self.db_connect.cursor()
-       return self.db_cursor
-    def __del__(self):
-        # coupe la connexion à MSSQL à la mort de l'objet (normalement ça se fait tout seul mais j'en suis pas sûr sûr)
-        if hasattr(self,'db_connect'):
-           db_connect.close()
-
-class document(objetsql):
-    """objet document CODA (demlog, comlog, demdep... ils ont tous le même format)"""
-    accept_like_filters = [ 'code', 'demandeur', 'approbateur', 'code_service', 'statut', 'fournisseur' ]
-    def __init__(self, environ, code_document='%', prefix_template='document'):
-        super(document, self).__init__(environ)
-        self.code_document = code_document
-        self.prefix_template = prefix_template
-       # filtrage : on extrait la liste des filtres "acceptes" pour en faire des where
-        sqlwhere = []
-       for filter in self.accept_like_filters:
-            if filter in self.filters:
-               or_list = []
-               for v in self.filters[filter]:
-                   or_list.append("%s like '%s'" % (filter,v))
-               sqlwhere.append('(' + ' or '.join(or_list) + ')')
-       # TODO : ajouter filtres min_*/max_*, avant/apres dans ce sqlwhere
-       self.sqlwhere = ''
-       for f in sqlwhere:
-           self.sqlwhere += ' and ' + f
-       # filtrage : valeur en cas de limite ("top n" en ODBC), 50 maxi
-       if 'limite' in self.filters:
-           self.sqllimit = min( int(self.filters['limite'][0]), 50 )
-       else:
-           self.sqllimit = 15
-       # TODO : ajouter filtres tri_asc/tri_desc
-
-    def _get_index(self, code):
-        """renvoie une liste de documents"""
-       # connexion a la base de données
-       cursor = self.cursor()
-       # extraction des données
-       cursor.execute("select top %d * from auf_v_acces_demcom where (code like '%s') %s order by date_modif desc" % ( self.sqllimit, code, self.sqlwhere ) )
-       items = []
-       while 1:
-           item = dict_fetchone( cursor )
-           if item == None: break
-           item['code_rest'] = coda2rest(item['code'])
-           items.append(item)
-       index = {}
-       index['code'] = code
-       index['documents'] = items
-       return index
-
-    def _get_details(self,code,id):
-        """renvoie la liste des détails pour un document"""
-        details = []
-       cursor = self.cursor()
-        cursor.execute("select * from auf_v_acces_dtls_demcom where (code like '%s') and (numero = %d)" % (code, id))
-        while 1:
-            detail = dict_fetchone(cursor)
-            if detail == None: break
-            details.append(detail)
-        return details
-
-    def _get_document(self,code,id):
-        """renvoie un document"""
-       cursor = self.cursor()
-        cursor.execute("select top 1 * from auf_v_acces_demcom where (code like '%s') and (numero = %d) %s" % (code, id, self.sqlwhere))
-        document = dict_fetchone(cursor)
-       if document == None:
-           raise "document inexistant"
-       document['code_rest'] = coda2rest( document['code'] )
-        document['details'] = self._get_details( code,id )
-       return document
-
-    def index(self):
-        """renvoie une liste de documents formatée"""
-        template = self.template('%s-index' % self.prefix_template) # préparation du modèle
-       index = self._get_index( self.code_document )               # extraction des documents concernés
-       output = template.render( index )                           # formattage via le modèle
-       return self.outputformat, output
-
-    def show(self):
-        """renvoie un document formaté"""
-        # return 'txt', '%s' % self.environ # pour debug
-        id = int(self.environ['wsgiorg.routing_args'][1]['id'])
-        template = self.template( self.prefix_template )
-        document = self._get_document( self.code_document, id )
-        output = template.render( document )
-       return self.outputformat, output
-
-class dem(document):
-    def __init__(self, environ):
-        super(dem, self).__init__(environ, code_document = 'DEM-%')
-
-class com(document):
-    def __init__(self, environ):
-        super(com, self).__init__(environ, code_document = 'COM-%')
-
-class demlog(document):
-    def __init__(self, environ):
-        super(demlog, self).__init__(environ, code_document = 'DEM-LOG-AUF')
-
-class comlog(document):
-    def __init__(self, environ):
-        super(comlog, self).__init__(environ, code_document = 'COM-LOG-AUF')
-
-class demdep(document):
-    def __init__(self, environ):
-        super(demdep, self).__init__(environ, code_document = 'DEM-DEP-AUF')
-
-class comdep(document):
-    def __init__(self, environ):
-        super(comdep, self).__init__(environ, code_document = 'COM-DEP-AUF')
-
-class dempub(document):
-    def __init__(self, environ):
-        super(dempub, self).__init__(environ, code_document = 'DEM-PUB-AUF')
-
-class compub(document):
-    def __init__(self, environ):
-        super(compub, self).__init__(environ, code_document = 'COM-PUB-AUF')
-
-class comsre(document):
-    def __init__(self, environ):
-        # on distingue comsre et comsrexxx (avec xxx = implantation)
-        impl = environ['wsgiorg.routing_args'][1].get('impl','%')
-        super(comsre, self).__init__(environ, code_document = 'COM-SRE-%s' % impl)
-
-class comare(document):
-    def __init__(self, environ):
-        impl = environ['wsgiorg.routing_args'][1].get('impl','%')
-        super(comare, self).__init__(environ, code_document = 'COM-ARE-%s' % impl)
-
-class comxre(document):
-    def __init__(self, environ):
-        impl = environ['wsgiorg.routing_args'][1].get('impl','%')
-        super(comxre, self).__init__(environ, code_document = 'COM-%%RE-%s' % impl)
-
-#
-# fin des objets accessibles
-#
+# objets disponibles
+from document import demlog, comlog, demdep, comdep, dempub, compub, comare, comsre, comxre, dem, com
 
 def dispatcher(environ, start_response):
     """dispatch vers la bonne methode du bon objet, et retour WSGI"""
-    results = environ['wsgiorg.routing_args'][1]
+    results = environ['wsgiorg.routing_args'][1] # résultat du middleware Routes
     try:
+        # On cherche l'objet puis la méthode
         target_class = globals()[results['controller']]
         method = getattr(target_class,results['action'])
     except:
+        # Si erreur pendant la recherche, on renvoie un 404
         start_response("404 Not Found", [('Content-type', 'text/html')])
        return '<html><body><h2>404 objet ou action invalide</h2><pre>%s: %s\n%s</pre></body></html>' % ( sys.exc_info()[0] , sys.exc_info()[1] , traceback.format_exc())
     try:
+        # On lance la méthode et on renvoie le résultat
         type, output = method(target_class(environ))
        start_response("200 OK", [('Content-type', formats[type])])
-        return output.encode('utf-8')
-    except jinja.exceptions.TemplateNotFound, template:
+        return output.encode('utf-8') 
+    # gestion des problèmes possibles pendant l'exécution
+    except TemplateNotFound, template:
         start_response("415 Unsupported Media Type", [('Content-type', 'text/html')])
        return '<html><body><h2>415 format non supporté (%s inexistant)</h2></body></html>' % template
     except:
         start_response("500 INTERNAL ERROR", [('Content-type', 'text/html')])
         return '<html><body><h2>500 erreur lors du traitement</h2><pre>%s: %s\n%s</pre></body></html>' % ( sys.exc_info()[0] , sys.exc_info()[1] , traceback.format_exc())
 
-# application qui sera lancée par mod_wsgi : on route et on dispatche
-application = RoutesMiddleware( dispatcher, mapper)
-# TODO : ajouter un middleware de cache (beaker, basé sur REQUEST_URI quand la methode est GET)
 
-#
-# petits utilitaires
-#
-def dict_fetchone(cursor):
-    """Renvoie le resultat d'un fetchone dans un dictionnaire"""
-    result = cursor.fetchone()
-    if result == None: return None
-    result_dict = {}
-    for i in range(len(result)):
-        if isinstance( result[i], str ):
-           result_dict[cursor.description[i][0]] = result[i].decode('iso-8859-1')
-       else:
-            result_dict[cursor.description[i][0]] = result[i]
-    return result_dict
+# application() sera lancée par mod_wsgi : on route et on dispatche
+application = RoutesMiddleware( dispatcher, mapper )
 
-import re 
-p = re.compile('(dem|com)-(...)-(...)',re.IGNORECASE)
-def coda2rest(value):
-    """Traduit un nom CODA vers la base REST correspodante, 
-    par exemple DEM-LOG-AUF en demlog ou COM-ARE-VN3 en comarevn3"""
-    m = p.search(value)
-    if m == None: return value
-    if m.group(3).lower() == 'auf':
-        return m.group(1).lower() + m.group(2).lower()
-    else:
-        return m.group(1).lower() + m.group(2).lower() + m.group(3).lower()
 
diff --git a/utils.py b/utils.py
new file mode 100644 (file)
index 0000000..af27fcf
--- /dev/null
+++ b/utils.py
@@ -0,0 +1,30 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+#
+# petits utilitaires
+#
+def dict_fetchone(cursor):
+    """Renvoie le resultat d'un fetchone dans un dictionnaire"""
+    result = cursor.fetchone()
+    if result == None: return None
+    result_dict = {}
+    for i in range(len(result)):
+        if isinstance( result[i], str ):
+           result_dict[cursor.description[i][0]] = result[i].decode('iso-8859-1')
+       else:
+            result_dict[cursor.description[i][0]] = result[i]
+    return result_dict
+
+import re 
+p = re.compile('(dem|com)-(...)-(...)',re.IGNORECASE)
+def coda2rest(value):
+    """Traduit un nom CODA vers la base REST correspodante, 
+    par exemple DEM-LOG-AUF en demlog ou COM-ARE-VN3 en comarevn3"""
+    m = p.search(value)
+    if m == None: return value
+    if m.group(3).lower() == 'auf':
+        return m.group(1).lower() + m.group(2).lower()
+    else:
+        return m.group(1).lower() + m.group(2).lower() + m.group(3).lower()
+