Commit | Line | Data |
---|---|---|
1795efcd TN |
1 | #!/usr/bin/python |
2 | # -*- coding: utf-8 -*- | |
3 | ||
4 | import sys, traceback | |
5 | ||
a6627284 TN |
6 | # configuration (codes d'accès à la base MS-SQL) |
7 | sys.path.append('/home/thomas/public_html/') | |
8 | import rest_config | |
9 | ||
0a4c31d4 | 10 | # pour savoir quel objet interroger : Routes |
1795efcd | 11 | from routes import Mapper |
a6627284 | 12 | from routes.middleware import RoutesMiddleware |
81b8aba3 TN |
13 | # et analyse des paramètres (POST et query_string) pour filtrage |
14 | from paste.request import parse_formvars | |
1795efcd | 15 | |
a6627284 | 16 | # pour chercher les données sur MS-SQL |
1795efcd | 17 | from pymssql import connect |
a6627284 TN |
18 | |
19 | # pour afficher le résultat : jinja | |
1795efcd TN |
20 | from jinja import Environment, FileSystemLoader |
21 | from jinja.filters import stringfilter | |
0ae81148 | 22 | import jinja.exceptions |
85a89d99 TN |
23 | # on prepare un environnement jinja toujours disponible |
24 | ejinja = Environment(loader=FileSystemLoader('/home/thomas/public_html/')) | |
1795efcd | 25 | |
0a4c31d4 TN |
26 | # TODO systeme de cache : beaker |
27 | # from beaker.middleware import CacheMiddleware | |
1795efcd | 28 | |
a6627284 | 29 | # formats de sortie autorisés, et content-type correspondant |
d588d902 TN |
30 | # formats = { 'xml': 'application/xml', 'html': 'text/html', 'txt': 'text/plain', 'json': 'application/json', 'rss': 'application/rss+xml' } |
31 | formats = { 'xml': 'application/xml', 'html': 'text/html', 'txt': 'text/plain', 'json': 'text/plain', 'rss': 'application/rss+xml' } | |
1795efcd | 32 | |
0a4c31d4 | 33 | # les routes RESTful (cf http://routes.groovie.org/manual.html#restful-services) |
1795efcd | 34 | mapper = Mapper() |
0a4c31d4 TN |
35 | mapper.resource('demlog','demlog') |
36 | mapper.resource('comlog','comlog') | |
37 | mapper.resource('demdep','demdep') | |
38 | mapper.resource('comdep','comdep') | |
39 | mapper.resource('dempub','dempub') | |
40 | mapper.resource('compub','compub') | |
0a4c31d4 | 41 | mapper.resource('comare','comare') |
85a89d99 TN |
42 | mapper.resource('comarei','comare:(impl)',controller='comare') # pour les comarexxx où xxx est un code d'implantation |
43 | mapper.resource('comsre','comsre') | |
44 | mapper.resource('comsrei','comsre:(impl)',controller='comsre') | |
45 | mapper.resource('comxre','comxre') # comxre = comare + comsre | |
46 | mapper.resource('comxrei','comxre:(impl)',controller='comxre') | |
f6b772ed TN |
47 | mapper.resource('dem','dem') |
48 | mapper.resource('com','com') | |
1795efcd | 49 | |
85a89d99 | 50 | |
1795efcd | 51 | class objetsql(object): |
0a4c31d4 | 52 | """objet de base : dispose d'un accès à MS-SQL (lire les données) et d'un accès à jinja (rendu des données)""" |
1795efcd | 53 | def __init__(self, environ): |
1795efcd | 54 | self.environ = environ |
85a89d99 TN |
55 | parse_formvars(environ) # extraction des variables de query_string et POST |
56 | self.filters = environ['paste.parsed_formvars'][0].dict_of_lists() # puis traduction en dictionnaire {var1:[val1,val2], ...} | |
0ae81148 | 57 | def template(self,template): |
0ae81148 | 58 | self.outputformat = self.environ['wsgiorg.routing_args'][1].get('format','xml') |
85a89d99 | 59 | return ejinja.get_template('%s.%s' % (template,self.outputformat)) |
0ae81148 | 60 | def cursor(self): |
85a89d99 TN |
61 | if not hasattr(self,'db_connect'): |
62 | self.db_connect = connect(host=rest_config.host,user=rest_config.user,password=rest_config.password,database=rest_config.database) | |
63 | if not hasattr(self,'db_cursor'): | |
64 | self.db_cursor = self.db_connect.cursor() | |
65 | return self.db_cursor | |
66 | def __del__(self): | |
67 | # coupe la connexion à MSSQL à la mort de l'objet (normalement ça se fait tout seul mais j'en suis pas sûr sûr) | |
68 | if hasattr(self,'db_connect'): | |
69 | db_connect.close() | |
a6627284 | 70 | |
1795efcd | 71 | class document(objetsql): |
0a4c31d4 | 72 | """objet document CODA (demlog, comlog, demdep... ils ont tous le même format)""" |
85a89d99 TN |
73 | accept_like_filters = [ 'code', 'demandeur', 'approbateur', 'code_service', 'statut', 'fournisseur' ] |
74 | def __init__(self, environ, code_document='%', prefix_template='document'): | |
1795efcd TN |
75 | super(document, self).__init__(environ) |
76 | self.code_document = code_document | |
85a89d99 TN |
77 | self.prefix_template = prefix_template |
78 | # filtrage : on extrait la liste des filtres "acceptes" pour en faire des where | |
79 | sqlwhere = [] | |
80 | for filter in self.accept_like_filters: | |
81 | if filter in self.filters: | |
82 | or_list = [] | |
83 | for v in self.filters[filter]: | |
84 | or_list.append("%s like '%s'" % (filter,v)) | |
85 | sqlwhere.append('(' + ' or '.join(or_list) + ')') | |
86 | # TODO : ajouter filtres min_*/max_*, avant/apres dans ce sqlwhere | |
87 | self.sqlwhere = '' | |
88 | for f in sqlwhere: | |
89 | self.sqlwhere += ' and ' + f | |
90 | # filtrage : valeur en cas de limite ("top n" en ODBC), 50 maxi | |
91 | if 'limite' in self.filters: | |
92 | self.sqllimit = min( int(self.filters['limite'][0]), 50 ) | |
93 | else: | |
94 | self.sqllimit = 15 | |
95 | # TODO : ajouter filtres tri_asc/tri_desc | |
1795efcd | 96 | |
0ae81148 TN |
97 | def _get_index(self, code): |
98 | """renvoie une liste de documents""" | |
99 | # connexion a la base de données | |
100 | cursor = self.cursor() | |
101 | # extraction des données | |
85a89d99 | 102 | 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 ) ) |
0ae81148 | 103 | items = [] |
1795efcd | 104 | while 1: |
0ae81148 TN |
105 | item = dict_fetchone( cursor ) |
106 | if item == None: break | |
107 | item['code_rest'] = coda2rest(item['code']) | |
108 | items.append(item) | |
85a89d99 TN |
109 | index = {} |
110 | index['code'] = code | |
0ae81148 TN |
111 | index['documents'] = items |
112 | return index | |
113 | ||
114 | def _get_details(self,code,id): | |
f2897cb3 | 115 | """renvoie la liste des détails pour un document""" |
1795efcd | 116 | details = [] |
0ae81148 | 117 | cursor = self.cursor() |
85a89d99 | 118 | cursor.execute("select * from auf_v_acces_dtls_demcom where (code like '%s') and (numero = %d)" % (code, id)) |
1795efcd | 119 | while 1: |
0ae81148 | 120 | detail = dict_fetchone(cursor) |
1795efcd TN |
121 | if detail == None: break |
122 | details.append(detail) | |
0ae81148 TN |
123 | return details |
124 | ||
125 | def _get_document(self,code,id): | |
f2897cb3 | 126 | """renvoie un document""" |
0ae81148 | 127 | cursor = self.cursor() |
85a89d99 | 128 | cursor.execute("select top 1 * from auf_v_acces_demcom where (code like '%s') and (numero = %d) %s" % (code, id, self.sqlwhere)) |
0ae81148 TN |
129 | document = dict_fetchone(cursor) |
130 | if document == None: | |
131 | raise "document inexistant" | |
132 | document['code_rest'] = coda2rest( document['code'] ) | |
133 | document['details'] = self._get_details( code,id ) | |
134 | return document | |
135 | ||
136 | def index(self): | |
137 | """renvoie une liste de documents formatée""" | |
85a89d99 TN |
138 | template = self.template('%s-index' % self.prefix_template) # préparation du modèle |
139 | index = self._get_index( self.code_document ) # extraction des documents concernés | |
140 | output = template.render( index ) # formattage via le modèle | |
0ae81148 TN |
141 | return self.outputformat, output |
142 | ||
143 | def show(self): | |
144 | """renvoie un document formaté""" | |
145 | # return 'txt', '%s' % self.environ # pour debug | |
146 | id = int(self.environ['wsgiorg.routing_args'][1]['id']) | |
85a89d99 | 147 | template = self.template( self.prefix_template ) |
0ae81148 TN |
148 | document = self._get_document( self.code_document, id ) |
149 | output = template.render( document ) | |
1795efcd TN |
150 | return self.outputformat, output |
151 | ||
f6b772ed TN |
152 | class dem(document): |
153 | def __init__(self, environ): | |
154 | super(dem, self).__init__(environ, code_document = 'DEM-%') | |
155 | ||
156 | class com(document): | |
157 | def __init__(self, environ): | |
158 | super(com, self).__init__(environ, code_document = 'COM-%') | |
159 | ||
1795efcd TN |
160 | class demlog(document): |
161 | def __init__(self, environ): | |
162 | super(demlog, self).__init__(environ, code_document = 'DEM-LOG-AUF') | |
163 | ||
164 | class comlog(document): | |
165 | def __init__(self, environ): | |
166 | super(comlog, self).__init__(environ, code_document = 'COM-LOG-AUF') | |
167 | ||
0a4c31d4 TN |
168 | class demdep(document): |
169 | def __init__(self, environ): | |
170 | super(demdep, self).__init__(environ, code_document = 'DEM-DEP-AUF') | |
171 | ||
172 | class comdep(document): | |
173 | def __init__(self, environ): | |
174 | super(comdep, self).__init__(environ, code_document = 'COM-DEP-AUF') | |
175 | ||
176 | class dempub(document): | |
177 | def __init__(self, environ): | |
178 | super(dempub, self).__init__(environ, code_document = 'DEM-PUB-AUF') | |
179 | ||
180 | class compub(document): | |
181 | def __init__(self, environ): | |
182 | super(compub, self).__init__(environ, code_document = 'COM-PUB-AUF') | |
183 | ||
0a4c31d4 TN |
184 | class comsre(document): |
185 | def __init__(self, environ): | |
85a89d99 | 186 | # on distingue comsre et comsrexxx (avec xxx = implantation) |
db1681c0 TN |
187 | impl = environ['wsgiorg.routing_args'][1].get('impl','%') |
188 | super(comsre, self).__init__(environ, code_document = 'COM-SRE-%s' % impl) | |
0a4c31d4 TN |
189 | |
190 | class comare(document): | |
191 | def __init__(self, environ): | |
db1681c0 TN |
192 | impl = environ['wsgiorg.routing_args'][1].get('impl','%') |
193 | super(comare, self).__init__(environ, code_document = 'COM-ARE-%s' % impl) | |
0a4c31d4 | 194 | |
85a89d99 TN |
195 | class comxre(document): |
196 | def __init__(self, environ): | |
197 | impl = environ['wsgiorg.routing_args'][1].get('impl','%') | |
198 | super(comxre, self).__init__(environ, code_document = 'COM-%%RE-%s' % impl) | |
199 | ||
0ae81148 TN |
200 | # |
201 | # fin des objets accessibles | |
202 | # | |
203 | ||
a6627284 | 204 | def dispatcher(environ, start_response): |
81b8aba3 | 205 | """dispatch vers la bonne methode du bon objet, et retour WSGI""" |
a6627284 | 206 | results = environ['wsgiorg.routing_args'][1] |
1795efcd TN |
207 | try: |
208 | target_class = globals()[results['controller']] | |
0ae81148 TN |
209 | method = getattr(target_class,results['action']) |
210 | except: | |
211 | start_response("404 Not Found", [('Content-type', 'text/html')]) | |
212 | 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()) | |
213 | try: | |
1795efcd TN |
214 | type, output = method(target_class(environ)) |
215 | start_response("200 OK", [('Content-type', formats[type])]) | |
216 | return output.encode('utf-8') | |
0ae81148 TN |
217 | except jinja.exceptions.TemplateNotFound, template: |
218 | start_response("415 Unsupported Media Type", [('Content-type', 'text/html')]) | |
219 | return '<html><body><h2>415 format non supporté (%s inexistant)</h2></body></html>' % template | |
1795efcd | 220 | except: |
0ae81148 TN |
221 | start_response("500 INTERNAL ERROR", [('Content-type', 'text/html')]) |
222 | 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()) | |
1795efcd | 223 | |
81b8aba3 | 224 | # application qui sera lancée par mod_wsgi : on route et on dispatche |
a6627284 | 225 | application = RoutesMiddleware( dispatcher, mapper) |
0ae81148 | 226 | # TODO : ajouter un middleware de cache (beaker, basé sur REQUEST_URI quand la methode est GET) |
1795efcd | 227 | |
0a4c31d4 TN |
228 | # |
229 | # petits utilitaires | |
230 | # | |
1795efcd TN |
231 | def dict_fetchone(cursor): |
232 | """Renvoie le resultat d'un fetchone dans un dictionnaire""" | |
233 | result = cursor.fetchone() | |
234 | if result == None: return None | |
235 | result_dict = {} | |
236 | for i in range(len(result)): | |
237 | if isinstance( result[i], str ): | |
238 | result_dict[cursor.description[i][0]] = result[i].decode('iso-8859-1') | |
239 | else: | |
240 | result_dict[cursor.description[i][0]] = result[i] | |
241 | return result_dict | |
242 | ||
a6627284 | 243 | import re |
db1681c0 | 244 | p = re.compile('(dem|com)-(...)-(...)',re.IGNORECASE) |
a6627284 | 245 | def coda2rest(value): |
db1681c0 TN |
246 | """Traduit un nom CODA vers la base REST correspodante, |
247 | par exemple DEM-LOG-AUF en demlog ou COM-ARE-VN3 en comarevn3""" | |
a6627284 TN |
248 | m = p.search(value) |
249 | if m == None: return value | |
db1681c0 TN |
250 | if m.group(3).lower() == 'auf': |
251 | return m.group(1).lower() + m.group(2).lower() | |
252 | else: | |
253 | return m.group(1).lower() + m.group(2).lower() + m.group(3).lower() | |
a6627284 | 254 |