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