Merge commit 'origin/master' into mdv-gpl3-py26
[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_open(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) or os.path.isfile(mod_path+'.zip'):
59             info = eval(file_open(terp_file).read())
60         if info:
61             categs = info.get('category', 'Uncategorized').split('/')
62             p_id = None
63             while categs:
64                 if p_id is not None:
65                     cr.execute('select id \
66                             from ir_module_category \
67                             where name=%s and parent_id=%s', (categs[0], p_id))
68                 else:
69                     cr.execute('select id \
70                             from ir_module_category \
71                             where name=%s and parent_id is NULL', (categs[0],))
72                 c_id = cr.fetchone()
73                 if not c_id:
74                     cr.execute('select nextval(\'ir_module_category_id_seq\')')
75                     c_id = cr.fetchone()[0]
76                     cr.execute('insert into ir_module_category \
77                             (id, name, parent_id) \
78                             values (%s, %s, %s)', (c_id, categs[0], p_id))
79                 else:
80                     c_id = c_id[0]
81                 p_id = c_id
82                 categs = categs[1:]
83
84             active = info.get('active', False)
85             installable = info.get('installable', True)
86             if installable:
87                 if active:
88                     state = 'to install'
89                 else:
90                     state = 'uninstalled'
91             else:
92                 state = 'uninstallable'
93             cr.execute('select nextval(\'ir_module_module_id_seq\')')
94             id = cr.fetchone()[0]
95             cr.execute('insert into ir_module_module \
96                     (id, author, website, name, shortdesc, description, \
97                         category_id, state) \
98                     values (%s, %s, %s, %s, %s, %s, %s, %s)', (
99                 id, info.get('author', ''),
100                 info.get('website', ''), i, info.get('name', False),
101                 info.get('description', ''), p_id, state))
102             cr.execute('insert into ir_model_data \
103                 (name,model,module, res_id) values (%s,%s,%s,%s)', (
104                     'module_meta_information', 'ir.module.module', i, id))
105             dependencies = info.get('depends', [])
106             for d in dependencies:
107                 cr.execute('insert into ir_module_module_dependency \
108                         (module_id,name) values (%s, %s)', (id, d))
109             cr.commit()
110
111 def find_in_path(name):
112     if os.name == "nt":
113         sep = ';'
114     else:
115         sep = ':'
116     path = [dir for dir in os.environ['PATH'].split(sep)
117             if os.path.isdir(dir)]
118     for dir in path:
119         val = os.path.join(dir, name)
120         if os.path.isfile(val) or os.path.islink(val):
121             return val
122     return None
123
124 def find_pg_tool(name):
125     if config['pg_path'] and config['pg_path'] != 'None':
126         return os.path.join(config['pg_path'], name)
127     else:
128         return find_in_path(name)
129
130 def exec_pg_command(name, *args):
131     prog = find_pg_tool(name)
132     if not prog:
133         raise Exception('Couldn\'t find %s' % name)
134     args2 = (os.path.basename(prog),) + args
135     return os.spawnv(os.P_WAIT, prog, args2)
136
137 def exec_pg_command_pipe(name, *args):
138     prog = find_pg_tool(name)
139     if not prog:
140         raise Exception('Couldn\'t find %s' % name)
141     if os.name == "nt":
142         cmd = '"' + prog + '" ' + ' '.join(args)
143     else:
144         cmd = prog + ' ' + ' '.join(args)
145     return os.popen2(cmd, 'b')
146
147 def exec_command_pipe(name, *args):
148     prog = find_in_path(name)
149     if not prog:
150         raise Exception('Couldn\'t find %s' % name)
151     if os.name == "nt":
152         cmd = '"'+prog+'" '+' '.join(args)
153     else:
154         cmd = prog+' '+' '.join(args)
155     return os.popen2(cmd, 'b')
156
157 #----------------------------------------------------------
158 # File paths
159 #----------------------------------------------------------
160 #file_path_root = os.getcwd()
161 #file_path_addons = os.path.join(file_path_root, 'addons')
162
163 def file_open(name, mode="r", subdir='addons', pathinfo=False):
164     """Open a file from the OpenERP root, using a subdir folder.
165
166     >>> file_open('hr/report/timesheer.xsl')
167     >>> file_open('addons/hr/report/timesheet.xsl')
168     >>> file_open('../../base/report/rml_template.xsl', subdir='addons/hr/report', pathinfo=True)
169
170     @param name: name of the file
171     @param mode: file open mode
172     @param subdir: subdirectory
173     @param pathinfo: if True returns tupple (fileobject, filepath)
174
175     @return: fileobject if pathinfo is False else (fileobject, filepath)
176     """
177
178     adp = os.path.normcase(os.path.abspath(config['addons_path']))
179     rtp = os.path.normcase(os.path.abspath(config['root_path']))
180
181     if name.replace(os.path.sep, '/').startswith('addons/'):
182         subdir = 'addons'
183         name = name[7:]
184
185     # First try to locate in addons_path
186     if subdir:
187         subdir2 = subdir
188         if subdir2.replace(os.path.sep, '/').startswith('addons/'):
189             subdir2 = subdir2[7:]
190
191         subdir2 = (subdir2 != 'addons' or None) and subdir2
192
193         try:
194             if subdir2:
195                 fn = os.path.join(adp, subdir2, name)
196             else:
197                 fn = os.path.join(adp, name)
198             fn = os.path.normpath(fn)
199             fo = file_open(fn, mode=mode, subdir=None, pathinfo=pathinfo)
200             if pathinfo:
201                 return fo, fn
202             return fo
203         except IOError, e:
204             pass
205
206     if subdir:
207         name = os.path.join(rtp, subdir, name)
208     else:
209         name = os.path.join(rtp, name)
210
211     name = os.path.normpath(name)
212
213     # Check for a zipfile in the path
214     head = name
215     zipname = False
216     name2 = False
217     while True:
218         head, tail = os.path.split(head)
219         if not tail:
220             break
221         if zipname:
222             zipname = os.path.join(tail, zipname)
223         else:
224             zipname = tail
225         if zipfile.is_zipfile(head+'.zip'):
226             from cStringIO import StringIO
227             zfile = zipfile.ZipFile(head+'.zip')
228             try:
229                 fo = StringIO()
230                 fo.write(zfile.read(os.path.join(
231                     os.path.basename(head), zipname).replace(
232                         os.sep, '/')))
233                 fo.seek(0)
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(key=lambda (x,y): x)
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     try: # first try utf-8
681         return unicode(value, 'utf-8')
682     except:
683         pass
684
685     try: # then extened iso-8858
686         return unicode(value, 'iso-8859-15')
687     except:
688         pass
689
690     # else use default system locale
691     from locale import getlocale
692     return unicode(value, getlocale()[1])
693
694 def exception_to_unicode(e):
695     if hasattr(e, 'message'):
696         return ustr(e.message)
697     if hasattr(e, 'args'):
698         return "\n".join((ustr(a) for a in e.args))
699     try:
700         return ustr(e)
701     except:
702         return u"Unknow message"
703
704
705 # to be compatible with python 2.4
706 import __builtin__
707 if not hasattr(__builtin__, 'all'):
708     def all(iterable):
709         for element in iterable:
710             if not element:
711                return False
712         return True
713         
714     __builtin__.all = all
715     del all
716     
717 if not hasattr(__builtin__, 'any'):
718     def any(iterable):
719         for element in iterable:
720             if element:
721                return True
722         return False
723         
724     __builtin__.any = any
725     del any
726
727
728
729 def get_languages():
730     languages={
731         'ar_AR': u'Arabic / الْعَرَبيّة',
732         'bg_BG': u'Bulgarian / български',
733         'bs_BS': u'Bosnian / bosanski jezik',
734         'ca_ES': u'Catalan / Català',
735         'cs_CZ': u'Czech / Čeština',
736         'da_DK': u'Danish / Dansk',
737         'de_DE': u'German / Deutsch',
738         'el_GR': u'Greek / Ελληνικά',
739         'en_CA': u'English (CA)',
740         'en_EN': u'English (default)',
741         'en_GB': u'English (UK)',
742         'en_US': u'English (US)',
743         'es_AR': u'Spanish (AR) / Español (AR)',
744         'es_ES': u'Spanish / Español',
745         'et_EE': u'Estonian / Eesti keel',
746         'fr_BE': u'French (BE) / Français (BE)',
747         'fr_CH': u'French (CH) / Français (CH)',
748         'fr_FR': u'French / Français',
749         'hr_HR': u'Croatian / hrvatski jezik',
750         'hu_HU': u'Hungarian / Magyar',
751         'id_ID': u'Indonesian / Bahasa Indonesia',
752         'it_IT': u'Italian / Italiano',
753         'lt_LT': u'Lithuanian / Lietuvių kalba',
754         'nl_NL': u'Dutch / Nederlands',
755         'nl_BE': u'Dutch (Belgium) / Nederlands (Belgïe)',
756         'pl_PL': u'Polish / Język polski',
757         'pt_BR': u'Portugese (BR) / português (BR)',
758         'pt_PT': u'Portugese / português',
759         'ro_RO': u'Romanian / limba română',
760         'ru_RU': u'Russian / русский язык',
761         'sl_SL': u'Slovenian / slovenščina',
762         'sv_SE': u'Swedish / svenska',
763         'tr_TR': u'Turkish / Türkçe',
764         'uk_UK': u'Ukrainian / украї́нська мо́ва',
765         'zh_CN': u'Chinese (CN) / 简体中文' ,
766         'zh_TW': u'Chinese (TW) / 正體字',
767     }
768     return languages
769
770 def scan_languages():
771     import glob
772     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'))]
773     lang_dict = get_languages()
774     ret = [(lang, lang_dict.get(lang, lang)) for lang in file_list]
775     ret.sort(key=lambda k:k[1])
776     return ret
777
778
779 def get_user_companies(cr, user):
780     def _get_company_children(cr, ids):
781         if not ids:
782             return []
783         cr.execute('SELECT id FROM res_company WHERE parent_id = any(array[%s])' %(','.join([str(x) for x in ids]),))
784         res=[x[0] for x in cr.fetchall()]
785         res.extend(_get_company_children(cr, res))
786         return res
787     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,))
788     compids=[cr.fetchone()[0]]
789     compids.extend(_get_company_children(cr, compids))
790     return compids
791
792 def mod10r(number):
793     """
794     Input number : account or invoice number
795     Output return: the same number completed with the recursive mod10
796     key
797     """
798     codec=[0,9,4,6,8,2,7,1,3,5]
799     report = 0
800     result=""
801     for digit in number:
802         result += digit
803         if digit.isdigit():
804             report = codec[ (int(digit) + report) % 10 ]
805     return result + str((10 - report) % 10)
806
807
808 def human_size(sz):
809     """
810     Return the size in a human readable format
811     """
812     if not sz:
813         return False
814     units = ('bytes', 'Kb', 'Mb', 'Gb')
815     if isinstance(sz,basestring):
816         sz=len(sz)
817     s, i = float(sz), 0
818     while s >= 1024 and i < len(units)-1:
819         s = s / 1024
820         i = i + 1
821     return "%0.2f %s" % (s, units[i])
822
823 def logged(f):
824     from tools.func import wraps
825     
826     @wraps(f)
827     def wrapper(*args, **kwargs):
828         import netsvc
829         from pprint import pformat
830
831         vector = ['Call -> function: %r' % f]
832         for i, arg in enumerate(args):
833             vector.append('  arg %02d: %s' % (i, pformat(arg)))
834         for key, value in kwargs.items():
835             vector.append('  kwarg %10s: %s' % (key, pformat(value)))
836
837         timeb4 = time.time()
838         res = f(*args, **kwargs)
839         
840         vector.append('  result: %s' % pformat(res))
841         vector.append('  time delta: %s' % (time.time() - timeb4))
842         netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector))
843         return res
844
845     return wrapper
846
847 class profile(object):
848     def __init__(self, fname=None):
849         self.fname = fname
850
851     def __call__(self, f):
852         from tools.func import wraps
853
854         @wraps(f)
855         def wrapper(*args, **kwargs):
856             class profile_wrapper(object):
857                 def __init__(self):
858                     self.result = None
859                 def __call__(self):
860                     self.result = f(*args, **kwargs)
861             pw = profile_wrapper()
862             import cProfile
863             fname = self.fname or ("%s.cprof" % (f.func_name,))
864             cProfile.runctx('pw()', globals(), locals(), filename=fname)
865             return pw.result
866
867         return wrapper
868
869 def debug(what):
870     """
871         This method allow you to debug your code without print
872         Example:
873         >>> def func_foo(bar)
874         ...     baz = bar
875         ...     debug(baz)
876         ...     qnx = (baz, bar)
877         ...     debug(qnx)
878         ...
879         >>> func_foo(42)
880
881         This will output on the logger:
882         
883             [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42
884             [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42)
885
886         To view the DEBUG lines in the logger you must start the server with the option
887             --log-level=debug
888
889     """
890     import netsvc
891     from inspect import stack
892     import re
893     from pprint import pformat
894     st = stack()[1]
895     param = re.split("debug *\((.+)\)", st[4][0].strip())[1].strip()
896     while param.count(')') > param.count('('): param = param[:param.rfind(')')]
897     what = pformat(what)
898     if param != what:
899         what = "%s = %s" % (param, what)
900     netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what)
901
902
903 icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
904 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
905 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
906 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
907 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
908 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
909 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
910 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
911 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
912 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
913 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
914 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
915 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
916 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
917 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
918 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
919 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
920 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
921 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
922 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
923 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
924 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
925 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
926 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
927 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
928 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
929 ])
930
931 def extract_zip_file(zip_file, outdirectory):
932     import zipfile
933     import os
934
935     zf = zipfile.ZipFile(zip_file, 'r')
936     out = outdirectory
937     for path in zf.namelist():
938         tgt = os.path.join(out, path)
939         tgtdir = os.path.dirname(tgt)
940         if not os.path.exists(tgtdir):
941             os.makedirs(tgtdir)
942
943         if not tgt.endswith(os.sep):
944             fp = open(tgt, 'wb')
945             fp.write(zf.read(path))
946             fp.close()
947     zf.close()
948
949
950
951
952
953 if __name__ == '__main__':
954     import doctest
955     doctest.testmod()
956
957
958
959 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
960