[FIX] handle correctly the convertion of exceptions to unicode
[odoo/odoo.git] / bin / tools / misc.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 """
24 Miscelleanous tools used by OpenERP.
25 """
26
27 import os, time, sys
28 import inspect
29
30 from config import config
31
32 import zipfile
33 import release
34 import socket
35
36 if sys.version_info[:2] < (2, 4):
37     from threadinglocal import local
38 else:
39     from threading import local
40
41 from itertools import izip
42
43 # initialize a database with base/base.sql
44 def init_db(cr):
45     import addons
46     f = addons.get_module_resource('base', 'base.sql')
47     for line in file(f).read().split(';'):
48         if (len(line)>0) and (not line.isspace()):
49             cr.execute(line)
50     cr.commit()
51
52     for i in addons.get_modules():
53         terp_file = addons.get_module_resource(i, '__terp__.py')
54         mod_path = addons.get_module_path(i)
55         if not mod_path:
56             continue
57         info = False
58         if os.path.isfile(terp_file) and not os.path.isfile(mod_path+'.zip'):
59             info = eval(file(terp_file).read())
60         elif zipfile.is_zipfile(mod_path+'.zip'):
61             zfile = zipfile.ZipFile(mod_path+'.zip')
62             i = os.path.splitext(i)[0]
63             info = eval(zfile.read(os.path.join(i, '__terp__.py')))
64         if info:
65             categs = info.get('category', 'Uncategorized').split('/')
66             p_id = None
67             while categs:
68                 if p_id is not None:
69                     cr.execute('select id \
70                             from ir_module_category \
71                             where name=%s and parent_id=%s', (categs[0], p_id))
72                 else:
73                     cr.execute('select id \
74                             from ir_module_category \
75                             where name=%s and parent_id is NULL', (categs[0],))
76                 c_id = cr.fetchone()
77                 if not c_id:
78                     cr.execute('select nextval(\'ir_module_category_id_seq\')')
79                     c_id = cr.fetchone()[0]
80                     cr.execute('insert into ir_module_category \
81                             (id, name, parent_id) \
82                             values (%s, %s, %s)', (c_id, categs[0], p_id))
83                 else:
84                     c_id = c_id[0]
85                 p_id = c_id
86                 categs = categs[1:]
87
88             active = info.get('active', False)
89             installable = info.get('installable', True)
90             if installable:
91                 if active:
92                     state = 'to install'
93                 else:
94                     state = 'uninstalled'
95             else:
96                 state = 'uninstallable'
97             cr.execute('select nextval(\'ir_module_module_id_seq\')')
98             id = cr.fetchone()[0]
99             cr.execute('insert into ir_module_module \
100                     (id, author, website, name, shortdesc, description, \
101                         category_id, state) \
102                     values (%s, %s, %s, %s, %s, %s, %s, %s)', (
103                 id, info.get('author', ''),
104                 info.get('website', ''), i, info.get('name', False),
105                 info.get('description', ''), p_id, state))
106             dependencies = info.get('depends', [])
107             for d in dependencies:
108                 cr.execute('insert into ir_module_module_dependency \
109                         (module_id,name) values (%s, %s)', (id, d))
110             cr.commit()
111
112 def find_in_path(name):
113     if os.name == "nt":
114         sep = ';'
115     else:
116         sep = ':'
117     path = [dir for dir in os.environ['PATH'].split(sep)
118             if os.path.isdir(dir)]
119     for dir in path:
120         val = os.path.join(dir, name)
121         if os.path.isfile(val) or os.path.islink(val):
122             return val
123     return None
124
125 def find_pg_tool(name):
126     if config['pg_path'] and config['pg_path'] != 'None':
127         return os.path.join(config['pg_path'], name)
128     else:
129         return find_in_path(name)
130
131 def exec_pg_command(name, *args):
132     prog = find_pg_tool(name)
133     if not prog:
134         raise Exception('Couldn\'t find %s' % name)
135     args2 = (os.path.basename(prog),) + args
136     return os.spawnv(os.P_WAIT, prog, args2)
137
138 def exec_pg_command_pipe(name, *args):
139     prog = find_pg_tool(name)
140     if not prog:
141         raise Exception('Couldn\'t find %s' % name)
142     if os.name == "nt":
143         cmd = '"' + prog + '" ' + ' '.join(args)
144     else:
145         cmd = prog + ' ' + ' '.join(args)
146     return os.popen2(cmd, 'b')
147
148 def exec_command_pipe(name, *args):
149     prog = find_in_path(name)
150     if not prog:
151         raise Exception('Couldn\'t find %s' % name)
152     if os.name == "nt":
153         cmd = '"'+prog+'" '+' '.join(args)
154     else:
155         cmd = prog+' '+' '.join(args)
156     return os.popen2(cmd, 'b')
157
158 #----------------------------------------------------------
159 # File paths
160 #----------------------------------------------------------
161 #file_path_root = os.getcwd()
162 #file_path_addons = os.path.join(file_path_root, 'addons')
163
164 def file_open(name, mode="r", subdir='addons', pathinfo=False):
165     """Open a file from the OpenERP root, using a subdir folder.
166
167     >>> file_open('hr/report/timesheer.xsl')
168     >>> file_open('addons/hr/report/timesheet.xsl')
169     >>> file_open('../../base/report/rml_template.xsl', subdir='addons/hr/report', pathinfo=True)
170
171     @param name: name of the file
172     @param mode: file open mode
173     @param subdir: subdirectory
174     @param pathinfo: if True returns tupple (fileobject, filepath)
175
176     @return: fileobject if pathinfo is False else (fileobject, filepath)
177     """
178
179     adp = os.path.normcase(os.path.abspath(config['addons_path']))
180     rtp = os.path.normcase(os.path.abspath(config['root_path']))
181
182     if name.replace(os.path.sep, '/').startswith('addons/'):
183         subdir = 'addons'
184         name = name[7:]
185
186     # First try to locate in addons_path
187     if subdir:
188         subdir2 = subdir
189         if subdir2.replace(os.path.sep, '/').startswith('addons/'):
190             subdir2 = subdir2[7:]
191
192         subdir2 = (subdir2 != 'addons' or None) and subdir2
193
194         try:
195             if subdir2:
196                 fn = os.path.join(adp, subdir2, name)
197             else:
198                 fn = os.path.join(adp, name)
199             fn = os.path.normpath(fn)
200             fo = file_open(fn, mode=mode, subdir=None, pathinfo=pathinfo)
201             if pathinfo:
202                 return fo, fn
203             return fo
204         except IOError, e:
205             pass
206
207     if subdir:
208         name = os.path.join(rtp, subdir, name)
209     else:
210         name = os.path.join(rtp, name)
211
212     name = os.path.normpath(name)
213
214     # Check for a zipfile in the path
215     head = name
216     zipname = False
217     name2 = False
218     while True:
219         head, tail = os.path.split(head)
220         if not tail:
221             break
222         if zipname:
223             zipname = os.path.join(tail, zipname)
224         else:
225             zipname = tail
226         if zipfile.is_zipfile(head+'.zip'):
227             import cStringIO
228             zfile = zipfile.ZipFile(head+'.zip')
229             try:
230                 fo = cStringIO.StringIO(zfile.read(os.path.join(
231                     os.path.basename(head), zipname).replace(
232                         os.sep, '/')))
233
234                 if pathinfo:
235                     return fo, name
236                 return fo
237             except:
238                 name2 = os.path.normpath(os.path.join(head + '.zip', zipname))
239                 pass
240     for i in (name2, name):
241         if i and os.path.isfile(i):
242             fo = file(i, mode)
243             if pathinfo:
244                 return fo, i
245             return fo
246     if os.path.splitext(name)[1] == '.rml':
247         raise IOError, 'Report %s doesn\'t exist or deleted : ' %str(name)
248     raise IOError, 'File not found : '+str(name)
249
250
251 #----------------------------------------------------------
252 # iterables
253 #----------------------------------------------------------
254 def flatten(list):
255     """Flatten a list of elements into a uniqu list
256     Author: Christophe Simonis (christophe@tinyerp.com)
257
258     Examples:
259     >>> flatten(['a'])
260     ['a']
261     >>> flatten('b')
262     ['b']
263     >>> flatten( [] )
264     []
265     >>> flatten( [[], [[]]] )
266     []
267     >>> flatten( [[['a','b'], 'c'], 'd', ['e', [], 'f']] )
268     ['a', 'b', 'c', 'd', 'e', 'f']
269     >>> t = (1,2,(3,), [4, 5, [6, [7], (8, 9), ([10, 11, (12, 13)]), [14, [], (15,)], []]])
270     >>> flatten(t)
271     [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
272     """
273
274     def isiterable(x):
275         return hasattr(x, "__iter__")
276
277     r = []
278     for e in list:
279         if isiterable(e):
280             map(r.append, flatten(e))
281         else:
282             r.append(e)
283     return r
284
285 def reverse_enumerate(l):
286     """Like enumerate but in the other sens
287     >>> a = ['a', 'b', 'c']
288     >>> it = reverse_enumerate(a)
289     >>> it.next()
290     (2, 'c')
291     >>> it.next()
292     (1, 'b')
293     >>> it.next()
294     (0, 'a')
295     >>> it.next()
296     Traceback (most recent call last):
297       File "<stdin>", line 1, in <module>
298     StopIteration
299     """
300     return izip(xrange(len(l)-1, -1, -1), reversed(l))
301
302 #----------------------------------------------------------
303 # Emails
304 #----------------------------------------------------------
305 def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False, attach=None, tinycrm=False, ssl=False, debug=False,subtype='plain'):
306     """Send an email."""
307     import smtplib
308     from email.MIMEText import MIMEText
309     from email.MIMEBase import MIMEBase
310     from email.MIMEMultipart import MIMEMultipart
311     from email.Header import Header
312     from email.Utils import formatdate, COMMASPACE
313     from email.Utils import formatdate, COMMASPACE
314     from email import Encoders
315
316     if not ssl:
317         ssl = config.get('smtp_ssl', False)
318
319     if not email_cc:
320         email_cc = []
321     if not email_bcc:
322         email_bcc = []
323
324     if not attach:
325         msg = MIMEText(body or '',_subtype=subtype,_charset='utf-8')
326     else:
327         msg = MIMEMultipart()
328
329     msg['Subject'] = Header(subject.decode('utf8'), 'utf-8')
330     msg['From'] = email_from
331     del msg['Reply-To']
332     if reply_to:
333         msg['Reply-To'] = msg['From']+', '+reply_to
334     msg['To'] = COMMASPACE.join(email_to)
335     if email_cc:
336         msg['Cc'] = COMMASPACE.join(email_cc)
337     if email_bcc:
338         msg['Bcc'] = COMMASPACE.join(email_bcc)
339     msg['Date'] = formatdate(localtime=True)
340
341     if tinycrm:
342         msg['Message-Id'] = "<%s-tinycrm-%s@%s>" % (time.time(), tinycrm, socket.gethostname())
343
344     if attach:
345         msg.attach( MIMEText(body or '', _charset='utf-8', _subtype=subtype) )
346
347         for (fname,fcontent) in attach:
348             part = MIMEBase('application', "octet-stream")
349             part.set_payload( fcontent )
350             Encoders.encode_base64(part)
351             part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
352             msg.attach(part)
353     try:
354         s = smtplib.SMTP()
355
356         if debug:
357             s.debuglevel = 5
358         s.connect(config['smtp_server'], config['smtp_port'])
359         if ssl:
360             s.ehlo()
361             s.starttls()
362             s.ehlo()
363
364         if config['smtp_user'] or config['smtp_password']:
365             s.login(config['smtp_user'], config['smtp_password'])
366
367         s.sendmail(email_from, 
368                    flatten([email_to, email_cc, email_bcc]), 
369                    msg.as_string()
370                   )
371         s.quit()
372     except Exception, e:
373         import logging
374         logging.getLogger().error(str(e))
375         return False
376     return True
377
378 #----------------------------------------------------------
379 # SMS
380 #----------------------------------------------------------
381 # text must be latin-1 encoded
382 def sms_send(user, password, api_id, text, to):
383     import urllib
384     params = urllib.urlencode({'user': user, 'password': password, 'api_id': api_id, 'text': text, 'to':to})
385     #f = urllib.urlopen("http://api.clickatell.com/http/sendmsg", params)
386     f = urllib.urlopen("http://196.7.150.220/http/sendmsg", params)
387     # FIXME: Use the logger if there is an error
388     return True
389
390 #---------------------------------------------------------
391 # Class that stores an updateable string (used in wizards)
392 #---------------------------------------------------------
393 class UpdateableStr(local):
394
395     def __init__(self, string=''):
396         self.string = string
397
398     def __str__(self):
399         return str(self.string)
400
401     def __repr__(self):
402         return str(self.string)
403
404     def __nonzero__(self):
405         return bool(self.string)
406
407
408 class UpdateableDict(local):
409     '''Stores an updateable dict to use in wizards'''
410
411     def __init__(self, dict=None):
412         if dict is None:
413             dict = {}
414         self.dict = dict
415
416     def __str__(self):
417         return str(self.dict)
418
419     def __repr__(self):
420         return str(self.dict)
421
422     def clear(self):
423         return self.dict.clear()
424
425     def keys(self):
426         return self.dict.keys()
427
428     def __setitem__(self, i, y):
429         self.dict.__setitem__(i, y)
430
431     def __getitem__(self, i):
432         return self.dict.__getitem__(i)
433
434     def copy(self):
435         return self.dict.copy()
436
437     def iteritems(self):
438         return self.dict.iteritems()
439
440     def iterkeys(self):
441         return self.dict.iterkeys()
442
443     def itervalues(self):
444         return self.dict.itervalues()
445
446     def pop(self, k, d=None):
447         return self.dict.pop(k, d)
448
449     def popitem(self):
450         return self.dict.popitem()
451
452     def setdefault(self, k, d=None):
453         return self.dict.setdefault(k, d)
454
455     def update(self, E, **F):
456         return self.dict.update(E, F)
457
458     def values(self):
459         return self.dict.values()
460
461     def get(self, k, d=None):
462         return self.dict.get(k, d)
463
464     def has_key(self, k):
465         return self.dict.has_key(k)
466
467     def items(self):
468         return self.dict.items()
469
470     def __cmp__(self, y):
471         return self.dict.__cmp__(y)
472
473     def __contains__(self, k):
474         return self.dict.__contains__(k)
475
476     def __delitem__(self, y):
477         return self.dict.__delitem__(y)
478
479     def __eq__(self, y):
480         return self.dict.__eq__(y)
481
482     def __ge__(self, y):
483         return self.dict.__ge__(y)
484
485     def __getitem__(self, y):
486         return self.dict.__getitem__(y)
487
488     def __gt__(self, y):
489         return self.dict.__gt__(y)
490
491     def __hash__(self):
492         return self.dict.__hash__()
493
494     def __iter__(self):
495         return self.dict.__iter__()
496
497     def __le__(self, y):
498         return self.dict.__le__(y)
499
500     def __len__(self):
501         return self.dict.__len__()
502
503     def __lt__(self, y):
504         return self.dict.__lt__(y)
505
506     def __ne__(self, y):
507         return self.dict.__ne__(y)
508
509
510 # Don't use ! Use res.currency.round()
511 class currency(float):
512
513     def __init__(self, value, accuracy=2, rounding=None):
514         if rounding is None:
515             rounding=10**-accuracy
516         self.rounding=rounding
517         self.accuracy=accuracy
518
519     def __new__(cls, value, accuracy=2, rounding=None):
520         return float.__new__(cls, round(value, accuracy))
521
522     #def __str__(self):
523     #   display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
524     #   return str(display_value)
525
526
527 def is_hashable(h):
528     try:
529         hash(h)
530         return True
531     except TypeError:
532         return False
533
534 class cache(object):
535     """
536     Use it as a decorator of the function you plan to cache
537     Timeout: 0 = no timeout, otherwise in seconds
538     """
539     
540     __caches = []
541     
542     def __init__(self, timeout=None, skiparg=2, multi=None):
543         assert skiparg >= 2 # at least self and cr
544         if timeout is None:
545             self.timeout = config['cache_timeout']
546         else:
547             self.timeout = timeout
548         self.skiparg = skiparg
549         self.multi = multi
550         self.lasttime = time.time()
551         self.cache = {}
552         self.fun = None 
553         cache.__caches.append(self)
554
555     
556     def _generate_keys(self, dbname, kwargs2):
557         """
558         Generate keys depending of the arguments and the self.mutli value
559         """
560         
561         def to_tuple(d):
562             i = d.items()
563             i.sort()
564             return tuple(i)
565
566         if not self.multi:
567             key = (('dbname', dbname),) + to_tuple(kwargs2)
568             yield key, None
569         else:
570             multis = kwargs2[self.multi][:]    
571             for id in multis:
572                 kwargs2[self.multi] = (id,)
573                 key = (('dbname', dbname),) + to_tuple(kwargs2)
574                 yield key, id
575     
576     def _unify_args(self, *args, **kwargs):
577         # Update named arguments with positional argument values (without self and cr)
578         kwargs2 = self.fun_default_values.copy()
579         kwargs2.update(kwargs)
580         kwargs2.update(dict(zip(self.fun_arg_names, args[self.skiparg-2:])))
581         for k in kwargs2:
582             if isinstance(kwargs2[k], (list, dict, set)):
583                 kwargs2[k] = tuple(kwargs2[k])
584             elif not is_hashable(kwargs2[k]):
585                 kwargs2[k] = repr(kwargs2[k])
586
587         return kwargs2
588     
589     def clear(self, dbname, *args, **kwargs):
590         """clear the cache for database dbname
591             if *args and **kwargs are both empty, clear all the keys related to this database
592         """
593         if not args and not kwargs:
594             keys_to_del = [key for key in self.cache if key[0][1] == dbname]
595         else:
596             kwargs2 = self._unify_args(*args, **kwargs)
597             keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache]
598         
599         for key in keys_to_del:
600             del self.cache[key]
601     
602     @classmethod
603     def clean_caches_for_db(cls, dbname):
604         for c in cls.__caches:
605             c.clear(dbname)
606
607     def __call__(self, fn):
608         if self.fun is not None:
609             raise Exception("Can not use a cache instance on more than one function")
610         self.fun = fn
611
612         argspec = inspect.getargspec(fn)
613         self.fun_arg_names = argspec[0][self.skiparg:]
614         self.fun_default_values = {}
615         if argspec[3]:
616             self.fun_default_values = dict(zip(self.fun_arg_names[-len(argspec[3]):], argspec[3]))
617         
618         def cached_result(self2, cr, *args, **kwargs):
619             if time.time()-self.timeout > self.lasttime:
620                 self.lasttime = time.time()
621                 t = time.time()-self.timeout 
622                 for key in self.cache.keys():
623                     if self.cache[key][1]<t:
624                         del self.cache[key]
625
626             kwargs2 = self._unify_args(*args, **kwargs)
627
628             result = {}
629             notincache = {}
630             for key, id in self._generate_keys(cr.dbname, kwargs2):
631                 if key in self.cache:
632                     result[id] = self.cache[key][0]
633                 else:
634                     notincache[id] = key
635             
636             if notincache:
637                 if self.multi:
638                     kwargs2[self.multi] = notincache.keys()
639                 
640                 result2 = fn(self2, cr, *args[2:self.skiparg], **kwargs2)
641                 if not self.multi:
642                     key = notincache[None]
643                     self.cache[key] = (result2, time.time())
644                     result[None] = result2
645                 else:
646                     for id in result2:
647                         key = notincache[id]
648                         self.cache[key] = (result2[id], time.time())
649                     result.update(result2)
650                         
651             if not self.multi:
652                 return result[None]
653             return result
654
655         cached_result.clear_cache = self.clear
656         return cached_result
657
658 def to_xml(s):
659     return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')
660
661 def ustr(value):
662     """This method is similar to the builtin `str` method, except
663     it will return Unicode string.
664
665     @param value: the value to convert
666
667     @rtype: unicode
668     @return: unicode string
669     """
670
671     if isinstance(value, unicode):
672         return value
673
674     if hasattr(value, '__unicode__'):
675         return unicode(value)
676
677     if not isinstance(value, str):
678         value = str(value)
679
680     return unicode(value, 'utf-8')
681
682 def exception_to_unicode(e):
683     if hasattr(e, 'message'):
684         return ustr(e.message)
685     if hasattr(e, 'args'):
686         return "\n".join((ustr(a) for a in e.args))
687     try:
688         return ustr(e)
689     except:
690         return u"Unknow message"
691
692
693 # to be compatible with python 2.4
694 import __builtin__
695 if not hasattr(__builtin__, 'all'):
696     def all(iterable):
697         for element in iterable:
698             if not element:
699                return False
700         return True
701         
702     __builtin__.all = all
703     del all
704     
705 if not hasattr(__builtin__, 'any'):
706     def any(iterable):
707         for element in iterable:
708             if element:
709                return True
710         return False
711         
712     __builtin__.any = any
713     del any
714
715
716
717 def get_languages():
718     languages={
719         'ar_AR': u'Arabic / الْعَرَبيّة',
720         'bg_BG': u'Bulgarian / български',
721         'bs_BS': u'Bosnian / bosanski jezik',
722         'ca_ES': u'Catalan / Català',
723         'cs_CZ': u'Czech / Čeština',
724         'de_DE': u'German / Deutsch',
725         'en_CA': u'English (CA)',
726         'en_EN': u'English (default)',
727         'en_GB': u'English (UK)',
728         'en_US': u'English (US)',
729         'es_AR': u'Spanish (AR) / Español (AR)',
730         'es_ES': u'Spanish / Español',
731         'et_ET': u'Estonian / Eesti keel',
732         'fr_BE': u'French (BE) / Français (BE)',
733         'fr_CH': u'French (CH) / Français (CH)',
734         'fr_FR': u'French / Français',
735         'hr_HR': u'Croatian / hrvatski jezik',
736         'hu_HU': u'Hungarian / Magyar',
737         'it_IT': u'Italian / Italiano',
738         'lt_LT': u'Lithuanian / Lietuvių kalba',
739         'nl_NL': u'Dutch / Nederlands',
740         'pl_PL': u'Polish / Język polski',
741         'pt_BR': u'Portugese (BR) / português (BR)',
742         'pt_PT': u'Portugese / português',
743         'ro_RO': u'Romanian / limba română',
744         'ru_RU': u'Russian / русский язык',
745         'sl_SL': u'Slovenian / slovenščina',
746         'sv_SE': u'Swedish / svenska',
747         'tr_TR': u'Turkish / Türkçe',
748         'uk_UK': u'Ukrainian / украї́нська мо́ва',
749         'zh_CN': u'Chinese (CN) / 简体中文' ,
750         'zh_TW': u'Chinese (TW) / 正體字',
751     }
752     return languages
753
754 def scan_languages():
755     import glob
756     file_list = [os.path.splitext(os.path.basename(f))[0] for f in glob.glob(os.path.join(config['root_path'],'addons', 'base', 'i18n', '*.po'))]
757     lang_dict = get_languages()
758     ret = [(lang, lang_dict.get(lang, lang)) for lang in file_list]
759     ret.sort(key=lambda k:k[1])
760     return ret
761
762
763 def get_user_companies(cr, user):
764     def _get_company_children(cr, ids):
765         if not ids:
766             return []
767         cr.execute('SELECT id FROM res_company WHERE parent_id = any(array[%s])' %(','.join([str(x) for x in ids]),))
768         res=[x[0] for x in cr.fetchall()]
769         res.extend(_get_company_children(cr, res))
770         return res
771     cr.execute('SELECT comp.id FROM res_company AS comp, res_users AS u WHERE u.id = %s AND comp.id = u.company_id' % (user,))
772     compids=[cr.fetchone()[0]]
773     compids.extend(_get_company_children(cr, compids))
774     return compids
775
776 def mod10r(number):
777     """
778     Input number : account or invoice number
779     Output return: the same number completed with the recursive mod10
780     key
781     """
782     codec=[0,9,4,6,8,2,7,1,3,5]
783     report = 0
784     result=""
785     for digit in number:
786         result += digit
787         if digit.isdigit():
788             report = codec[ (int(digit) + report) % 10 ]
789     return result + str((10 - report) % 10)
790
791
792 def human_size(sz):
793     """
794     Return the size in a human readable format
795     """
796     if not sz:
797         return False
798     units = ('bytes', 'Kb', 'Mb', 'Gb')
799     if isinstance(sz,basestring):
800         sz=len(sz)
801     s, i = float(sz), 0
802     while s >= 1024 and i < len(units)-1:
803         s = s / 1024
804         i = i + 1
805     return "%0.2f %s" % (s, units[i])
806
807 def logged(f):
808     from tools.func import wraps
809     
810     @wraps(f)
811     def wrapper(*args, **kwargs):
812         import netsvc
813         from pprint import pformat
814
815         vector = ['Call -> function: %r' % f]
816         for i, arg in enumerate(args):
817             vector.append('  arg %02d: %s' % (i, pformat(arg)))
818         for key, value in kwargs.items():
819             vector.append('  kwarg %10s: %s' % (key, pformat(value)))
820
821         timeb4 = time.time()
822         res = f(*args, **kwargs)
823         
824         vector.append('  result: %s' % pformat(res))
825         vector.append('  time delta: %s' % (time.time() - timeb4))
826         netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector))
827         return res
828
829     return wrapper
830
831 def debug(what):
832     """
833         This method allow you to debug your code without print
834         Example:
835         >>> def func_foo(bar)
836         ...     baz = bar
837         ...     debug(baz)
838         ...     qnx = (baz, bar)
839         ...     debug(qnx)
840         ...
841         >>> func_foo(42)
842
843         This will output on the logger:
844         
845             [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42
846             [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42)
847
848         To view the DEBUG lines in the logger you must start the server with the option
849             --log-level=debug
850
851     """
852     import netsvc
853     from inspect import stack
854     import re
855     from pprint import pformat
856     st = stack()[1]
857     param = re.split("debug *\((.+)\)", st[4][0].strip())[1].strip()
858     while param.count(')') > param.count('('): param = param[:param.rfind(')')]
859     what = pformat(what)
860     if param != what:
861         what = "%s = %s" % (param, what)
862     netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what)
863
864
865 icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
866 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
867 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
868 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
869 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
870 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
871 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
872 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
873 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
874 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
875 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
876 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
877 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
878 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
879 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
880 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
881 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
882 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
883 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
884 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
885 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
886 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
887 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
888 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
889 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
890 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
891 ])
892
893
894
895 if __name__ == '__main__':
896     import doctest
897     doctest.testmod()
898
899
900
901 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
902