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