1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
23 Miscelleanous tools used by OpenERP.
29 from config import config
36 if sys.version_info[:2] < (2, 4):
37 from threadinglocal import local
39 from threading import local
41 from itertools import izip
43 # initialize a database with base/base.sql
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()):
52 for i in addons.get_modules():
53 terp_file = addons.get_module_resource(i, '__openerp__.py')
54 mod_path = addons.get_module_path(i)
58 # if os.path.isfile(terp_file) or os.path.isfile(mod_path+'.zip'):
59 # info = eval(file_open(terp_file).read())
60 if not os.path.isfile(terp_file):
61 terp_file = addons.get_module_resource(i, '__terp__.py')
63 if os.path.isfile(terp_file) or os.path.isfile(mod_path+'.zip'):
64 info = eval(file_open(terp_file).read())
67 categs = info.get('category', 'Uncategorized').split('/')
71 cr.execute('select id \
72 from ir_module_category \
73 where name=%s and parent_id=%s', (categs[0], p_id))
75 cr.execute('select id \
76 from ir_module_category \
77 where name=%s and parent_id is NULL', (categs[0],))
80 cr.execute('select nextval(\'ir_module_category_id_seq\')')
81 c_id = cr.fetchone()[0]
82 cr.execute('insert into ir_module_category \
83 (id, name, parent_id) \
84 values (%s, %s, %s)', (c_id, categs[0], p_id))
90 active = info.get('active', False)
91 installable = info.get('installable', True)
98 state = 'uninstallable'
99 cr.execute('select nextval(\'ir_module_module_id_seq\')')
100 id = cr.fetchone()[0]
101 cr.execute('insert into ir_module_module \
102 (id, author, website, name, shortdesc, description, \
103 category_id, state, certificate) \
104 values (%s, %s, %s, %s, %s, %s, %s, %s, %s)', (
105 id, info.get('author', ''),
106 info.get('website', ''), i, info.get('name', False),
107 info.get('description', ''), p_id, state, info.get('certificate')))
108 cr.execute('insert into ir_model_data \
109 (name,model,module, res_id, noupdate) values (%s,%s,%s,%s,%s)', (
110 'module_meta_information', 'ir.module.module', i, id, True))
111 dependencies = info.get('depends', [])
112 for d in dependencies:
113 cr.execute('insert into ir_module_module_dependency \
114 (module_id,name) values (%s, %s)', (id, d))
117 def find_in_path(name):
122 path = [dir for dir in os.environ['PATH'].split(sep)
123 if os.path.isdir(dir)]
125 val = os.path.join(dir, name)
126 if os.path.isfile(val) or os.path.islink(val):
130 def find_pg_tool(name):
131 if config['pg_path'] and config['pg_path'] != 'None':
132 return os.path.join(config['pg_path'], name)
134 return find_in_path(name)
136 def exec_pg_command(name, *args):
137 prog = find_pg_tool(name)
139 raise Exception('Couldn\'t find %s' % name)
140 args2 = (os.path.basename(prog),) + args
141 return os.spawnv(os.P_WAIT, prog, args2)
143 def exec_pg_command_pipe(name, *args):
144 prog = find_pg_tool(name)
146 raise Exception('Couldn\'t find %s' % name)
148 cmd = '"' + prog + '" ' + ' '.join(args)
150 cmd = prog + ' ' + ' '.join(args)
151 return os.popen2(cmd, 'b')
153 def exec_command_pipe(name, *args):
154 prog = find_in_path(name)
156 raise Exception('Couldn\'t find %s' % name)
158 cmd = '"'+prog+'" '+' '.join(args)
160 cmd = prog+' '+' '.join(args)
161 return os.popen2(cmd, 'b')
163 #----------------------------------------------------------
165 #----------------------------------------------------------
166 #file_path_root = os.getcwd()
167 #file_path_addons = os.path.join(file_path_root, 'addons')
169 def file_open(name, mode="r", subdir='addons', pathinfo=False):
170 """Open a file from the OpenERP root, using a subdir folder.
172 >>> file_open('hr/report/timesheer.xsl')
173 >>> file_open('addons/hr/report/timesheet.xsl')
174 >>> file_open('../../base/report/rml_template.xsl', subdir='addons/hr/report', pathinfo=True)
176 @param name: name of the file
177 @param mode: file open mode
178 @param subdir: subdirectory
179 @param pathinfo: if True returns tupple (fileobject, filepath)
181 @return: fileobject if pathinfo is False else (fileobject, filepath)
184 adps = addons.ad_paths
185 rtp = os.path.normcase(os.path.abspath(config['root_path']))
187 if name.replace(os.path.sep, '/').startswith('addons/'):
191 # First try to locate in addons_path
194 if subdir2.replace(os.path.sep, '/').startswith('addons/'):
195 subdir2 = subdir2[7:]
197 subdir2 = (subdir2 != 'addons' or None) and subdir2
202 fn = os.path.join(adp, subdir2, name)
204 fn = os.path.join(adp, name)
205 fn = os.path.normpath(fn)
206 fo = file_open(fn, mode=mode, subdir=None, pathinfo=pathinfo)
214 name = os.path.join(rtp, subdir, name)
216 name = os.path.join(rtp, name)
218 name = os.path.normpath(name)
220 # Check for a zipfile in the path
225 head, tail = os.path.split(head)
229 zipname = os.path.join(tail, zipname)
232 if zipfile.is_zipfile(head+'.zip'):
233 from cStringIO import StringIO
234 zfile = zipfile.ZipFile(head+'.zip')
237 fo.write(zfile.read(os.path.join(
238 os.path.basename(head), zipname).replace(
245 name2 = os.path.normpath(os.path.join(head + '.zip', zipname))
247 for i in (name2, name):
248 if i and os.path.isfile(i):
253 if os.path.splitext(name)[1] == '.rml':
254 raise IOError, 'Report %s doesn\'t exist or deleted : ' %str(name)
255 raise IOError, 'File not found : '+str(name)
258 #----------------------------------------------------------
260 #----------------------------------------------------------
262 """Flatten a list of elements into a uniqu list
263 Author: Christophe Simonis (christophe@tinyerp.com)
272 >>> flatten( [[], [[]]] )
274 >>> flatten( [[['a','b'], 'c'], 'd', ['e', [], 'f']] )
275 ['a', 'b', 'c', 'd', 'e', 'f']
276 >>> t = (1,2,(3,), [4, 5, [6, [7], (8, 9), ([10, 11, (12, 13)]), [14, [], (15,)], []]])
278 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
282 return hasattr(x, "__iter__")
287 map(r.append, flatten(e))
292 def reverse_enumerate(l):
293 """Like enumerate but in the other sens
294 >>> a = ['a', 'b', 'c']
295 >>> it = reverse_enumerate(a)
303 Traceback (most recent call last):
304 File "<stdin>", line 1, in <module>
307 return izip(xrange(len(l)-1, -1, -1), reversed(l))
309 #----------------------------------------------------------
311 #----------------------------------------------------------
312 email_re = re.compile(r"""
313 ([a-zA-Z][\w\.-]*[a-zA-Z0-9] # username part
315 [a-zA-Z0-9][\w\.-]* # domain must start with a letter ... Ged> why do we include a 0-9 then?
320 res_re = re.compile(r"\[([0-9]+)\]", re.UNICODE)
321 command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
322 reference_re = re.compile("<.*-openobject-(\\d+)@(.*)>", re.UNICODE)
332 def html2plaintext(html, body_id=None, encoding='utf-8'):
333 ## (c) Fry-IT, www.fry-it.com, 2007
334 ## <peter@fry-it.com>
335 ## download here: http://www.peterbe.com/plog/html2plaintext
338 """ from an HTML text, convert the HTML to plain text.
339 If @body_id is provided then this is the tag where the
340 body (not necessarily <body>) starts.
343 from BeautifulSoup import BeautifulSoup, SoupStrainer, Comment
348 if body_id is not None:
349 strainer = SoupStrainer(id=body_id)
351 strainer = SoupStrainer('body')
353 soup = BeautifulSoup(html, parseOnlyThese=strainer, fromEncoding=encoding)
354 for link in soup.findAll('a'):
355 title = link.renderContents()
356 for url in [x[1] for x in link.attrs if x[0]=='href']:
357 urls.append(dict(url=url, tag=str(link), title=title))
359 html = soup.__str__()
364 if d['title'] == d['url'] or 'http://'+d['title'] == d['url']:
365 html = html.replace(d['tag'], d['url'])
368 html = html.replace(d['tag'], '%s [%s]' % (d['title'], i))
369 url_index.append(d['url'])
371 html = html.replace('<strong>','*').replace('</strong>','*')
372 html = html.replace('<b>','*').replace('</b>','*')
373 html = html.replace('<h3>','*').replace('</h3>','*')
374 html = html.replace('<h2>','**').replace('</h2>','**')
375 html = html.replace('<h1>','**').replace('</h1>','**')
376 html = html.replace('<em>','/').replace('</em>','/')
379 # the only line breaks we respect is those of ending tags and
382 html = html.replace('\n',' ')
383 html = html.replace('<br>', '\n')
384 html = html.replace('<tr>', '\n')
385 html = html.replace('</p>', '\n\n')
386 html = re.sub('<br\s*/>', '\n', html)
387 html = html.replace(' ' * 2, ' ')
390 # for all other tags we failed to clean up, just remove then and
391 # complain about them on the stderr
392 def desperate_fixer(g):
393 #print >>sys.stderr, "failed to clean up %s" % str(g.group())
396 html = re.sub('<.*?>', desperate_fixer, html)
399 html = '\n'.join([x.lstrip() for x in html.splitlines()])
401 for i, url in enumerate(url_index):
404 html += '[%s] %s\n' % (i+1, url)
407 def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
408 attach=None, openobject_id=False, ssl=False, debug=False, subtype='plain', x_headers=None, priority='3'):
414 `email_from`: A string used to fill the `From` header, if falsy,
415 config['email_from'] is used instead. Also used for
416 the `Reply-To` header if `reply_to` is not provided
418 `email_to`: a sequence of addresses to send the mail to.
421 from email.MIMEText import MIMEText
422 from email.MIMEBase import MIMEBase
423 from email.MIMEMultipart import MIMEMultipart
424 from email.Header import Header
425 from email.Utils import formatdate, COMMASPACE
426 from email.Utils import formatdate, COMMASPACE
427 from email import Encoders
430 if x_headers is None:
433 if not ssl: ssl = config.get('smtp_ssl', False)
435 if not (email_from or config['email_from']):
436 raise ValueError("Sending an email requires either providing a sender "
437 "address or having configured one")
439 if not email_from: email_from = config.get('email_from', False)
441 if not email_cc: email_cc = []
442 if not email_bcc: email_bcc = []
443 if not body: body = u''
444 try: email_body = body.encode('utf-8')
445 except (UnicodeEncodeError, UnicodeDecodeError):
449 email_text = MIMEText(email_body.encode('utf8') or '',_subtype=subtype,_charset='utf-8')
451 email_text = MIMEText(email_body or '',_subtype=subtype,_charset='utf-8')
453 if attach: msg = MIMEMultipart()
454 else: msg = email_text
456 msg['Subject'] = Header(ustr(subject), 'utf-8')
457 msg['From'] = email_from
460 msg['Reply-To'] = reply_to
462 msg['Reply-To'] = msg['From']
463 msg['To'] = COMMASPACE.join(email_to)
465 msg['Cc'] = COMMASPACE.join(email_cc)
467 msg['Bcc'] = COMMASPACE.join(email_bcc)
468 msg['Date'] = formatdate(localtime=True)
470 # Add OpenERP Server information
471 msg['X-Generated-By'] = 'OpenERP (http://www.openerp.com)'
472 msg['X-OpenERP-Server-Host'] = socket.gethostname()
473 msg['X-OpenERP-Server-Version'] = release.version
475 msg['X-Priority'] = priorities.get(priority, '3 (Normal)')
477 # Add dynamic X Header
478 for key, value in x_headers.iteritems():
479 msg['X-OpenERP-%s' % key] = str(value)
482 msg['Message-Id'] = "<%s-openobject-%s@%s>" % (time.time(), openobject_id, socket.gethostname())
485 msg.attach(email_text)
486 for (fname,fcontent) in attach:
487 part = MIMEBase('application', "octet-stream")
488 part.set_payload( fcontent )
489 Encoders.encode_base64(part)
490 part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
493 class WriteToLogger(object):
495 self.logger = netsvc.Logger()
498 self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s)
500 smtp_server = config['smtp_server']
501 if smtp_server.startswith('maildir:/'):
502 from mailbox import Maildir
503 maildir_path = smtp_server[8:]
505 mdir = Maildir(maildir_path,factory=None, create = True)
506 mdir.add(msg.as_string(True))
509 netsvc.Logger().notifyChannel('email_send (maildir)', netsvc.LOG_ERROR, e)
513 oldstderr = smtplib.stderr
516 # in case of debug, the messages are printed to stderr.
518 smtplib.stderr = WriteToLogger()
520 s.set_debuglevel(int(bool(debug))) # 0 or 1
521 s.connect(smtp_server, config['smtp_port'])
527 if config['smtp_user'] or config['smtp_password']:
528 s.login(config['smtp_user'], config['smtp_password'])
529 s.sendmail(email_from,
530 flatten([email_to, email_cc, email_bcc]),
536 smtplib.stderr = oldstderr
539 netsvc.Logger().notifyChannel('email_send', netsvc.LOG_ERROR, e)
544 #----------------------------------------------------------
546 #----------------------------------------------------------
547 # text must be latin-1 encoded
548 def sms_send(user, password, api_id, text, to):
550 url = "http://api.urlsms.com/SendSMS.aspx"
551 #url = "http://196.7.150.220/http/sendmsg"
552 params = urllib.urlencode({'UserID': user, 'Password': password, 'SenderID': api_id, 'MsgText': text, 'RecipientMobileNo':to})
553 f = urllib.urlopen(url+"?"+params)
554 # FIXME: Use the logger if there is an error
557 #---------------------------------------------------------
558 # Class that stores an updateable string (used in wizards)
559 #---------------------------------------------------------
560 class UpdateableStr(local):
562 def __init__(self, string=''):
566 return str(self.string)
569 return str(self.string)
571 def __nonzero__(self):
572 return bool(self.string)
575 class UpdateableDict(local):
576 '''Stores an updateable dict to use in wizards'''
578 def __init__(self, dict=None):
584 return str(self.dict)
587 return str(self.dict)
590 return self.dict.clear()
593 return self.dict.keys()
595 def __setitem__(self, i, y):
596 self.dict.__setitem__(i, y)
598 def __getitem__(self, i):
599 return self.dict.__getitem__(i)
602 return self.dict.copy()
605 return self.dict.iteritems()
608 return self.dict.iterkeys()
610 def itervalues(self):
611 return self.dict.itervalues()
613 def pop(self, k, d=None):
614 return self.dict.pop(k, d)
617 return self.dict.popitem()
619 def setdefault(self, k, d=None):
620 return self.dict.setdefault(k, d)
622 def update(self, E, **F):
623 return self.dict.update(E, F)
626 return self.dict.values()
628 def get(self, k, d=None):
629 return self.dict.get(k, d)
631 def has_key(self, k):
632 return self.dict.has_key(k)
635 return self.dict.items()
637 def __cmp__(self, y):
638 return self.dict.__cmp__(y)
640 def __contains__(self, k):
641 return self.dict.__contains__(k)
643 def __delitem__(self, y):
644 return self.dict.__delitem__(y)
647 return self.dict.__eq__(y)
650 return self.dict.__ge__(y)
653 return self.dict.__gt__(y)
656 return self.dict.__hash__()
659 return self.dict.__iter__()
662 return self.dict.__le__(y)
665 return self.dict.__len__()
668 return self.dict.__lt__(y)
671 return self.dict.__ne__(y)
674 # Don't use ! Use res.currency.round()
675 class currency(float):
677 def __init__(self, value, accuracy=2, rounding=None):
679 rounding=10**-accuracy
680 self.rounding=rounding
681 self.accuracy=accuracy
683 def __new__(cls, value, accuracy=2, rounding=None):
684 return float.__new__(cls, round(value, accuracy))
687 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
688 # return str(display_value)
700 Use it as a decorator of the function you plan to cache
701 Timeout: 0 = no timeout, otherwise in seconds
706 def __init__(self, timeout=None, skiparg=2, multi=None):
707 assert skiparg >= 2 # at least self and cr
709 self.timeout = config['cache_timeout']
711 self.timeout = timeout
712 self.skiparg = skiparg
714 self.lasttime = time.time()
717 cache.__caches.append(self)
720 def _generate_keys(self, dbname, kwargs2):
722 Generate keys depending of the arguments and the self.mutli value
727 pairs.sort(key=lambda (k,v): k)
728 for i, (k, v) in enumerate(pairs):
729 if isinstance(v, dict):
730 pairs[i] = (k, to_tuple(v))
731 if isinstance(v, (list, set)):
732 pairs[i] = (k, tuple(v))
733 elif not is_hashable(v):
734 pairs[i] = (k, repr(v))
738 key = (('dbname', dbname),) + to_tuple(kwargs2)
741 multis = kwargs2[self.multi][:]
743 kwargs2[self.multi] = (id,)
744 key = (('dbname', dbname),) + to_tuple(kwargs2)
747 def _unify_args(self, *args, **kwargs):
748 # Update named arguments with positional argument values (without self and cr)
749 kwargs2 = self.fun_default_values.copy()
750 kwargs2.update(kwargs)
751 kwargs2.update(dict(zip(self.fun_arg_names, args[self.skiparg-2:])))
754 def clear(self, dbname, *args, **kwargs):
755 """clear the cache for database dbname
756 if *args and **kwargs are both empty, clear all the keys related to this database
758 if not args and not kwargs:
759 keys_to_del = [key for key in self.cache.keys() if key[0][1] == dbname]
761 kwargs2 = self._unify_args(*args, **kwargs)
762 keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache.keys()]
764 for key in keys_to_del:
768 def clean_caches_for_db(cls, dbname):
769 for c in cls.__caches:
772 def __call__(self, fn):
773 if self.fun is not None:
774 raise Exception("Can not use a cache instance on more than one function")
777 argspec = inspect.getargspec(fn)
778 self.fun_arg_names = argspec[0][self.skiparg:]
779 self.fun_default_values = {}
781 self.fun_default_values = dict(zip(self.fun_arg_names[-len(argspec[3]):], argspec[3]))
783 def cached_result(self2, cr, *args, **kwargs):
784 if time.time()-int(self.timeout) > self.lasttime:
785 self.lasttime = time.time()
786 t = time.time()-int(self.timeout)
787 old_keys = [key for key in self.cache.keys() if self.cache[key][1] < t]
791 kwargs2 = self._unify_args(*args, **kwargs)
795 for key, id in self._generate_keys(cr.dbname, kwargs2):
796 if key in self.cache:
797 result[id] = self.cache[key][0]
803 kwargs2[self.multi] = notincache.keys()
805 result2 = fn(self2, cr, *args[:self.skiparg-2], **kwargs2)
807 key = notincache[None]
808 self.cache[key] = (result2, time.time())
809 result[None] = result2
813 self.cache[key] = (result2[id], time.time())
814 result.update(result2)
820 cached_result.clear_cache = self.clear
824 return s.replace('&','&').replace('<','<').replace('>','>')
827 """This method is similar to the builtin `str` method, except
828 it will return Unicode string.
830 @param value: the value to convert
833 @return: unicode string
836 if isinstance(value, unicode):
839 if hasattr(value, '__unicode__'):
840 return unicode(value)
842 if not isinstance(value, str):
845 try: # first try utf-8
846 return unicode(value, 'utf-8')
850 try: # then extened iso-8858
851 return unicode(value, 'iso-8859-15')
855 # else use default system locale
856 from locale import getlocale
857 return unicode(value, getlocale()[1])
859 def exception_to_unicode(e):
860 if (sys.version_info[:2] < (2,6)) and hasattr(e, 'message'):
861 return ustr(e.message)
862 if hasattr(e, 'args'):
863 return "\n".join((ustr(a) for a in e.args))
867 return u"Unknown message"
870 # to be compatible with python 2.4
872 if not hasattr(__builtin__, 'all'):
874 for element in iterable:
879 __builtin__.all = all
882 if not hasattr(__builtin__, 'any'):
884 for element in iterable:
889 __builtin__.any = any
892 def get_iso_codes(lang):
893 if lang.find('_') != -1:
894 if lang.split('_')[0] == lang.split('_')[1].lower():
895 lang = lang.split('_')[0]
900 'ab_RU': u'Abkhazian (RU)',
901 'ar_AR': u'Arabic / الْعَرَبيّة',
902 'bg_BG': u'Bulgarian / български',
903 'bs_BS': u'Bosnian / bosanski jezik',
904 'ca_ES': u'Catalan / Català',
905 'cs_CZ': u'Czech / Čeština',
906 'da_DK': u'Danish / Dansk',
907 'de_DE': u'German / Deutsch',
908 'el_GR': u'Greek / Ελληνικά',
909 'en_CA': u'English (CA)',
910 'en_GB': u'English (UK)',
911 'en_US': u'English (US)',
912 'es_AR': u'Spanish (AR) / Español (AR)',
913 'es_ES': u'Spanish / Español',
914 'et_EE': u'Estonian / Eesti keel',
915 'fa_IR': u'Persian / Iran, Islamic Republic of',
916 'fi_FI': u'Finland / Suomi',
917 'fr_BE': u'French (BE) / Français (BE)',
918 'fr_CH': u'French (CH) / Français (CH)',
919 'fr_FR': u'French / Français',
920 'gl_ES': u'Galician / Spain',
921 'gu_IN': u'Gujarati / India',
922 'hi_IN': u'Hindi / India',
923 'hr_HR': u'Croatian / hrvatski jezik',
924 'hu_HU': u'Hungarian / Magyar',
925 'id_ID': u'Indonesian / Bahasa Indonesia',
926 'it_IT': u'Italian / Italiano',
927 'iu_CA': u'Inuktitut / Canada',
928 'ja_JP': u'Japanese / Japan',
929 'ko_KP': u'Korean / Korea, Democratic Peoples Republic of',
930 'ko_KR': u'Korean / Korea, Republic of',
931 'lt_LT': u'Lithuanian / Lietuvių kalba',
932 'lv_LV': u'Latvian / Latvia',
933 'ml_IN': u'Malayalam / India',
934 'mn_MN': u'Mongolian / Mongolia',
935 'nb_NO': u'Norwegian Bokmål / Norway',
936 'nl_NL': u'Dutch / Nederlands',
937 'nl_BE': u'Dutch (Belgium) / Nederlands (Belgïe)',
938 'oc_FR': u'Occitan (post 1500) / France',
939 'pl_PL': u'Polish / Język polski',
940 'pt_BR': u'Portugese (BR) / português (BR)',
941 'pt_PT': u'Portugese / português',
942 'ro_RO': u'Romanian / limba română',
943 'ru_RU': u'Russian / русский язык',
944 'si_LK': u'Sinhalese / Sri Lanka',
945 'sk_SK': u'Slovak / Slovakia',
946 'sl_SL': u'Slovenian / slovenščina',
947 'sq_AL': u'Albanian / Shqipëri',
948 'sr_RS': u'Serbian / Serbia',
949 'sv_SE': u'Swedish / svenska',
950 'te_IN': u'Telugu / India',
951 'tr_TR': u'Turkish / Türkçe',
952 'vi_VN': u'Vietnam / Cộng hòa xã hội chủ nghĩa Việt Nam',
953 'uk_UA': u'Ukrainian / украї́нська мо́ва',
954 'ur_PK': u'Urdu / Pakistan',
955 'zh_CN': u'Chinese (CN) / 简体中文',
956 'zh_HK': u'Chinese (HK)',
957 'zh_TW': u'Chinese (TW) / 正體字',
958 'th_TH': u'Thai / ภาษาไทย',
959 'tlh_TLH': u'Klingon',
963 def scan_languages():
965 # 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'))]
966 # ret = [(lang, lang_dict.get(lang, lang)) for lang in file_list]
967 # Now it will take all languages from get languages function without filter it with base module languages
968 lang_dict = get_languages()
969 ret = [(lang, lang_dict.get(lang, lang)) for lang in list(lang_dict)]
970 ret.sort(key=lambda k:k[1])
974 def get_user_companies(cr, user):
975 def _get_company_children(cr, ids):
978 cr.execute('SELECT id FROM res_company WHERE parent_id = ANY (%s)', (ids,))
979 res=[x[0] for x in cr.fetchall()]
980 res.extend(_get_company_children(cr, res))
982 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,))
983 compids=[cr.fetchone()[0]]
984 compids.extend(_get_company_children(cr, compids))
989 Input number : account or invoice number
990 Output return: the same number completed with the recursive mod10
993 codec=[0,9,4,6,8,2,7,1,3,5]
999 report = codec[ (int(digit) + report) % 10 ]
1000 return result + str((10 - report) % 10)
1005 Return the size in a human readable format
1009 units = ('bytes', 'Kb', 'Mb', 'Gb')
1010 if isinstance(sz,basestring):
1013 while s >= 1024 and i < len(units)-1:
1016 return "%0.2f %s" % (s, units[i])
1019 from tools.func import wraps
1022 def wrapper(*args, **kwargs):
1024 from pprint import pformat
1026 vector = ['Call -> function: %r' % f]
1027 for i, arg in enumerate(args):
1028 vector.append(' arg %02d: %s' % (i, pformat(arg)))
1029 for key, value in kwargs.items():
1030 vector.append(' kwarg %10s: %s' % (key, pformat(value)))
1032 timeb4 = time.time()
1033 res = f(*args, **kwargs)
1035 vector.append(' result: %s' % pformat(res))
1036 vector.append(' time delta: %s' % (time.time() - timeb4))
1037 netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector))
1042 class profile(object):
1043 def __init__(self, fname=None):
1046 def __call__(self, f):
1047 from tools.func import wraps
1050 def wrapper(*args, **kwargs):
1051 class profile_wrapper(object):
1055 self.result = f(*args, **kwargs)
1056 pw = profile_wrapper()
1058 fname = self.fname or ("%s.cprof" % (f.func_name,))
1059 cProfile.runctx('pw()', globals(), locals(), filename=fname)
1066 This method allow you to debug your code without print
1068 >>> def func_foo(bar)
1071 ... qnx = (baz, bar)
1076 This will output on the logger:
1078 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42
1079 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42)
1081 To view the DEBUG lines in the logger you must start the server with the option
1086 from inspect import stack
1088 from pprint import pformat
1090 param = re.split("debug *\((.+)\)", st[4][0].strip())[1].strip()
1091 while param.count(')') > param.count('('): param = param[:param.rfind(')')]
1092 what = pformat(what)
1094 what = "%s = %s" % (param, what)
1095 netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what)
1098 icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
1099 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
1100 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
1101 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
1102 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
1103 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
1104 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
1105 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
1106 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
1107 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
1108 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
1109 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
1110 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
1111 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
1112 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
1113 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
1114 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
1115 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
1116 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
1117 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
1118 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
1119 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
1120 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
1121 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
1122 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
1123 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
1126 def extract_zip_file(zip_file, outdirectory):
1130 zf = zipfile.ZipFile(zip_file, 'r')
1132 for path in zf.namelist():
1133 tgt = os.path.join(out, path)
1134 tgtdir = os.path.dirname(tgt)
1135 if not os.path.exists(tgtdir):
1138 if not tgt.endswith(os.sep):
1139 fp = open(tgt, 'wb')
1140 fp.write(zf.read(path))
1144 def detect_ip_addr():
1145 def _detect_ip_addr():
1146 from array import array
1148 from struct import pack, unpack
1157 if not fcntl: # not UNIX:
1158 host = socket.gethostname()
1159 ip_addr = socket.gethostbyname(host)
1161 # get all interfaces:
1163 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1164 names = array('B', '\0' * nbytes)
1165 #print 'names: ', names
1166 outbytes = unpack('iL', fcntl.ioctl( s.fileno(), 0x8912, pack('iL', nbytes, names.buffer_info()[0])))[0]
1167 namestr = names.tostring()
1169 # try 64 bit kernel:
1170 for i in range(0, outbytes, 40):
1171 name = namestr[i:i+16].split('\0', 1)[0]
1173 ip_addr = socket.inet_ntoa(namestr[i+20:i+24])
1176 # try 32 bit kernel:
1178 ifaces = filter(None, [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)])
1180 for ifname in [iface for iface in ifaces if iface != 'lo']:
1181 ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24])
1184 return ip_addr or 'localhost'
1187 ip_addr = _detect_ip_addr()
1189 ip_addr = 'localhost'
1192 # RATIONALE BEHIND TIMESTAMP CALCULATIONS AND TIMEZONE MANAGEMENT:
1193 # The server side never does any timestamp calculation, always
1194 # sends them in a naive (timezone agnostic) format supposed to be
1195 # expressed within the server timezone, and expects the clients to
1196 # provide timestamps in the server timezone as well.
1197 # It stores all timestamps in the database in naive format as well,
1198 # which also expresses the time in the server timezone.
1199 # For this reason the server makes its timezone name available via the
1200 # common/timezone_get() rpc method, which clients need to read
1201 # to know the appropriate time offset to use when reading/writing
1203 def get_win32_timezone():
1204 """Attempt to return the "standard name" of the current timezone on a win32 system.
1205 @return: the standard name of the current win32 timezone, or False if it cannot be found.
1208 if (sys.platform == "win32"):
1211 hklm = _winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
1212 current_tz_key = _winreg.OpenKey(hklm, r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation", 0,_winreg.KEY_ALL_ACCESS)
1213 res = str(_winreg.QueryValueEx(current_tz_key,"StandardName")[0]) # [0] is value, [1] is type code
1214 _winreg.CloseKey(current_tz_key)
1215 _winreg.CloseKey(hklm)
1220 def detect_server_timezone():
1221 """Attempt to detect the timezone to use on the server side.
1222 Defaults to UTC if no working timezone can be found.
1223 @return: the timezone identifier as expected by pytz.timezone.
1230 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1231 "Python pytz module is not available. Timezone will be set to UTC by default.")
1234 # Option 1: the configuration option (did not exist before, so no backwards compatibility issue)
1235 # Option 2: to be backwards compatible with 5.0 or earlier, the value from time.tzname[0], but only if it is known to pytz
1236 # Option 3: the environment variable TZ
1237 sources = [ (config['timezone'], 'OpenERP configuration'),
1238 (time.tzname[0], 'time.tzname'),
1239 (os.environ.get('TZ',False),'TZ environment variable'), ]
1240 # Option 4: OS-specific: /etc/timezone on Unix
1241 if (os.path.exists("/etc/timezone")):
1244 f = open("/etc/timezone")
1245 tz_value = f.read(128).strip()
1250 sources.append((tz_value,"/etc/timezone file"))
1251 # Option 5: timezone info from registry on Win32
1252 if (sys.platform == "win32"):
1253 # Timezone info is stored in windows registry.
1254 # However this is not likely to work very well as the standard name
1255 # of timezones in windows is rarely something that is known to pytz.
1256 # But that's ok, it is always possible to use a config option to set
1258 sources.append((get_win32_timezone(),"Windows Registry"))
1260 for (value,source) in sources:
1263 tz = pytz.timezone(value)
1264 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_INFO,
1265 "Using timezone %s obtained from %s." % (tz.zone,source))
1267 except pytz.UnknownTimeZoneError:
1268 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1269 "The timezone specified in %s (%s) is invalid, ignoring it." % (source,value))
1271 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1272 "No valid timezone could be detected, using default UTC timezone. You can specify it explicitly with option 'timezone' in the server configuration.")
1276 if __name__ == '__main__':
1281 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: