1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
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
43 class edi(netsvc.ExportService):
44 def exp_get_edi_document(self, edi_token, db_name):
47 cr = pooler.get_db(db_name).cursor()
49 raise Exception("No database cursor found!")
50 pool = pooler.get_pool(db_name)
51 edi_pool = pool.get('ir.edi.document')
53 res = edi_pool.get_document(cr, 1, edi_token)
58 def exp_import_edi_document(self, db, uid, passwd, edi_document, context=None):
60 cr = pooler.get_db(db).cursor()
61 pool = pooler.get_pool(db)
62 edi_pool = pool.get('ir.edi.document')
64 res = edi_pool.import_edi(cr, uid, edi_document=edi_document, context=context)
67 print traceback.format_exc()
73 def exp_import_edi_url(self, db, uid, passwd, edi_url, context=None):
75 cr = pooler.get_db(db).cursor()
76 pool = pooler.get_pool(db)
77 edi_pool = pool.get('ir.edi.document')
79 res = edi_pool.import_edi(cr, uid, edi_url=edi_url, context=context)
82 print traceback.format_exc()
88 def __init__(self, name="edi"):
89 netsvc.ExportService.__init__(self, name)
90 self.joinGroup("web-services")
92 def dispatch(self, method, auth, params):
93 if method in ['import_edi_document', 'import_edi_url']:
94 (db, uid, passwd ) = params[0:3]
95 security.check(db, uid, passwd)
96 elif method in ['get_edi_document']:
98 # No security check for these methods
101 raise KeyError("Method not found: %s" % method)
102 fn = getattr(self, 'exp_'+method)
105 #.apidoc title: Exported Service methods
106 #.apidoc module-mods: member-order: bysource
108 """ This python module defines the RPC methods available to remote clients.
110 Each 'Export Service' is a group of 'methods', which in turn are RPC
111 procedures to be called. Each method has its own arguments footprint.
114 # This should be moved to openerp.modules.db, along side initialize().
115 def _initialize_db(serv, id, db_name, demo, lang, user_password):
118 serv.actions[id]['progress'] = 0
119 cr = sql_db.db_connect(db_name).cursor()
120 openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by pooler.restart_pool.
121 tools.config['lang'] = lang
125 pool = pooler.restart_pool(db_name, demo, serv.actions[id],
126 update_module=True)[1]
128 cr = sql_db.db_connect(db_name).cursor()
131 modobj = pool.get('ir.module.module')
132 mids = modobj.search(cr, 1, [('state', '=', 'installed')])
133 modobj.update_translations(cr, 1, mids, lang)
135 cr.execute('UPDATE res_users SET password=%s, context_lang=%s, active=True WHERE login=%s', (
136 user_password, lang, 'admin'))
137 cr.execute('SELECT login, password, name ' \
140 serv.actions[id].update(users=cr.dictfetchall(), clean=True)
144 serv.actions[id].update(clean=False, exception=e)
145 logging.getLogger('db.create').exception('CREATE DATABASE failed:')
146 serv.actions[id]['traceback'] = traceback.format_exc()
150 class db(netsvc.ExportService):
151 def __init__(self, name="db"):
152 netsvc.ExportService.__init__(self, name)
155 self.id_protect = threading.Semaphore()
157 self._pg_psw_env_var_is_set = False # on win32, pg_dump need the PGPASSWORD env var
159 def dispatch(self, method, params):
160 if method in [ 'create', 'get_progress', 'drop', 'dump',
162 'change_admin_password', 'migrate_databases',
166 security.check_super(passwd)
167 elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]:
169 # No security check for these methods
172 raise KeyError("Method not found: %s" % method)
173 fn = getattr(self, 'exp_'+method)
176 def _create_empty_database(self, name):
177 db = sql_db.db_connect('template1')
180 cr.autocommit(True) # avoid transaction block
181 cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "template0" """ % name)
185 def exp_create(self, db_name, demo, lang, user_password='admin'):
186 self.id_protect.acquire()
189 self.id_protect.release()
191 self.actions[id] = {'clean': False}
193 self._create_empty_database(db_name)
195 logging.getLogger('db.create').info('CREATE DATABASE %s', db_name.lower())
196 create_thread = threading.Thread(target=_initialize_db,
197 args=(self, id, db_name, demo, lang, user_password))
198 create_thread.start()
199 self.actions[id]['thread'] = create_thread
202 def exp_create_database(self, db_name, demo, lang, user_password='admin'):
203 """ Similar to exp_create but blocking."""
204 self.id_protect.acquire()
207 self.id_protect.release()
209 self.actions[id] = {'clean': False}
211 logging.getLogger('db.create').info('CREATE DATABASE %s', db_name.lower())
212 self._create_empty_database(db_name)
213 _initialize_db(self, id, db_name, demo, lang, user_password)
216 def exp_get_progress(self, id):
217 if self.actions[id]['thread'].isAlive():
218 # return openerp.modules.init_progress[db_name]
219 return (min(self.actions[id].get('progress', 0),0.95), [])
221 clean = self.actions[id]['clean']
223 users = self.actions[id]['users']
227 e = self.actions[id]['exception'] # TODO this seems wrong: actions[id]['traceback'] is set, but not 'exception'.
231 def exp_drop(self, db_name):
232 openerp.modules.registry.RegistryManager.delete(db_name)
233 sql_db.close_db(db_name)
234 logger = netsvc.Logger()
236 db = sql_db.db_connect('template1')
238 cr.autocommit(True) # avoid transaction block
241 cr.execute('DROP DATABASE "%s"' % db_name)
243 logger.notifyChannel("web-services", netsvc.LOG_ERROR,
244 'DROP DB: %s failed:\n%s' % (db_name, e))
245 raise Exception("Couldn't drop database %s: %s" % (db_name, e))
247 logger.notifyChannel("web-services", netsvc.LOG_INFO,
248 'DROP DB: %s' % (db_name))
253 def _set_pg_psw_env_var(self):
254 if os.name == 'nt' and not os.environ.get('PGPASSWORD', ''):
255 os.environ['PGPASSWORD'] = tools.config['db_password']
256 self._pg_psw_env_var_is_set = True
258 def _unset_pg_psw_env_var(self):
259 if os.name == 'nt' and self._pg_psw_env_var_is_set:
260 os.environ['PGPASSWORD'] = ''
262 def exp_dump(self, db_name):
263 logger = netsvc.Logger()
265 self._set_pg_psw_env_var()
267 cmd = ['pg_dump', '--format=c', '--no-owner']
268 if tools.config['db_user']:
269 cmd.append('--username=' + tools.config['db_user'])
270 if tools.config['db_host']:
271 cmd.append('--host=' + tools.config['db_host'])
272 if tools.config['db_port']:
273 cmd.append('--port=' + str(tools.config['db_port']))
276 stdin, stdout = tools.exec_pg_command_pipe(*tuple(cmd))
281 logger.notifyChannel("web-services", netsvc.LOG_ERROR,
282 'DUMP DB: %s failed\n%s' % (db_name, data))
283 raise Exception, "Couldn't dump database"
284 logger.notifyChannel("web-services", netsvc.LOG_INFO,
285 'DUMP DB: %s' % (db_name))
287 self._unset_pg_psw_env_var()
289 return base64.encodestring(data)
291 def exp_restore(self, db_name, data):
292 logger = netsvc.Logger()
294 self._set_pg_psw_env_var()
296 if self.exp_db_exist(db_name):
297 logger.notifyChannel("web-services", netsvc.LOG_WARNING,
298 'RESTORE DB: %s already exists' % (db_name,))
299 raise Exception, "Database already exists"
301 self._create_empty_database(db_name)
303 cmd = ['pg_restore', '--no-owner']
304 if tools.config['db_user']:
305 cmd.append('--username=' + tools.config['db_user'])
306 if tools.config['db_host']:
307 cmd.append('--host=' + tools.config['db_host'])
308 if tools.config['db_port']:
309 cmd.append('--port=' + str(tools.config['db_port']))
310 cmd.append('--dbname=' + db_name)
313 buf=base64.decodestring(data)
315 tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam()
316 file(tmpfile, 'wb').write(buf)
318 args2.append(' ' + tmpfile)
320 stdin, stdout = tools.exec_pg_command_pipe(*args2)
321 if not os.name == "nt":
322 stdin.write(base64.decodestring(data))
326 raise Exception, "Couldn't restore database"
327 logger.notifyChannel("web-services", netsvc.LOG_INFO,
328 'RESTORE DB: %s' % (db_name))
330 self._unset_pg_psw_env_var()
334 def exp_rename(self, old_name, new_name):
335 openerp.modules.registry.RegistryManager.delete(old_name)
336 sql_db.close_db(old_name)
337 logger = netsvc.Logger()
339 db = sql_db.db_connect('template1')
341 cr.autocommit(True) # avoid transaction block
344 cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
346 logger.notifyChannel("web-services", netsvc.LOG_ERROR,
347 'RENAME DB: %s -> %s failed:\n%s' % (old_name, new_name, e))
348 raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e))
350 fs = os.path.join(tools.config['root_path'], 'filestore')
351 if os.path.exists(os.path.join(fs, old_name)):
352 os.rename(os.path.join(fs, old_name), os.path.join(fs, new_name))
354 logger.notifyChannel("web-services", netsvc.LOG_INFO,
355 'RENAME DB: %s -> %s' % (old_name, new_name))
360 def exp_db_exist(self, db_name):
361 ## Not True: in fact, check if connection to database is possible. The database may exists
362 return bool(sql_db.db_connect(db_name))
364 def exp_list(self, document=False):
365 if not tools.config['list_db'] and not document:
366 raise openerp.exceptions.AccessDenied()
368 db = sql_db.db_connect('template1')
372 db_user = tools.config["db_user"]
373 if not db_user and os.name == 'posix':
375 db_user = pwd.getpwuid(os.getuid())[0]
377 cr.execute("select decode(usename, 'escape') from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (tools.config["db_name"],))
379 db_user = res and str(res[0])
381 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,))
383 cr.execute("select decode(datname, 'escape') from pg_database where datname not in('template0', 'template1','postgres') order by datname")
384 res = [str(name) for (name,) in cr.fetchall()]
392 def exp_change_admin_password(self, new_password):
393 tools.config['admin_passwd'] = new_password
397 def exp_list_lang(self):
398 return tools.scan_languages()
400 def exp_server_version(self):
401 """ Return the version of the server
402 Used by the client to verify the compatibility with its own version
404 return release.version
406 def exp_migrate_databases(self,databases):
408 from openerp.osv.orm import except_orm
409 from openerp.osv.osv import except_osv
414 l.notifyChannel('migration', netsvc.LOG_INFO, 'migrate database %s' % (db,))
415 tools.config['update']['base'] = True
416 pooler.restart_pool(db, force_demo=False, update_module=True)
417 except except_orm, inst:
418 netsvc.abort_response(1, inst.name, 'warning', inst.value)
419 except except_osv, inst:
420 netsvc.abort_response(1, inst.name, 'warning', inst.value)
423 tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
424 l.notifyChannel('web-services', netsvc.LOG_ERROR, tb_s)
428 class common(netsvc.ExportService):
429 def __init__(self,name="common"):
430 netsvc.ExportService.__init__(self,name)
432 def dispatch(self, method, params):
433 logger = netsvc.Logger()
434 if method == 'login':
435 res = security.login(params[0], params[1], params[2])
436 msg = res and 'successful login' or 'bad login or password'
437 # TODO log the client ip address..
438 logger.notifyChannel("web-service", netsvc.LOG_INFO, "%s from '%s' using database '%s'" % (msg, params[1], params[0].lower()))
440 elif method in ['about', 'timezone_get', 'get_server_environment',
441 'login_message','get_stats', 'check_connectivity',
442 'list_http_services']:
444 elif method in ['get_available_updates', 'get_migration_scripts', 'set_loglevel', 'get_os_time', 'get_sqlcount']:
447 security.check_super(passwd)
449 raise Exception("Method not found: %s" % method)
451 fn = getattr(self, 'exp_'+method)
454 def exp_about(self, extended=False):
455 """Return information about the OpenERP Server.
457 @param extended: if True then return version info
458 @return string if extended is False else tuple
463 OpenERP is an ERP+CRM program for small and medium businesses.
465 The whole source code is distributed under the terms of the
468 (c) 2003-TODAY, Fabien Pinckaers - Tiny sprl''')
471 return info, release.version
474 def exp_timezone_get(self, db, login, password):
475 return tools.misc.get_server_timezone()
477 def exp_get_available_updates(self, contract_id, contract_password):
478 import openerp.tools.maintenance as tm
480 rc = tm.remote_contract(contract_id, contract_password)
482 raise tm.RemoteContractException('This contract does not exist or is not active')
484 return rc.get_available_updates(rc.id, openerp.modules.get_modules_with_version())
486 except tm.RemoteContractException, e:
487 netsvc.abort_response(1, 'Migration Error', 'warning', str(e))
490 def exp_get_migration_scripts(self, contract_id, contract_password):
492 import openerp.tools.maintenance as tm
494 rc = tm.remote_contract(contract_id, contract_password)
496 raise tm.RemoteContractException('This contract does not exist or is not active')
497 if rc.status != 'full':
498 raise tm.RemoteContractException('Can not get updates for a partial contract')
500 l.notifyChannel('migration', netsvc.LOG_INFO, 'starting migration with contract %s' % (rc.name,))
502 zips = rc.retrieve_updates(rc.id, openerp.modules.get_modules_with_version())
504 from shutil import rmtree, copytree, copy
506 backup_directory = os.path.join(tools.config['root_path'], 'backup', time.strftime('%Y-%m-%d-%H-%M'))
507 if zips and not os.path.isdir(backup_directory):
508 l.notifyChannel('migration', netsvc.LOG_INFO, 'create a new backup directory to \
509 store the old modules: %s' % (backup_directory,))
510 os.makedirs(backup_directory)
513 l.notifyChannel('migration', netsvc.LOG_INFO, 'upgrade module %s' % (module,))
514 mp = openerp.modules.get_module_path(module)
516 if os.path.isdir(mp):
517 copytree(mp, os.path.join(backup_directory, module))
518 if os.path.islink(mp):
523 copy(mp + 'zip', backup_directory)
524 os.unlink(mp + '.zip')
528 base64_decoded = base64.decodestring(zips[module])
530 l.notifyChannel('migration', netsvc.LOG_ERROR, 'unable to read the module %s' % (module,))
533 zip_contents = StringIO(base64_decoded)
537 tools.extract_zip_file(zip_contents, tools.config['addons_path'] )
539 l.notifyChannel('migration', netsvc.LOG_ERROR, 'unable to extract the module %s' % (module, ))
545 l.notifyChannel('migration', netsvc.LOG_ERROR, 'restore the previous version of the module %s' % (module, ))
546 nmp = os.path.join(backup_directory, module)
547 if os.path.isdir(nmp):
548 copytree(nmp, tools.config['addons_path'])
550 copy(nmp+'.zip', tools.config['addons_path'])
554 except tm.RemoteContractException, e:
555 netsvc.abort_response(1, 'Migration Error', 'warning', str(e))
558 tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
559 l.notifyChannel('migration', netsvc.LOG_ERROR, tb_s)
562 def exp_get_server_environment(self):
563 os_lang = '.'.join( [x for x in locale.getdefaultlocale() if x] )
566 environment = '\nEnvironment Information : \n' \
569 %(platform.platform(), platform.os.name)
570 if os.name == 'posix':
571 if platform.system() == 'Linux':
572 lsbinfo = os.popen('lsb_release -a').read()
573 environment += '%s'%(lsbinfo)
575 environment += 'Your System is not lsb compliant\n'
576 environment += 'Operating System Release : %s\n' \
577 'Operating System Version : %s\n' \
578 'Operating System Architecture : %s\n' \
579 'Operating System Locale : %s\n'\
580 'Python Version : %s\n'\
581 'OpenERP-Server Version : %s'\
582 %(platform.release(), platform.version(), platform.architecture()[0],
583 os_lang, platform.python_version(),release.version)
586 def exp_login_message(self):
587 return tools.config.get('login_message', False)
589 def exp_set_loglevel(self, loglevel, logger=None):
591 l.set_loglevel(int(loglevel), logger)
594 def exp_get_stats(self):
596 res = "OpenERP server: %d threads\n" % threading.active_count()
597 res += netsvc.Server.allStats()
600 def exp_list_http_services(self):
601 return http_server.list_http_services()
603 def exp_check_connectivity(self):
604 return bool(sql_db.db_connect('template1'))
606 def exp_get_os_time(self):
609 def exp_get_sqlcount(self):
610 logger = logging.getLogger('db.cursor')
611 if not logger.isEnabledFor(logging.DEBUG_SQL):
612 logger.warning("Counters of SQL will not be reliable unless DEBUG_SQL is set at the server's config.")
613 return sql_db.sql_counter
616 class objects_proxy(netsvc.ExportService):
617 def __init__(self, name="object"):
618 netsvc.ExportService.__init__(self,name)
620 def dispatch(self, method, params):
621 (db, uid, passwd ) = params[0:3]
623 if method == 'obj_list':
624 raise NameError("obj_list has been discontinued via RPC as of 6.0, please query ir.model directly!")
625 if method not in ['execute','exec_workflow']:
626 raise NameError("Method not available %s" % method)
627 security.check(db,uid,passwd)
628 assert openerp.osv.osv.service, "The object_proxy class must be started with start_object_proxy."
629 fn = getattr(openerp.osv.osv.service, method)
630 res = fn(db, uid, *params)
636 # - None = end of wizard
638 # Wizard Type: 'form'
643 # TODO: change local request to OSE request/reply pattern
645 class wizard(netsvc.ExportService):
646 def __init__(self, name='wizard'):
647 netsvc.ExportService.__init__(self,name)
653 def dispatch(self, method, params):
654 (db, uid, passwd ) = params[0:3]
656 if method not in ['execute','create']:
657 raise KeyError("Method not supported %s" % method)
658 security.check(db,uid,passwd)
659 fn = getattr(self, 'exp_'+method)
660 res = fn(db, uid, *params)
663 def _execute(self, db, uid, wiz_id, datas, action, context):
664 self.wiz_datas[wiz_id].update(datas)
665 wiz = netsvc.LocalService('wizard.'+self.wiz_name[wiz_id])
666 return wiz.execute(db, uid, self.wiz_datas[wiz_id], action, context)
668 def exp_create(self, db, uid, wiz_name, datas=None):
671 #FIXME: this is not thread-safe
673 self.wiz_datas[self.id] = {}
674 self.wiz_name[self.id] = wiz_name
675 self.wiz_uid[self.id] = uid
678 def exp_execute(self, db, uid, wiz_id, datas, action='init', context=None):
682 if wiz_id in self.wiz_uid:
683 if self.wiz_uid[wiz_id] == uid:
684 return self._execute(db, uid, wiz_id, datas, action, context)
686 raise openerp.exceptions.AccessDenied()
688 raise openerp.exceptions.Warning('Wizard not found.')
691 # TODO: set a maximum report number per user to avoid DOS attacks
697 class report_spool(netsvc.ExportService):
698 def __init__(self, name='report'):
699 netsvc.ExportService.__init__(self, name)
702 self.id_protect = threading.Semaphore()
704 def dispatch(self, method, params):
705 (db, uid, passwd ) = params[0:3]
707 if method not in ['report', 'report_get', 'render_report']:
708 raise KeyError("Method not supported %s" % method)
709 security.check(db,uid,passwd)
710 fn = getattr(self, 'exp_' + method)
711 res = fn(db, uid, *params)
714 def exp_render_report(self, db, uid, object, ids, datas=None, context=None):
720 self.id_protect.acquire()
723 self.id_protect.release()
725 self._reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None}
727 cr = pooler.get_db(db).cursor()
731 obj = netsvc.LocalService('report.'+object)
732 (result, format) = obj.create(cr, uid, ids, datas, context)
735 self._reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb)
736 self._reports[id]['result'] = result
737 self._reports[id]['format'] = format
738 self._reports[id]['state'] = True
739 except Exception, exception:
742 tb_s = "".join(traceback.format_exception(*tb))
743 logger = netsvc.Logger()
744 logger.notifyChannel('web-services', netsvc.LOG_ERROR,
745 'Exception: %s\n%s' % (str(exception), tb_s))
746 if hasattr(exception, 'name') and hasattr(exception, 'value'):
747 self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value))
749 self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb)
750 self._reports[id]['state'] = True
754 return self._check_report(id)
756 def exp_report(self, db, uid, object, ids, datas=None, context=None):
762 self.id_protect.acquire()
765 self.id_protect.release()
767 self._reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None}
769 def go(id, uid, ids, datas, context):
770 cr = pooler.get_db(db).cursor()
774 obj = netsvc.LocalService('report.'+object)
775 (result, format) = obj.create(cr, uid, ids, datas, context)
778 self._reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb)
779 self._reports[id]['result'] = result
780 self._reports[id]['format'] = format
781 self._reports[id]['state'] = True
782 except Exception, exception:
785 tb_s = "".join(traceback.format_exception(*tb))
786 logger = netsvc.Logger()
787 logger.notifyChannel('web-services', netsvc.LOG_ERROR,
788 'Exception: %s\n%s' % (str(exception), tb_s))
789 if hasattr(exception, 'name') and hasattr(exception, 'value'):
790 self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value))
792 self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb)
793 self._reports[id]['state'] = True
798 thread.start_new_thread(go, (id, uid, ids, datas, context))
801 def _check_report(self, report_id):
802 result = self._reports[report_id]
803 exc = result['exception']
805 netsvc.abort_response(exc, exc.message, 'warning', exc.traceback)
806 res = {'state': result['state']}
808 if tools.config['reportgz']:
810 res2 = zlib.compress(result['result'])
813 #CHECKME: why is this needed???
814 if isinstance(result['result'], unicode):
815 res2 = result['result'].encode('latin1', 'replace')
817 res2 = result['result']
819 res['result'] = base64.encodestring(res2)
820 res['format'] = result['format']
821 del self._reports[report_id]
824 def exp_report_get(self, db, uid, report_id):
825 if report_id in self._reports:
826 if self._reports[report_id]['uid'] == uid:
827 return self._check_report(report_id)
829 raise Exception, 'AccessDenied'
831 raise Exception, 'ReportNotFound'
834 def start_web_services():
843 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: