[IMP]:removed salesman assigned to demo data partners
[odoo/odoo.git] / openerp / service / web_services.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import base64
23 import locale
24 import logging
25 import os
26 import platform
27 import security
28 import sys
29 import thread
30 import threading
31 import time
32 import traceback
33 from cStringIO import StringIO
34 from openerp.tools.translate import _
35 import openerp.netsvc as netsvc
36 import openerp.pooler as pooler
37 import openerp.release as release
38 import openerp.sql_db as sql_db
39 import openerp.tools as tools
40 import openerp.modules
41 import openerp.exceptions
42
43 #.apidoc title: Exported Service methods
44 #.apidoc module-mods: member-order: bysource
45
46 """ This python module defines the RPC methods available to remote clients.
47
48     Each 'Export Service' is a group of 'methods', which in turn are RPC
49     procedures to be called. Each method has its own arguments footprint.
50 """
51
52 RPC_VERSION_1 = {'server_version': '6.1', 'protocol_version': 1}
53
54 # This should be moved to openerp.modules.db, along side initialize().
55 def _initialize_db(serv, id, db_name, demo, lang, user_password):
56     cr = None
57     try:
58         serv.actions[id]['progress'] = 0
59         cr = sql_db.db_connect(db_name).cursor()
60         openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by pooler.restart_pool.
61         tools.config['lang'] = lang
62         cr.commit()
63         cr.close()
64
65         pool = pooler.restart_pool(db_name, demo, serv.actions[id],
66                                    update_module=True)[1]
67
68         cr = sql_db.db_connect(db_name).cursor()
69
70         if lang:
71             modobj = pool.get('ir.module.module')
72             mids = modobj.search(cr, 1, [('state', '=', 'installed')])
73             modobj.update_translations(cr, 1, mids, lang)
74
75         cr.execute('UPDATE res_users SET password=%s, context_lang=%s, active=True WHERE login=%s', (
76             user_password, lang, 'admin'))
77         cr.execute('SELECT login, password, name ' \
78                    '  FROM res_users ' \
79                    ' ORDER BY login')
80         serv.actions[id].update(users=cr.dictfetchall(), clean=True)
81         cr.commit()
82         cr.close()
83     except Exception, e:
84         serv.actions[id].update(clean=False, exception=e)
85         logging.getLogger('db.create').exception('CREATE DATABASE failed:')
86         serv.actions[id]['traceback'] = traceback.format_exc()
87         if cr:
88             cr.close()
89
90 class db(netsvc.ExportService):
91     def __init__(self, name="db"):
92         netsvc.ExportService.__init__(self, name)
93         self.actions = {}
94         self.id = 0
95         self.id_protect = threading.Semaphore()
96
97         self._pg_psw_env_var_is_set = False # on win32, pg_dump need the PGPASSWORD env var
98
99     def dispatch(self, method, params):
100         if method in [ 'create', 'get_progress', 'drop', 'dump',
101             'restore', 'rename',
102             'change_admin_password', 'migrate_databases',
103             'create_database' ]:
104             passwd = params[0]
105             params = params[1:]
106             security.check_super(passwd)
107         elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]:
108             # params = params
109             # No security check for these methods
110             pass
111         else:
112             raise KeyError("Method not found: %s" % method)
113         fn = getattr(self, 'exp_'+method)
114         return fn(*params)
115
116     def _create_empty_database(self, name):
117         db = sql_db.db_connect('template1')
118         cr = db.cursor()
119         try:
120             cr.autocommit(True) # avoid transaction block
121             cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "template0" """ % name)
122         finally:
123             cr.close()
124
125     def exp_create(self, db_name, demo, lang, user_password='admin'):
126         self.id_protect.acquire()
127         self.id += 1
128         id = self.id
129         self.id_protect.release()
130
131         self.actions[id] = {'clean': False}
132
133         self._create_empty_database(db_name)
134
135         logging.getLogger('db.create').info('CREATE DATABASE %s', db_name.lower())
136         create_thread = threading.Thread(target=_initialize_db,
137                 args=(self, id, db_name, demo, lang, user_password))
138         create_thread.start()
139         self.actions[id]['thread'] = create_thread
140         return id
141
142     def exp_create_database(self, db_name, demo, lang, user_password='admin'):
143         """ Similar to exp_create but blocking."""
144         self.id_protect.acquire()
145         self.id += 1
146         id = self.id
147         self.id_protect.release()
148
149         self.actions[id] = {'clean': False}
150
151         logging.getLogger('db.create').info('CREATE DATABASE %s', db_name.lower())
152         self._create_empty_database(db_name)
153         _initialize_db(self, id, db_name, demo, lang, user_password)
154         return True
155
156     def exp_get_progress(self, id):
157         if self.actions[id]['thread'].isAlive():
158 #           return openerp.modules.init_progress[db_name]
159             return (min(self.actions[id].get('progress', 0),0.95), [])
160         else:
161             clean = self.actions[id]['clean']
162             if clean:
163                 users = self.actions[id]['users']
164                 self.actions.pop(id)
165                 return (1.0, users)
166             else:
167                 e = self.actions[id]['exception'] # TODO this seems wrong: actions[id]['traceback'] is set, but not 'exception'.
168                 self.actions.pop(id)
169                 raise Exception, e
170
171     def exp_drop(self, db_name):
172         openerp.modules.registry.RegistryManager.delete(db_name)
173         sql_db.close_db(db_name)
174         logger = netsvc.Logger()
175
176         db = sql_db.db_connect('template1')
177         cr = db.cursor()
178         cr.autocommit(True) # avoid transaction block
179         try:
180             try:
181                 cr.execute('DROP DATABASE "%s"' % db_name)
182             except Exception, e:
183                 logger.notifyChannel("web-services", netsvc.LOG_ERROR,
184                         'DROP DB: %s failed:\n%s' % (db_name, e))
185                 raise Exception("Couldn't drop database %s: %s" % (db_name, e))
186             else:
187                 logger.notifyChannel("web-services", netsvc.LOG_INFO,
188                     'DROP DB: %s' % (db_name))
189         finally:
190             cr.close()
191         return True
192
193     def _set_pg_psw_env_var(self):
194         if os.name == 'nt' and not os.environ.get('PGPASSWORD', ''):
195             os.environ['PGPASSWORD'] = tools.config['db_password']
196             self._pg_psw_env_var_is_set = True
197
198     def _unset_pg_psw_env_var(self):
199         if os.name == 'nt' and self._pg_psw_env_var_is_set:
200             os.environ['PGPASSWORD'] = ''
201
202     def exp_dump(self, db_name):
203         logger = netsvc.Logger()
204
205         self._set_pg_psw_env_var()
206
207         cmd = ['pg_dump', '--format=c', '--no-owner']
208         if tools.config['db_user']:
209             cmd.append('--username=' + tools.config['db_user'])
210         if tools.config['db_host']:
211             cmd.append('--host=' + tools.config['db_host'])
212         if tools.config['db_port']:
213             cmd.append('--port=' + str(tools.config['db_port']))
214         cmd.append(db_name)
215
216         stdin, stdout = tools.exec_pg_command_pipe(*tuple(cmd))
217         stdin.close()
218         data = stdout.read()
219         res = stdout.close()
220         if res:
221             logger.notifyChannel("web-services", netsvc.LOG_ERROR,
222                     'DUMP DB: %s failed\n%s' % (db_name, data))
223             raise Exception, "Couldn't dump database"
224         logger.notifyChannel("web-services", netsvc.LOG_INFO,
225                 'DUMP DB: %s' % (db_name))
226
227         self._unset_pg_psw_env_var()
228
229         return base64.encodestring(data)
230
231     def exp_restore(self, db_name, data):
232         logger = netsvc.Logger()
233
234         self._set_pg_psw_env_var()
235
236         if self.exp_db_exist(db_name):
237             logger.notifyChannel("web-services", netsvc.LOG_WARNING,
238                     'RESTORE DB: %s already exists' % (db_name,))
239             raise Exception, "Database already exists"
240
241         self._create_empty_database(db_name)
242
243         cmd = ['pg_restore', '--no-owner']
244         if tools.config['db_user']:
245             cmd.append('--username=' + tools.config['db_user'])
246         if tools.config['db_host']:
247             cmd.append('--host=' + tools.config['db_host'])
248         if tools.config['db_port']:
249             cmd.append('--port=' + str(tools.config['db_port']))
250         cmd.append('--dbname=' + db_name)
251         args2 = tuple(cmd)
252
253         buf=base64.decodestring(data)
254         if os.name == "nt":
255             tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam()
256             file(tmpfile, 'wb').write(buf)
257             args2=list(args2)
258             args2.append(' ' + tmpfile)
259             args2=tuple(args2)
260         stdin, stdout = tools.exec_pg_command_pipe(*args2)
261         if not os.name == "nt":
262             stdin.write(base64.decodestring(data))
263         stdin.close()
264         res = stdout.close()
265         if res:
266             raise Exception, "Couldn't restore database"
267         logger.notifyChannel("web-services", netsvc.LOG_INFO,
268                 'RESTORE DB: %s' % (db_name))
269
270         self._unset_pg_psw_env_var()
271
272         return True
273
274     def exp_rename(self, old_name, new_name):
275         openerp.modules.registry.RegistryManager.delete(old_name)
276         sql_db.close_db(old_name)
277         logger = netsvc.Logger()
278
279         db = sql_db.db_connect('template1')
280         cr = db.cursor()
281         cr.autocommit(True) # avoid transaction block
282         try:
283             try:
284                 cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
285             except Exception, e:
286                 logger.notifyChannel("web-services", netsvc.LOG_ERROR,
287                         'RENAME DB: %s -> %s failed:\n%s' % (old_name, new_name, e))
288                 raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e))
289             else:
290                 fs = os.path.join(tools.config['root_path'], 'filestore')
291                 if os.path.exists(os.path.join(fs, old_name)):
292                     os.rename(os.path.join(fs, old_name), os.path.join(fs, new_name))
293
294                 logger.notifyChannel("web-services", netsvc.LOG_INFO,
295                     'RENAME DB: %s -> %s' % (old_name, new_name))
296         finally:
297             cr.close()
298         return True
299
300     def exp_db_exist(self, db_name):
301         ## Not True: in fact, check if connection to database is possible. The database may exists
302         return bool(sql_db.db_connect(db_name))
303
304     def exp_list(self, document=False):
305         if not tools.config['list_db'] and not document:
306             raise openerp.exceptions.AccessDenied()
307
308         db = sql_db.db_connect('template1')
309         cr = db.cursor()
310         try:
311             try:
312                 db_user = tools.config["db_user"]
313                 if not db_user and os.name == 'posix':
314                     import pwd
315                     db_user = pwd.getpwuid(os.getuid())[0]
316                 if not db_user:
317                     cr.execute("select decode(usename, 'escape') from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (tools.config["db_name"],))
318                     res = cr.fetchone()
319                     db_user = res and str(res[0])
320                 if db_user:
321                     cr.execute("select decode(datname, 'escape') from pg_database where datdba=(select usesysid from pg_user where usename=%s) and datname not in ('template0', 'template1', 'postgres') order by datname", (db_user,))
322                 else:
323                     cr.execute("select decode(datname, 'escape') from pg_database where datname not in('template0', 'template1','postgres') order by datname")
324                 res = [str(name) for (name,) in cr.fetchall()]
325             except Exception:
326                 res = []
327         finally:
328             cr.close()
329         res.sort()
330         return res
331
332     def exp_change_admin_password(self, new_password):
333         tools.config['admin_passwd'] = new_password
334         tools.config.save()
335         return True
336
337     def exp_list_lang(self):
338         return tools.scan_languages()
339
340     def exp_server_version(self):
341         """ Return the version of the server
342             Used by the client to verify the compatibility with its own version
343         """
344         return release.version
345
346     def exp_migrate_databases(self,databases):
347
348         from openerp.osv.orm import except_orm
349         from openerp.osv.osv import except_osv
350
351         l = netsvc.Logger()
352         for db in databases:
353             try:
354                 l.notifyChannel('migration', netsvc.LOG_INFO, 'migrate database %s' % (db,))
355                 tools.config['update']['base'] = True
356                 pooler.restart_pool(db, force_demo=False, update_module=True)
357             except except_orm, inst:
358                 netsvc.abort_response(1, inst.name, 'warning', inst.value)
359             except except_osv, inst:
360                 netsvc.abort_response(1, inst.name, 'warning', inst.value)
361             except Exception:
362                 import traceback
363                 tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
364                 l.notifyChannel('web-services', netsvc.LOG_ERROR, tb_s)
365                 raise
366         return True
367
368 class common(netsvc.ExportService):
369     _logger = logging.getLogger('web-services')
370
371     def __init__(self,name="common"):
372         netsvc.ExportService.__init__(self,name)
373
374     def dispatch(self, method, params):
375         if method in ['login', 'about', 'timezone_get', 'get_server_environment',
376                       'login_message','get_stats', 'check_connectivity',
377                       'list_http_services', 'version', 'authenticate']:
378             pass
379         elif method in ['get_available_updates', 'get_migration_scripts', 'set_loglevel', 'get_os_time', 'get_sqlcount']:
380             passwd = params[0]
381             params = params[1:]
382             security.check_super(passwd)
383         else:
384             raise Exception("Method not found: %s" % method)
385
386         fn = getattr(self, 'exp_'+method)
387         return fn(*params)
388
389     def exp_login(self, db, login, password):
390         # TODO: legacy indirection through 'security', should use directly
391         # the res.users model
392         res = security.login(db, login, password)
393         msg = res and 'successful login' or 'bad login or password'
394         self._logger.info("%s from '%s' using database '%s'", msg, login, db.lower())
395         return res or False
396
397     def exp_authenticate(self, db, login, password, user_agent_env):
398         res_users = pooler.get_pool(db).get('res.users')
399         return res_users.authenticate(db, login, password, user_agent_env)
400
401     def exp_version(self):
402         return RPC_VERSION_1
403
404     def exp_about(self, extended=False):
405         """Return information about the OpenERP Server.
406
407         @param extended: if True then return version info
408         @return string if extended is False else tuple
409         """
410
411         info = _('''
412
413 OpenERP is an ERP+CRM program for small and medium businesses.
414
415 The whole source code is distributed under the terms of the
416 GNU Public Licence.
417
418 (c) 2003-TODAY, Fabien Pinckaers - Tiny sprl''')
419
420         if extended:
421             return info, release.version
422         return info
423
424     def exp_timezone_get(self, db, login, password):
425         return tools.misc.get_server_timezone()
426
427     def exp_get_available_updates(self, contract_id, contract_password):
428         import openerp.tools.maintenance as tm
429         try:
430             rc = tm.remote_contract(contract_id, contract_password)
431             if not rc.id:
432                 raise tm.RemoteContractException('This contract does not exist or is not active')
433
434             return rc.get_available_updates(rc.id, openerp.modules.get_modules_with_version())
435
436         except tm.RemoteContractException, e:
437             netsvc.abort_response(1, 'Migration Error', 'warning', str(e))
438
439
440     def exp_get_migration_scripts(self, contract_id, contract_password):
441         l = netsvc.Logger()
442         import openerp.tools.maintenance as tm
443         try:
444             rc = tm.remote_contract(contract_id, contract_password)
445             if not rc.id:
446                 raise tm.RemoteContractException('This contract does not exist or is not active')
447             if rc.status != 'full':
448                 raise tm.RemoteContractException('Can not get updates for a partial contract')
449
450             l.notifyChannel('migration', netsvc.LOG_INFO, 'starting migration with contract %s' % (rc.name,))
451
452             zips = rc.retrieve_updates(rc.id, openerp.modules.get_modules_with_version())
453
454             from shutil import rmtree, copytree, copy
455
456             backup_directory = os.path.join(tools.config['root_path'], 'backup', time.strftime('%Y-%m-%d-%H-%M'))
457             if zips and not os.path.isdir(backup_directory):
458                 l.notifyChannel('migration', netsvc.LOG_INFO, 'create a new backup directory to \
459                                 store the old modules: %s' % (backup_directory,))
460                 os.makedirs(backup_directory)
461
462             for module in zips:
463                 l.notifyChannel('migration', netsvc.LOG_INFO, 'upgrade module %s' % (module,))
464                 mp = openerp.modules.get_module_path(module)
465                 if mp:
466                     if os.path.isdir(mp):
467                         copytree(mp, os.path.join(backup_directory, module))
468                         if os.path.islink(mp):
469                             os.unlink(mp)
470                         else:
471                             rmtree(mp)
472                     else:
473                         copy(mp + 'zip', backup_directory)
474                         os.unlink(mp + '.zip')
475
476                 try:
477                     try:
478                         base64_decoded = base64.decodestring(zips[module])
479                     except Exception:
480                         l.notifyChannel('migration', netsvc.LOG_ERROR, 'unable to read the module %s' % (module,))
481                         raise
482
483                     zip_contents = StringIO(base64_decoded)
484                     zip_contents.seek(0)
485                     try:
486                         try:
487                             tools.extract_zip_file(zip_contents, tools.config['addons_path'] )
488                         except Exception:
489                             l.notifyChannel('migration', netsvc.LOG_ERROR, 'unable to extract the module %s' % (module, ))
490                             rmtree(module)
491                             raise
492                     finally:
493                         zip_contents.close()
494                 except Exception:
495                     l.notifyChannel('migration', netsvc.LOG_ERROR, 'restore the previous version of the module %s' % (module, ))
496                     nmp = os.path.join(backup_directory, module)
497                     if os.path.isdir(nmp):
498                         copytree(nmp, tools.config['addons_path'])
499                     else:
500                         copy(nmp+'.zip', tools.config['addons_path'])
501                     raise
502
503             return True
504         except tm.RemoteContractException, e:
505             netsvc.abort_response(1, 'Migration Error', 'warning', str(e))
506         except Exception, e:
507             import traceback
508             tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
509             l.notifyChannel('migration', netsvc.LOG_ERROR, tb_s)
510             raise
511
512     def exp_get_server_environment(self):
513         os_lang = '.'.join( [x for x in locale.getdefaultlocale() if x] )
514         if not os_lang:
515             os_lang = 'NOT SET'
516         environment = '\nEnvironment Information : \n' \
517                      'System : %s\n' \
518                      'OS Name : %s\n' \
519                      %(platform.platform(), platform.os.name)
520         if os.name == 'posix':
521           if platform.system() == 'Linux':
522              lsbinfo = os.popen('lsb_release -a').read()
523              environment += '%s'%(lsbinfo)
524           else:
525              environment += 'Your System is not lsb compliant\n'
526         environment += 'Operating System Release : %s\n' \
527                     'Operating System Version : %s\n' \
528                     'Operating System Architecture : %s\n' \
529                     'Operating System Locale : %s\n'\
530                     'Python Version : %s\n'\
531                     'OpenERP-Server Version : %s'\
532                     %(platform.release(), platform.version(), platform.architecture()[0],
533                       os_lang, platform.python_version(),release.version)
534         return environment
535
536     def exp_login_message(self):
537         return tools.config.get('login_message', False)
538
539     def exp_set_loglevel(self, loglevel, logger=None):
540         l = netsvc.Logger()
541         l.set_loglevel(int(loglevel), logger)
542         return True
543
544     def exp_get_stats(self):
545         import threading
546         res = "OpenERP server: %d threads\n" % threading.active_count()
547         res += netsvc.Server.allStats()
548         return res
549
550     def exp_list_http_services(self):
551         return http_server.list_http_services()
552
553     def exp_check_connectivity(self):
554         return bool(sql_db.db_connect('template1'))
555
556     def exp_get_os_time(self):
557         return os.times()
558
559     def exp_get_sqlcount(self):
560         logger = logging.getLogger('db.cursor')
561         if not logger.isEnabledFor(logging.DEBUG_SQL):
562             logger.warning("Counters of SQL will not be reliable unless DEBUG_SQL is set at the server's config.")
563         return sql_db.sql_counter
564
565
566 class objects_proxy(netsvc.ExportService):
567     def __init__(self, name="object"):
568         netsvc.ExportService.__init__(self,name)
569
570     def dispatch(self, method, params):
571         (db, uid, passwd ) = params[0:3]
572         params = params[3:]
573         if method == 'obj_list':
574             raise NameError("obj_list has been discontinued via RPC as of 6.0, please query ir.model directly!")
575         if method not in ['execute','exec_workflow']:
576             raise NameError("Method not available %s" % method)
577         security.check(db,uid,passwd)
578         assert openerp.osv.osv.service, "The object_proxy class must be started with start_object_proxy."
579         fn = getattr(openerp.osv.osv.service, method)
580         res = fn(db, uid, *params)
581         return res
582
583
584 #
585 # Wizard ID: 1
586 #    - None = end of wizard
587 #
588 # Wizard Type: 'form'
589 #    - form
590 #    - print
591 #
592 # Wizard datas: {}
593 # TODO: change local request to OSE request/reply pattern
594 #
595 class wizard(netsvc.ExportService):
596     def __init__(self, name='wizard'):
597         netsvc.ExportService.__init__(self,name)
598         self.id = 0
599         self.wiz_datas = {}
600         self.wiz_name = {}
601         self.wiz_uid = {}
602
603     def dispatch(self, method, params):
604         (db, uid, passwd ) = params[0:3]
605         params = params[3:]
606         if method not in ['execute','create']:
607             raise KeyError("Method not supported %s" % method)
608         security.check(db,uid,passwd)
609         fn = getattr(self, 'exp_'+method)
610         res = fn(db, uid, *params)
611         return res
612
613     def _execute(self, db, uid, wiz_id, datas, action, context):
614         self.wiz_datas[wiz_id].update(datas)
615         wiz = netsvc.LocalService('wizard.'+self.wiz_name[wiz_id])
616         return wiz.execute(db, uid, self.wiz_datas[wiz_id], action, context)
617
618     def exp_create(self, db, uid, wiz_name, datas=None):
619         if not datas:
620             datas={}
621 #FIXME: this is not thread-safe
622         self.id += 1
623         self.wiz_datas[self.id] = {}
624         self.wiz_name[self.id] = wiz_name
625         self.wiz_uid[self.id] = uid
626         return self.id
627
628     def exp_execute(self, db, uid, wiz_id, datas, action='init', context=None):
629         if not context:
630             context={}
631
632         if wiz_id in self.wiz_uid:
633             if self.wiz_uid[wiz_id] == uid:
634                 return self._execute(db, uid, wiz_id, datas, action, context)
635             else:
636                 raise openerp.exceptions.AccessDenied()
637         else:
638             raise openerp.exceptions.Warning('Wizard not found.')
639
640 #
641 # TODO: set a maximum report number per user to avoid DOS attacks
642 #
643 # Report state:
644 #     False -> True
645 #
646
647 class report_spool(netsvc.ExportService):
648     def __init__(self, name='report'):
649         netsvc.ExportService.__init__(self, name)
650         self._reports = {}
651         self.id = 0
652         self.id_protect = threading.Semaphore()
653
654     def dispatch(self, method, params):
655         (db, uid, passwd ) = params[0:3]
656         params = params[3:]
657         if method not in ['report', 'report_get', 'render_report']:
658             raise KeyError("Method not supported %s" % method)
659         security.check(db,uid,passwd)
660         fn = getattr(self, 'exp_' + method)
661         res = fn(db, uid, *params)
662         return res
663
664     def exp_render_report(self, db, uid, object, ids, datas=None, context=None):
665         if not datas:
666             datas={}
667         if not context:
668             context={}
669
670         self.id_protect.acquire()
671         self.id += 1
672         id = self.id
673         self.id_protect.release()
674
675         self._reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None}
676
677         cr = pooler.get_db(db).cursor()
678         import traceback
679         import sys
680         try:
681             obj = netsvc.LocalService('report.'+object)
682             (result, format) = obj.create(cr, uid, ids, datas, context)
683             if not result:
684                 tb = sys.exc_info()
685                 self._reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb)
686             self._reports[id]['result'] = result
687             self._reports[id]['format'] = format
688             self._reports[id]['state'] = True
689         except Exception, exception:
690
691             tb = sys.exc_info()
692             tb_s = "".join(traceback.format_exception(*tb))
693             logger = netsvc.Logger()
694             logger.notifyChannel('web-services', netsvc.LOG_ERROR,
695                     'Exception: %s\n%s' % (str(exception), tb_s))
696             if hasattr(exception, 'name') and hasattr(exception, 'value'):
697                 self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value))
698             else:
699                 self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb)
700             self._reports[id]['state'] = True
701         cr.commit()
702         cr.close()
703
704         return self._check_report(id)
705
706     def exp_report(self, db, uid, object, ids, datas=None, context=None):
707         if not datas:
708             datas={}
709         if not context:
710             context={}
711
712         self.id_protect.acquire()
713         self.id += 1
714         id = self.id
715         self.id_protect.release()
716
717         self._reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None}
718
719         def go(id, uid, ids, datas, context):
720             cr = pooler.get_db(db).cursor()
721             import traceback
722             import sys
723             try:
724                 obj = netsvc.LocalService('report.'+object)
725                 (result, format) = obj.create(cr, uid, ids, datas, context)
726                 if not result:
727                     tb = sys.exc_info()
728                     self._reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb)
729                 self._reports[id]['result'] = result
730                 self._reports[id]['format'] = format
731                 self._reports[id]['state'] = True
732             except Exception, exception:
733
734                 tb = sys.exc_info()
735                 tb_s = "".join(traceback.format_exception(*tb))
736                 logger = netsvc.Logger()
737                 logger.notifyChannel('web-services', netsvc.LOG_ERROR,
738                         'Exception: %s\n%s' % (str(exception), tb_s))
739                 if hasattr(exception, 'name') and hasattr(exception, 'value'):
740                     self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value))
741                 else:
742                     self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb)
743                 self._reports[id]['state'] = True
744             cr.commit()
745             cr.close()
746             return True
747
748         thread.start_new_thread(go, (id, uid, ids, datas, context))
749         return id
750
751     def _check_report(self, report_id):
752         result = self._reports[report_id]
753         exc = result['exception']
754         if exc:
755             netsvc.abort_response(exc, exc.message, 'warning', exc.traceback)
756         res = {'state': result['state']}
757         if res['state']:
758             if tools.config['reportgz']:
759                 import zlib
760                 res2 = zlib.compress(result['result'])
761                 res['code'] = 'zlib'
762             else:
763                 #CHECKME: why is this needed???
764                 if isinstance(result['result'], unicode):
765                     res2 = result['result'].encode('latin1', 'replace')
766                 else:
767                     res2 = result['result']
768             if res2:
769                 res['result'] = base64.encodestring(res2)
770             res['format'] = result['format']
771             del self._reports[report_id]
772         return res
773
774     def exp_report_get(self, db, uid, report_id):
775         if report_id in self._reports:
776             if self._reports[report_id]['uid'] == uid:
777                 return self._check_report(report_id)
778             else:
779                 raise Exception, 'AccessDenied'
780         else:
781             raise Exception, 'ReportNotFound'
782
783
784 def start_web_services():
785     db()
786     common()
787     objects_proxy()
788     wizard()
789     report_spool()
790
791
792 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
793