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