make sms function working for the local service
[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     url = "http://api.urlsms.com/SendSMS.aspx"
385     #url = "http://196.7.150.220/http/sendmsg"
386     params = urllib.urlencode({'UserID': user, 'Password': password, 'SenderID': api_id, 'MsgText': text, 'RecipientMobileNo':to})
387     f = urllib.urlopen(url+"?"+params)
388     # FIXME: Use the logger if there is an error
389     return True
390
391 #---------------------------------------------------------
392 # Class that stores an updateable string (used in wizards)
393 #---------------------------------------------------------
394 class UpdateableStr(local):
395
396     def __init__(self, string=''):
397         self.string = string
398
399     def __str__(self):
400         return str(self.string)
401
402     def __repr__(self):
403         return str(self.string)
404
405     def __nonzero__(self):
406         return bool(self.string)
407
408
409 class UpdateableDict(local):
410     '''Stores an updateable dict to use in wizards'''
411
412     def __init__(self, dict=None):
413         if dict is None:
414             dict = {}
415         self.dict = dict
416
417     def __str__(self):
418         return str(self.dict)
419
420     def __repr__(self):
421         return str(self.dict)
422
423     def clear(self):
424         return self.dict.clear()
425
426     def keys(self):
427         return self.dict.keys()
428
429     def __setitem__(self, i, y):
430         self.dict.__setitem__(i, y)
431
432     def __getitem__(self, i):
433         return self.dict.__getitem__(i)
434
435     def copy(self):
436         return self.dict.copy()
437
438     def iteritems(self):
439         return self.dict.iteritems()
440
441     def iterkeys(self):
442         return self.dict.iterkeys()
443
444     def itervalues(self):
445         return self.dict.itervalues()
446
447     def pop(self, k, d=None):
448         return self.dict.pop(k, d)
449
450     def popitem(self):
451         return self.dict.popitem()
452
453     def setdefault(self, k, d=None):
454         return self.dict.setdefault(k, d)
455
456     def update(self, E, **F):
457         return self.dict.update(E, F)
458
459     def values(self):
460         return self.dict.values()
461
462     def get(self, k, d=None):
463         return self.dict.get(k, d)
464
465     def has_key(self, k):
466         return self.dict.has_key(k)
467
468     def items(self):
469         return self.dict.items()
470
471     def __cmp__(self, y):
472         return self.dict.__cmp__(y)
473
474     def __contains__(self, k):
475         return self.dict.__contains__(k)
476
477     def __delitem__(self, y):
478         return self.dict.__delitem__(y)
479
480     def __eq__(self, y):
481         return self.dict.__eq__(y)
482
483     def __ge__(self, y):
484         return self.dict.__ge__(y)
485
486     def __getitem__(self, y):
487         return self.dict.__getitem__(y)
488
489     def __gt__(self, y):
490         return self.dict.__gt__(y)
491
492     def __hash__(self):
493         return self.dict.__hash__()
494
495     def __iter__(self):
496         return self.dict.__iter__()
497
498     def __le__(self, y):
499         return self.dict.__le__(y)
500
501     def __len__(self):
502         return self.dict.__len__()
503
504     def __lt__(self, y):
505         return self.dict.__lt__(y)
506
507     def __ne__(self, y):
508         return self.dict.__ne__(y)
509
510
511 # Don't use ! Use res.currency.round()
512 class currency(float):
513
514     def __init__(self, value, accuracy=2, rounding=None):
515         if rounding is None:
516             rounding=10**-accuracy
517         self.rounding=rounding
518         self.accuracy=accuracy
519
520     def __new__(cls, value, accuracy=2, rounding=None):
521         return float.__new__(cls, round(value, accuracy))
522
523     #def __str__(self):
524     #   display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
525     #   return str(display_value)
526
527
528 def is_hashable(h):
529     try:
530         hash(h)
531         return True
532     except TypeError:
533         return False
534
535 class cache(object):
536     """
537     Use it as a decorator of the function you plan to cache
538     Timeout: 0 = no timeout, otherwise in seconds
539     """
540     
541     __caches = []
542     
543     def __init__(self, timeout=None, skiparg=2, multi=None):
544         assert skiparg >= 2 # at least self and cr
545         if timeout is None:
546             self.timeout = config['cache_timeout']
547         else:
548             self.timeout = timeout
549         self.skiparg = skiparg
550         self.multi = multi
551         self.lasttime = time.time()
552         self.cache = {}
553         self.fun = None 
554         cache.__caches.append(self)
555
556     
557     def _generate_keys(self, dbname, kwargs2):
558         """
559         Generate keys depending of the arguments and the self.mutli value
560         """
561         
562         def to_tuple(d):
563             i = d.items()
564             i.sort(key=lambda (x,y): x)
565             return tuple(i)
566
567         if not self.multi:
568             key = (('dbname', dbname),) + to_tuple(kwargs2)
569             yield key, None
570         else:
571             multis = kwargs2[self.multi][:]    
572             for id in multis:
573                 kwargs2[self.multi] = (id,)
574                 key = (('dbname', dbname),) + to_tuple(kwargs2)
575                 yield key, id
576     
577     def _unify_args(self, *args, **kwargs):
578         # Update named arguments with positional argument values (without self and cr)
579         kwargs2 = self.fun_default_values.copy()
580         kwargs2.update(kwargs)
581         kwargs2.update(dict(zip(self.fun_arg_names, args[self.skiparg-2:])))
582         for k in kwargs2:
583             if isinstance(kwargs2[k], (list, dict, set)):
584                 kwargs2[k] = tuple(kwargs2[k])
585             elif not is_hashable(kwargs2[k]):
586                 kwargs2[k] = repr(kwargs2[k])
587
588         return kwargs2
589     
590     def clear(self, dbname, *args, **kwargs):
591         """clear the cache for database dbname
592             if *args and **kwargs are both empty, clear all the keys related to this database
593         """
594         if not args and not kwargs:
595             keys_to_del = [key for key in self.cache if key[0][1] == dbname]
596         else:
597             kwargs2 = self._unify_args(*args, **kwargs)
598             keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache]
599         
600         for key in keys_to_del:
601             del self.cache[key]
602     
603     @classmethod
604     def clean_caches_for_db(cls, dbname):
605         for c in cls.__caches:
606             c.clear(dbname)
607
608     def __call__(self, fn):
609         if self.fun is not None:
610             raise Exception("Can not use a cache instance on more than one function")
611         self.fun = fn
612
613         argspec = inspect.getargspec(fn)
614         self.fun_arg_names = argspec[0][self.skiparg:]
615         self.fun_default_values = {}
616         if argspec[3]:
617             self.fun_default_values = dict(zip(self.fun_arg_names[-len(argspec[3]):], argspec[3]))
618         
619         def cached_result(self2, cr, *args, **kwargs):
620             if time.time()-self.timeout > self.lasttime:
621                 self.lasttime = time.time()
622                 t = time.time()-self.timeout 
623                 for key in self.cache.keys():
624                     if self.cache[key][1]<t:
625                         del self.cache[key]
626
627             kwargs2 = self._unify_args(*args, **kwargs)
628
629             result = {}
630             notincache = {}
631             for key, id in self._generate_keys(cr.dbname, kwargs2):
632                 if key in self.cache:
633                     result[id] = self.cache[key][0]
634                 else:
635                     notincache[id] = key
636             
637             if notincache:
638                 if self.multi:
639                     kwargs2[self.multi] = notincache.keys()
640                 
641                 result2 = fn(self2, cr, *args[2:self.skiparg], **kwargs2)
642                 if not self.multi:
643                     key = notincache[None]
644                     self.cache[key] = (result2, time.time())
645                     result[None] = result2
646                 else:
647                     for id in result2:
648                         key = notincache[id]
649                         self.cache[key] = (result2[id], time.time())
650                     result.update(result2)
651                         
652             if not self.multi:
653                 return result[None]
654             return result
655
656         cached_result.clear_cache = self.clear
657         return cached_result
658
659 def to_xml(s):
660     return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')
661
662 def ustr(value):
663     """This method is similar to the builtin `str` method, except
664     it will return Unicode string.
665
666     @param value: the value to convert
667
668     @rtype: unicode
669     @return: unicode string
670     """
671
672     if isinstance(value, unicode):
673         return value
674
675     if hasattr(value, '__unicode__'):
676         return unicode(value)
677
678     if not isinstance(value, str):
679         value = str(value)
680
681     try: # first try utf-8
682         return unicode(value, 'utf-8')
683     except:
684         pass
685
686     try: # then extened iso-8858
687         return unicode(value, 'iso-8859-15')
688     except:
689         pass
690
691     # else use default system locale
692     from locale import getlocale
693     return unicode(value, getlocale()[1])
694
695 def exception_to_unicode(e):
696     if hasattr(e, 'message'):
697         return ustr(e.message)
698     if hasattr(e, 'args'):
699         return "\n".join((ustr(a) for a in e.args))
700     try:
701         return ustr(e)
702     except:
703         return u"Unknow message"
704
705
706 # to be compatible with python 2.4
707 import __builtin__
708 if not hasattr(__builtin__, 'all'):
709     def all(iterable):
710         for element in iterable:
711             if not element:
712                return False
713         return True
714         
715     __builtin__.all = all
716     del all
717     
718 if not hasattr(__builtin__, 'any'):
719     def any(iterable):
720         for element in iterable:
721             if element:
722                return True
723         return False
724         
725     __builtin__.any = any
726     del any
727
728
729
730 def get_languages():
731     languages={
732         'ar_AR': u'Arabic / الْعَرَبيّة',
733         'bg_BG': u'Bulgarian / български',
734         'bs_BS': u'Bosnian / bosanski jezik',
735         'ca_ES': u'Catalan / Català',
736         'cs_CZ': u'Czech / Čeština',
737         'da_DK': u'Danish / Dansk',
738         'de_DE': u'German / Deutsch',
739         'el_EL': u'Greek / Ελληνικά',
740         'en_CA': u'English (CA)',
741         'en_EN': u'English (default)',
742         'en_GB': u'English (UK)',
743         'en_US': u'English (US)',
744         'es_AR': u'Spanish (AR) / Español (AR)',
745         'es_ES': u'Spanish / Español',
746         'et_EE': u'Estonian / Eesti keel',
747         'fr_BE': u'French (BE) / Français (BE)',
748         'fr_CH': u'French (CH) / Français (CH)',
749         'fr_FR': u'French / Français',
750         'hr_HR': u'Croatian / hrvatski jezik',
751         'hu_HU': u'Hungarian / Magyar',
752         'id_ID': u'Indonesian / Bahasa Indonesia',
753         'it_IT': u'Italian / Italiano',
754         'lt_LT': u'Lithuanian / Lietuvių kalba',
755         'nl_NL': u'Dutch / Nederlands',
756         'nl_BE': u'Dutch (Belgium) / Nederlands (Belgïe)',
757         'pl_PL': u'Polish / Język polski',
758         'pt_BR': u'Portugese (BR) / português (BR)',
759         'pt_PT': u'Portugese / português',
760         'ro_RO': u'Romanian / limba română',
761         'ru_RU': u'Russian / русский язык',
762         'sl_SL': u'Slovenian / slovenščina',
763         'sv_SE': u'Swedish / svenska',
764         'tr_TR': u'Turkish / Türkçe',
765         'uk_UK': u'Ukrainian / украї́нська мо́ва',
766         'zh_CN': u'Chinese (CN) / 简体中文' ,
767         'zh_TW': u'Chinese (TW) / 正體字',
768     }
769     return languages
770
771 def scan_languages():
772     import glob
773     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'))]
774     lang_dict = get_languages()
775     ret = [(lang, lang_dict.get(lang, lang)) for lang in file_list]
776     ret.sort(key=lambda k:k[1])
777     return ret
778
779
780 def get_user_companies(cr, user):
781     def _get_company_children(cr, ids):
782         if not ids:
783             return []
784         cr.execute('SELECT id FROM res_company WHERE parent_id = any(array[%s])' %(','.join([str(x) for x in ids]),))
785         res=[x[0] for x in cr.fetchall()]
786         res.extend(_get_company_children(cr, res))
787         return res
788     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,))
789     compids=[cr.fetchone()[0]]
790     compids.extend(_get_company_children(cr, compids))
791     return compids
792
793 def mod10r(number):
794     """
795     Input number : account or invoice number
796     Output return: the same number completed with the recursive mod10
797     key
798     """
799     codec=[0,9,4,6,8,2,7,1,3,5]
800     report = 0
801     result=""
802     for digit in number:
803         result += digit
804         if digit.isdigit():
805             report = codec[ (int(digit) + report) % 10 ]
806     return result + str((10 - report) % 10)
807
808
809 def human_size(sz):
810     """
811     Return the size in a human readable format
812     """
813     if not sz:
814         return False
815     units = ('bytes', 'Kb', 'Mb', 'Gb')
816     if isinstance(sz,basestring):
817         sz=len(sz)
818     s, i = float(sz), 0
819     while s >= 1024 and i < len(units)-1:
820         s = s / 1024
821         i = i + 1
822     return "%0.2f %s" % (s, units[i])
823
824 def logged(f):
825     from tools.func import wraps
826     
827     @wraps(f)
828     def wrapper(*args, **kwargs):
829         import netsvc
830         from pprint import pformat
831
832         vector = ['Call -> function: %r' % f]
833         for i, arg in enumerate(args):
834             vector.append('  arg %02d: %s' % (i, pformat(arg)))
835         for key, value in kwargs.items():
836             vector.append('  kwarg %10s: %s' % (key, pformat(value)))
837
838         timeb4 = time.time()
839         res = f(*args, **kwargs)
840         
841         vector.append('  result: %s' % pformat(res))
842         vector.append('  time delta: %s' % (time.time() - timeb4))
843         netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector))
844         return res
845
846     return wrapper
847
848 class profile(object):
849     def __init__(self, fname=None):
850         self.fname = fname
851
852     def __call__(self, f):
853         from tools.func import wraps
854
855         @wraps(f)
856         def wrapper(*args, **kwargs):
857             class profile_wrapper(object):
858                 def __init__(self):
859                     self.result = None
860                 def __call__(self):
861                     self.result = f(*args, **kwargs)
862             pw = profile_wrapper()
863             import cProfile
864             fname = self.fname or ("%s.cprof" % (f.func_name,))
865             cProfile.runctx('pw()', globals(), locals(), filename=fname)
866             return pw.result
867
868         return wrapper
869
870 def debug(what):
871     """
872         This method allow you to debug your code without print
873         Example:
874         >>> def func_foo(bar)
875         ...     baz = bar
876         ...     debug(baz)
877         ...     qnx = (baz, bar)
878         ...     debug(qnx)
879         ...
880         >>> func_foo(42)
881
882         This will output on the logger:
883         
884             [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42
885             [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42)
886
887         To view the DEBUG lines in the logger you must start the server with the option
888             --log-level=debug
889
890     """
891     import netsvc
892     from inspect import stack
893     import re
894     from pprint import pformat
895     st = stack()[1]
896     param = re.split("debug *\((.+)\)", st[4][0].strip())[1].strip()
897     while param.count(')') > param.count('('): param = param[:param.rfind(')')]
898     what = pformat(what)
899     if param != what:
900         what = "%s = %s" % (param, what)
901     netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what)
902
903
904 icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
905 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
906 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
907 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
908 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
909 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
910 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
911 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
912 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
913 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
914 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
915 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
916 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
917 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
918 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
919 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
920 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
921 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
922 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
923 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
924 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
925 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
926 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
927 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
928 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
929 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
930 ])
931
932 def extract_zip_file(zip_file, outdirectory):
933     import zipfile
934     import os
935
936     zf = zipfile.ZipFile(zip_file, 'r')
937     out = outdirectory
938     for path in zf.namelist():
939         tgt = os.path.join(out, path)
940         tgtdir = os.path.dirname(tgt)
941         if not os.path.exists(tgtdir):
942             os.makedirs(tgtdir)
943
944         if not tgt.endswith(os.sep):
945             fp = open(tgt, 'wb')
946             fp.write(zf.read(path))
947             fp.close()
948     zf.close()
949
950
951
952
953
954 if __name__ == '__main__':
955     import doctest
956     doctest.testmod()
957
958
959
960 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
961