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
35 from itertools import islice
37 if sys.version_info[:2] < (2, 4):
38 from threadinglocal import local
40 from threading import local
42 from itertools import izip
44 # initialize a database with base/base.sql
47 f = addons.get_module_resource('base', 'base.sql')
48 for line in file_open(f).read().split(';'):
49 if (len(line)>0) and (not line.isspace()):
53 for i in addons.get_modules():
54 mod_path = addons.get_module_path(i)
58 info = addons.load_information_from_description_file(i)
62 categs = info.get('category', 'Uncategorized').split('/')
66 cr.execute('select id \
67 from ir_module_category \
68 where name=%s and parent_id=%s', (categs[0], p_id))
70 cr.execute('select id \
71 from ir_module_category \
72 where name=%s and parent_id is NULL', (categs[0],))
75 cr.execute('select nextval(\'ir_module_category_id_seq\')')
76 c_id = cr.fetchone()[0]
77 cr.execute('insert into ir_module_category \
78 (id, name, parent_id) \
79 values (%s, %s, %s)', (c_id, categs[0], p_id))
85 active = info.get('active', False)
86 installable = info.get('installable', True)
93 state = 'uninstallable'
94 cr.execute('select nextval(\'ir_module_module_id_seq\')')
96 cr.execute('insert into ir_module_module \
97 (id, author, website, name, shortdesc, description, \
98 category_id, state, certificate) \
99 values (%s, %s, %s, %s, %s, %s, %s, %s, %s)', (
100 id, info.get('author', ''),
101 info.get('website', ''), i, info.get('name', False),
102 info.get('description', ''), p_id, state, info.get('certificate')))
103 cr.execute('insert into ir_model_data \
104 (name,model,module, res_id, noupdate) values (%s,%s,%s,%s,%s)', (
105 'module_meta_information', 'ir.module.module', i, id, True))
106 dependencies = info.get('depends', [])
107 for d in dependencies:
108 cr.execute('insert into ir_module_module_dependency \
109 (module_id,name) values (%s, %s)', (id, d))
112 def find_in_path(name):
117 path = [dir for dir in os.environ['PATH'].split(sep)
118 if os.path.isdir(dir)]
120 val = os.path.join(dir, name)
121 if os.path.isfile(val) or os.path.islink(val):
125 def find_pg_tool(name):
126 if config['pg_path'] and config['pg_path'] != 'None':
127 return os.path.join(config['pg_path'], name)
129 return find_in_path(name)
131 def exec_pg_command(name, *args):
132 prog = find_pg_tool(name)
134 raise Exception('Couldn\'t find %s' % name)
135 args2 = (os.path.basename(prog),) + args
136 return os.spawnv(os.P_WAIT, prog, args2)
138 def exec_pg_command_pipe(name, *args):
139 prog = find_pg_tool(name)
141 raise Exception('Couldn\'t find %s' % name)
143 cmd = '"' + prog + '" ' + ' '.join(args)
145 cmd = prog + ' ' + ' '.join(args)
146 return os.popen2(cmd, 'b')
148 def exec_command_pipe(name, *args):
149 prog = find_in_path(name)
151 raise Exception('Couldn\'t find %s' % name)
153 cmd = '"'+prog+'" '+' '.join(args)
155 cmd = prog+' '+' '.join(args)
156 return os.popen2(cmd, 'b')
158 #----------------------------------------------------------
160 #----------------------------------------------------------
161 #file_path_root = os.getcwd()
162 #file_path_addons = os.path.join(file_path_root, 'addons')
164 def file_open(name, mode="r", subdir='addons', pathinfo=False):
165 """Open a file from the OpenERP root, using a subdir folder.
167 >>> file_open('hr/report/timesheer.xsl')
168 >>> file_open('addons/hr/report/timesheet.xsl')
169 >>> file_open('../../base/report/rml_template.xsl', subdir='addons/hr/report', pathinfo=True)
171 @param name: name of the file
172 @param mode: file open mode
173 @param subdir: subdirectory
174 @param pathinfo: if True returns tupple (fileobject, filepath)
176 @return: fileobject if pathinfo is False else (fileobject, filepath)
179 adps = addons.ad_paths
180 rtp = os.path.normcase(os.path.abspath(config['root_path']))
182 if name.replace(os.path.sep, '/').startswith('addons/'):
186 # First try to locate in addons_path
189 if subdir2.replace(os.path.sep, '/').startswith('addons/'):
190 subdir2 = subdir2[7:]
192 subdir2 = (subdir2 != 'addons' or None) and subdir2
197 fn = os.path.join(adp, subdir2, name)
199 fn = os.path.join(adp, name)
200 fn = os.path.normpath(fn)
201 fo = file_open(fn, mode=mode, subdir=None, pathinfo=pathinfo)
209 name = os.path.join(rtp, subdir, name)
211 name = os.path.join(rtp, name)
213 name = os.path.normpath(name)
215 # Check for a zipfile in the path
220 head, tail = os.path.split(head)
224 zipname = os.path.join(tail, zipname)
227 if zipfile.is_zipfile(head+'.zip'):
228 from cStringIO import StringIO
229 zfile = zipfile.ZipFile(head+'.zip')
232 fo.write(zfile.read(os.path.join(
233 os.path.basename(head), zipname).replace(
240 name2 = os.path.normpath(os.path.join(head + '.zip', zipname))
242 for i in (name2, name):
243 if i and os.path.isfile(i):
248 if os.path.splitext(name)[1] == '.rml':
249 raise IOError, 'Report %s doesn\'t exist or deleted : ' %str(name)
250 raise IOError, 'File not found : '+str(name)
253 #----------------------------------------------------------
255 #----------------------------------------------------------
257 """Flatten a list of elements into a uniqu list
258 Author: Christophe Simonis (christophe@tinyerp.com)
267 >>> flatten( [[], [[]]] )
269 >>> flatten( [[['a','b'], 'c'], 'd', ['e', [], 'f']] )
270 ['a', 'b', 'c', 'd', 'e', 'f']
271 >>> t = (1,2,(3,), [4, 5, [6, [7], (8, 9), ([10, 11, (12, 13)]), [14, [], (15,)], []]])
273 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
277 return hasattr(x, "__iter__")
282 map(r.append, flatten(e))
287 def reverse_enumerate(l):
288 """Like enumerate but in the other sens
289 >>> a = ['a', 'b', 'c']
290 >>> it = reverse_enumerate(a)
298 Traceback (most recent call last):
299 File "<stdin>", line 1, in <module>
302 return izip(xrange(len(l)-1, -1, -1), reversed(l))
304 #----------------------------------------------------------
306 #----------------------------------------------------------
307 email_re = re.compile(r"""
308 ([a-zA-Z][\w\.-]*[a-zA-Z0-9] # username part
310 [a-zA-Z0-9][\w\.-]* # domain must start with a letter ... Ged> why do we include a 0-9 then?
315 res_re = re.compile(r"\[([0-9]+)\]", re.UNICODE)
316 command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
317 reference_re = re.compile("<.*-openobject-(\\d+)@(.*)>", re.UNICODE)
327 def html2plaintext(html, body_id=None, encoding='utf-8'):
328 ## (c) Fry-IT, www.fry-it.com, 2007
329 ## <peter@fry-it.com>
330 ## download here: http://www.peterbe.com/plog/html2plaintext
333 """ from an HTML text, convert the HTML to plain text.
334 If @body_id is provided then this is the tag where the
335 body (not necessarily <body>) starts.
338 from BeautifulSoup import BeautifulSoup, SoupStrainer, Comment
343 if body_id is not None:
344 strainer = SoupStrainer(id=body_id)
346 strainer = SoupStrainer('body')
348 soup = BeautifulSoup(html, parseOnlyThese=strainer, fromEncoding=encoding)
349 for link in soup.findAll('a'):
350 title = link.renderContents()
351 for url in [x[1] for x in link.attrs if x[0]=='href']:
352 urls.append(dict(url=url, tag=str(link), title=title))
354 html = soup.__str__()
359 if d['title'] == d['url'] or 'http://'+d['title'] == d['url']:
360 html = html.replace(d['tag'], d['url'])
363 html = html.replace(d['tag'], '%s [%s]' % (d['title'], i))
364 url_index.append(d['url'])
366 html = html.replace('<strong>','*').replace('</strong>','*')
367 html = html.replace('<b>','*').replace('</b>','*')
368 html = html.replace('<h3>','*').replace('</h3>','*')
369 html = html.replace('<h2>','**').replace('</h2>','**')
370 html = html.replace('<h1>','**').replace('</h1>','**')
371 html = html.replace('<em>','/').replace('</em>','/')
374 # the only line breaks we respect is those of ending tags and
377 html = html.replace('\n',' ')
378 html = html.replace('<br>', '\n')
379 html = html.replace('<tr>', '\n')
380 html = html.replace('</p>', '\n\n')
381 html = re.sub('<br\s*/>', '\n', html)
382 html = html.replace(' ' * 2, ' ')
385 # for all other tags we failed to clean up, just remove then and
386 # complain about them on the stderr
387 def desperate_fixer(g):
388 #print >>sys.stderr, "failed to clean up %s" % str(g.group())
391 html = re.sub('<.*?>', desperate_fixer, html)
394 html = '\n'.join([x.lstrip() for x in html.splitlines()])
396 for i, url in enumerate(url_index):
399 html += '[%s] %s\n' % (i+1, url)
402 def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
403 attach=None, openobject_id=False, ssl=False, debug=False, subtype='plain', x_headers=None, priority='3'):
409 `email_from`: A string used to fill the `From` header, if falsy,
410 config['email_from'] is used instead. Also used for
411 the `Reply-To` header if `reply_to` is not provided
413 `email_to`: a sequence of addresses to send the mail to.
416 from email.MIMEText import MIMEText
417 from email.MIMEBase import MIMEBase
418 from email.MIMEMultipart import MIMEMultipart
419 from email.Header import Header
420 from email.Utils import formatdate, COMMASPACE
421 from email.Utils import formatdate, COMMASPACE
422 from email import Encoders
425 if x_headers is None:
428 if not ssl: ssl = config.get('smtp_ssl', False)
430 if not (email_from or config['email_from']):
431 raise ValueError("Sending an email requires either providing a sender "
432 "address or having configured one")
434 if not email_from: email_from = config.get('email_from', False)
436 if not email_cc: email_cc = []
437 if not email_bcc: email_bcc = []
438 if not body: body = u''
439 try: email_body = body.encode('utf-8')
440 except (UnicodeEncodeError, UnicodeDecodeError):
444 email_text = MIMEText(email_body.encode('utf8') or '',_subtype=subtype,_charset='utf-8')
446 email_text = MIMEText(email_body or '',_subtype=subtype,_charset='utf-8')
448 if attach: msg = MIMEMultipart()
449 else: msg = email_text
451 msg['Subject'] = Header(ustr(subject), 'utf-8')
452 msg['From'] = email_from
455 msg['Reply-To'] = reply_to
457 msg['Reply-To'] = msg['From']
458 msg['To'] = COMMASPACE.join(email_to)
460 msg['Cc'] = COMMASPACE.join(email_cc)
462 msg['Bcc'] = COMMASPACE.join(email_bcc)
463 msg['Date'] = formatdate(localtime=True)
465 # Add OpenERP Server information
466 msg['X-Generated-By'] = 'OpenERP (http://www.openerp.com)'
467 msg['X-OpenERP-Server-Host'] = socket.gethostname()
468 msg['X-OpenERP-Server-Version'] = release.version
469 msg['X-Priority'] = priorities.get(priority, '3 (Normal)')
471 # Add dynamic X Header
472 for key, value in x_headers.iteritems():
473 msg['X-OpenERP-%s' % key] = str(value)
474 msg['%s' % key] = str(value)
477 msg['Message-Id'] = "<%s-openobject-%s@%s>" % (time.time(), openobject_id, socket.gethostname())
480 msg.attach(email_text)
481 for (fname,fcontent) in attach:
482 part = MIMEBase('application', "octet-stream")
483 part.set_payload( fcontent )
484 Encoders.encode_base64(part)
485 part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
488 class WriteToLogger(object):
490 self.logger = netsvc.Logger()
493 self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s)
495 smtp_server = config['smtp_server']
496 if smtp_server.startswith('maildir:/'):
497 from mailbox import Maildir
498 maildir_path = smtp_server[8:]
500 mdir = Maildir(maildir_path,factory=None, create = True)
501 mdir.add(msg.as_string(True))
504 netsvc.Logger().notifyChannel('email_send (maildir)', netsvc.LOG_ERROR, e)
508 oldstderr = smtplib.stderr
511 # in case of debug, the messages are printed to stderr.
513 smtplib.stderr = WriteToLogger()
515 s.set_debuglevel(int(bool(debug))) # 0 or 1
516 s.connect(smtp_server, config['smtp_port'])
522 if config['smtp_user'] or config['smtp_password']:
523 s.login(config['smtp_user'], config['smtp_password'])
524 s.sendmail(email_from,
525 flatten([email_to, email_cc, email_bcc]),
531 smtplib.stderr = oldstderr
534 netsvc.Logger().notifyChannel('email_send', netsvc.LOG_ERROR, e)
539 #----------------------------------------------------------
541 #----------------------------------------------------------
542 # text must be latin-1 encoded
543 def sms_send(user, password, api_id, text, to):
545 url = "http://api.urlsms.com/SendSMS.aspx"
546 #url = "http://196.7.150.220/http/sendmsg"
547 params = urllib.urlencode({'UserID': user, 'Password': password, 'SenderID': api_id, 'MsgText': text, 'RecipientMobileNo':to})
548 f = urllib.urlopen(url+"?"+params)
549 # FIXME: Use the logger if there is an error
552 #---------------------------------------------------------
553 # Class that stores an updateable string (used in wizards)
554 #---------------------------------------------------------
555 class UpdateableStr(local):
557 def __init__(self, string=''):
561 return str(self.string)
564 return str(self.string)
566 def __nonzero__(self):
567 return bool(self.string)
570 class UpdateableDict(local):
571 '''Stores an updateable dict to use in wizards'''
573 def __init__(self, dict=None):
579 return str(self.dict)
582 return str(self.dict)
585 return self.dict.clear()
588 return self.dict.keys()
590 def __setitem__(self, i, y):
591 self.dict.__setitem__(i, y)
593 def __getitem__(self, i):
594 return self.dict.__getitem__(i)
597 return self.dict.copy()
600 return self.dict.iteritems()
603 return self.dict.iterkeys()
605 def itervalues(self):
606 return self.dict.itervalues()
608 def pop(self, k, d=None):
609 return self.dict.pop(k, d)
612 return self.dict.popitem()
614 def setdefault(self, k, d=None):
615 return self.dict.setdefault(k, d)
617 def update(self, E, **F):
618 return self.dict.update(E, F)
621 return self.dict.values()
623 def get(self, k, d=None):
624 return self.dict.get(k, d)
626 def has_key(self, k):
627 return self.dict.has_key(k)
630 return self.dict.items()
632 def __cmp__(self, y):
633 return self.dict.__cmp__(y)
635 def __contains__(self, k):
636 return self.dict.__contains__(k)
638 def __delitem__(self, y):
639 return self.dict.__delitem__(y)
642 return self.dict.__eq__(y)
645 return self.dict.__ge__(y)
648 return self.dict.__gt__(y)
651 return self.dict.__hash__()
654 return self.dict.__iter__()
657 return self.dict.__le__(y)
660 return self.dict.__len__()
663 return self.dict.__lt__(y)
666 return self.dict.__ne__(y)
669 # Don't use ! Use res.currency.round()
670 class currency(float):
672 def __init__(self, value, accuracy=2, rounding=None):
674 rounding=10**-accuracy
675 self.rounding=rounding
676 self.accuracy=accuracy
678 def __new__(cls, value, accuracy=2, rounding=None):
679 return float.__new__(cls, round(value, accuracy))
682 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
683 # return str(display_value)
695 Use it as a decorator of the function you plan to cache
696 Timeout: 0 = no timeout, otherwise in seconds
701 def __init__(self, timeout=None, skiparg=2, multi=None):
702 assert skiparg >= 2 # at least self and cr
704 self.timeout = config['cache_timeout']
706 self.timeout = timeout
707 self.skiparg = skiparg
709 self.lasttime = time.time()
712 cache.__caches.append(self)
715 def _generate_keys(self, dbname, kwargs2):
717 Generate keys depending of the arguments and the self.mutli value
722 pairs.sort(key=lambda (k,v): k)
723 for i, (k, v) in enumerate(pairs):
724 if isinstance(v, dict):
725 pairs[i] = (k, to_tuple(v))
726 if isinstance(v, (list, set)):
727 pairs[i] = (k, tuple(v))
728 elif not is_hashable(v):
729 pairs[i] = (k, repr(v))
733 key = (('dbname', dbname),) + to_tuple(kwargs2)
736 multis = kwargs2[self.multi][:]
738 kwargs2[self.multi] = (id,)
739 key = (('dbname', dbname),) + to_tuple(kwargs2)
742 def _unify_args(self, *args, **kwargs):
743 # Update named arguments with positional argument values (without self and cr)
744 kwargs2 = self.fun_default_values.copy()
745 kwargs2.update(kwargs)
746 kwargs2.update(dict(zip(self.fun_arg_names, args[self.skiparg-2:])))
749 def clear(self, dbname, *args, **kwargs):
750 """clear the cache for database dbname
751 if *args and **kwargs are both empty, clear all the keys related to this database
753 if not args and not kwargs:
754 keys_to_del = [key for key in self.cache.keys() if key[0][1] == dbname]
756 kwargs2 = self._unify_args(*args, **kwargs)
757 keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache.keys()]
759 for key in keys_to_del:
763 def clean_caches_for_db(cls, dbname):
764 for c in cls.__caches:
767 def __call__(self, fn):
768 if self.fun is not None:
769 raise Exception("Can not use a cache instance on more than one function")
772 argspec = inspect.getargspec(fn)
773 self.fun_arg_names = argspec[0][self.skiparg:]
774 self.fun_default_values = {}
776 self.fun_default_values = dict(zip(self.fun_arg_names[-len(argspec[3]):], argspec[3]))
778 def cached_result(self2, cr, *args, **kwargs):
779 if time.time()-int(self.timeout) > self.lasttime:
780 self.lasttime = time.time()
781 t = time.time()-int(self.timeout)
782 old_keys = [key for key in self.cache.keys() if self.cache[key][1] < t]
786 kwargs2 = self._unify_args(*args, **kwargs)
790 for key, id in self._generate_keys(cr.dbname, kwargs2):
791 if key in self.cache:
792 result[id] = self.cache[key][0]
798 kwargs2[self.multi] = notincache.keys()
800 result2 = fn(self2, cr, *args[:self.skiparg-2], **kwargs2)
802 key = notincache[None]
803 self.cache[key] = (result2, time.time())
804 result[None] = result2
808 self.cache[key] = (result2[id], time.time())
809 result.update(result2)
815 cached_result.clear_cache = self.clear
819 return s.replace('&','&').replace('<','<').replace('>','>')
822 """This method is similar to the builtin `str` method, except
823 it will return Unicode string.
825 @param value: the value to convert
828 @return: unicode string
831 if isinstance(value, unicode):
834 if hasattr(value, '__unicode__'):
835 return unicode(value)
837 if not isinstance(value, str):
840 try: # first try utf-8
841 return unicode(value, 'utf-8')
845 try: # then extened iso-8858
846 return unicode(value, 'iso-8859-15')
850 # else use default system locale
851 from locale import getlocale
852 return unicode(value, getlocale()[1])
854 def exception_to_unicode(e):
855 if (sys.version_info[:2] < (2,6)) and hasattr(e, 'message'):
856 return ustr(e.message)
857 if hasattr(e, 'args'):
858 return "\n".join((ustr(a) for a in e.args))
862 return u"Unknown message"
865 # to be compatible with python 2.4
867 if not hasattr(__builtin__, 'all'):
869 for element in iterable:
874 __builtin__.all = all
877 if not hasattr(__builtin__, 'any'):
879 for element in iterable:
884 __builtin__.any = any
887 def get_iso_codes(lang):
888 if lang.find('_') != -1:
889 if lang.split('_')[0] == lang.split('_')[1].lower():
890 lang = lang.split('_')[0]
895 'ab_RU': u'Abkhazian (RU)',
896 'ar_AR': u'Arabic / الْعَرَبيّة',
897 'bg_BG': u'Bulgarian / български',
898 'bs_BS': u'Bosnian / bosanski jezik',
899 'ca_ES': u'Catalan / Català',
900 'cs_CZ': u'Czech / Čeština',
901 'da_DK': u'Danish / Dansk',
902 'de_DE': u'German / Deutsch',
903 'el_GR': u'Greek / Ελληνικά',
904 'en_CA': u'English (CA)',
905 'en_GB': u'English (UK)',
906 'en_US': u'English (US)',
907 'es_AR': u'Spanish (AR) / Español (AR)',
908 'es_ES': u'Spanish / Español',
909 'et_EE': u'Estonian / Eesti keel',
910 'fa_IR': u'Persian / فارس',
911 'fi_FI': u'Finland / Suomi',
912 'fr_BE': u'French (BE) / Français (BE)',
913 'fr_CH': u'French (CH) / Français (CH)',
914 'fr_FR': u'French / Français',
915 'gl_ES': u'Galician / Spain',
916 'gu_IN': u'Gujarati / India',
917 'hi_IN': u'Hindi / India',
918 'hr_HR': u'Croatian / hrvatski jezik',
919 'hu_HU': u'Hungarian / Magyar',
920 'id_ID': u'Indonesian / Bahasa Indonesia',
921 'it_IT': u'Italian / Italiano',
922 'iu_CA': u'Inuktitut / Canada',
923 'ja_JP': u'Japanese / Japan',
924 'ko_KP': u'Korean / Korea, Democratic Peoples Republic of',
925 'ko_KR': u'Korean / Korea, Republic of',
926 'lt_LT': u'Lithuanian / Lietuvių kalba',
927 'lv_LV': u'Latvian / Latvia',
928 'ml_IN': u'Malayalam / India',
929 'mn_MN': u'Mongolian / Mongolia',
930 'nb_NO': u'Norwegian Bokmål / Norway',
931 'nl_NL': u'Dutch / Nederlands',
932 'nl_BE': u'Dutch (Belgium) / Nederlands (Belgïe)',
933 'oc_FR': u'Occitan (post 1500) / France',
934 'pl_PL': u'Polish / Język polski',
935 'pt_BR': u'Portugese (BR) / português (BR)',
936 'pt_PT': u'Portugese / português',
937 'ro_RO': u'Romanian / limba română',
938 'ru_RU': u'Russian / русский язык',
939 'si_LK': u'Sinhalese / Sri Lanka',
940 'sk_SK': u'Slovak / Slovakia',
941 'sl_SL': u'Slovenian / slovenščina',
942 'sq_AL': u'Albanian / Shqipëri',
943 'sr_RS': u'Serbian / Serbia',
944 'sv_SE': u'Swedish / svenska',
945 'te_IN': u'Telugu / India',
946 'tr_TR': u'Turkish / Türkçe',
947 'vi_VN': u'Vietnam / Cộng hòa xã hội chủ nghĩa Việt Nam',
948 'uk_UA': u'Ukrainian / украї́нська мо́ва',
949 'ur_PK': u'Urdu / Pakistan',
950 'zh_CN': u'Chinese (CN) / 简体中文',
951 'zh_HK': u'Chinese (HK)',
952 'zh_TW': u'Chinese (TW) / 正體字',
953 'th_TH': u'Thai / ภาษาไทย',
954 'tlh_TLH': u'Klingon',
958 def scan_languages():
960 # 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'))]
961 # ret = [(lang, lang_dict.get(lang, lang)) for lang in file_list]
962 # Now it will take all languages from get languages function without filter it with base module languages
963 lang_dict = get_languages()
964 ret = [(lang, lang_dict.get(lang, lang)) for lang in list(lang_dict)]
965 ret.sort(key=lambda k:k[1])
969 def get_user_companies(cr, user):
970 def _get_company_children(cr, ids):
973 cr.execute('SELECT id FROM res_company WHERE parent_id = ANY (%s)', (ids,))
974 res=[x[0] for x in cr.fetchall()]
975 res.extend(_get_company_children(cr, res))
977 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,))
978 compids=[cr.fetchone()[0]]
979 compids.extend(_get_company_children(cr, compids))
984 Input number : account or invoice number
985 Output return: the same number completed with the recursive mod10
988 codec=[0,9,4,6,8,2,7,1,3,5]
994 report = codec[ (int(digit) + report) % 10 ]
995 return result + str((10 - report) % 10)
1000 Return the size in a human readable format
1004 units = ('bytes', 'Kb', 'Mb', 'Gb')
1005 if isinstance(sz,basestring):
1008 while s >= 1024 and i < len(units)-1:
1011 return "%0.2f %s" % (s, units[i])
1014 from tools.func import wraps
1017 def wrapper(*args, **kwargs):
1019 from pprint import pformat
1021 vector = ['Call -> function: %r' % f]
1022 for i, arg in enumerate(args):
1023 vector.append(' arg %02d: %s' % (i, pformat(arg)))
1024 for key, value in kwargs.items():
1025 vector.append(' kwarg %10s: %s' % (key, pformat(value)))
1027 timeb4 = time.time()
1028 res = f(*args, **kwargs)
1030 vector.append(' result: %s' % pformat(res))
1031 vector.append(' time delta: %s' % (time.time() - timeb4))
1032 netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector))
1037 class profile(object):
1038 def __init__(self, fname=None):
1041 def __call__(self, f):
1042 from tools.func import wraps
1045 def wrapper(*args, **kwargs):
1046 class profile_wrapper(object):
1050 self.result = f(*args, **kwargs)
1051 pw = profile_wrapper()
1053 fname = self.fname or ("%s.cprof" % (f.func_name,))
1054 cProfile.runctx('pw()', globals(), locals(), filename=fname)
1061 This method allow you to debug your code without print
1063 >>> def func_foo(bar)
1066 ... qnx = (baz, bar)
1071 This will output on the logger:
1073 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42
1074 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42)
1076 To view the DEBUG lines in the logger you must start the server with the option
1081 from inspect import stack
1083 from pprint import pformat
1085 param = re.split("debug *\((.+)\)", st[4][0].strip())[1].strip()
1086 while param.count(')') > param.count('('): param = param[:param.rfind(')')]
1087 what = pformat(what)
1089 what = "%s = %s" % (param, what)
1090 netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what)
1093 icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
1094 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
1095 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
1096 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
1097 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
1098 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
1099 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
1100 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
1101 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
1102 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
1103 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
1104 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
1105 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
1106 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
1107 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
1108 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
1109 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
1110 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
1111 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
1112 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
1113 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
1114 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
1115 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
1116 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
1117 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
1118 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
1121 def extract_zip_file(zip_file, outdirectory):
1125 zf = zipfile.ZipFile(zip_file, 'r')
1127 for path in zf.namelist():
1128 tgt = os.path.join(out, path)
1129 tgtdir = os.path.dirname(tgt)
1130 if not os.path.exists(tgtdir):
1133 if not tgt.endswith(os.sep):
1134 fp = open(tgt, 'wb')
1135 fp.write(zf.read(path))
1139 def detect_ip_addr():
1140 def _detect_ip_addr():
1141 from array import array
1143 from struct import pack, unpack
1152 if not fcntl: # not UNIX:
1153 host = socket.gethostname()
1154 ip_addr = socket.gethostbyname(host)
1156 # get all interfaces:
1158 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1159 names = array('B', '\0' * nbytes)
1160 #print 'names: ', names
1161 outbytes = unpack('iL', fcntl.ioctl( s.fileno(), 0x8912, pack('iL', nbytes, names.buffer_info()[0])))[0]
1162 namestr = names.tostring()
1164 # try 64 bit kernel:
1165 for i in range(0, outbytes, 40):
1166 name = namestr[i:i+16].split('\0', 1)[0]
1168 ip_addr = socket.inet_ntoa(namestr[i+20:i+24])
1171 # try 32 bit kernel:
1173 ifaces = filter(None, [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)])
1175 for ifname in [iface for iface in ifaces if iface != 'lo']:
1176 ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24])
1179 return ip_addr or 'localhost'
1182 ip_addr = _detect_ip_addr()
1184 ip_addr = 'localhost'
1187 # RATIONALE BEHIND TIMESTAMP CALCULATIONS AND TIMEZONE MANAGEMENT:
1188 # The server side never does any timestamp calculation, always
1189 # sends them in a naive (timezone agnostic) format supposed to be
1190 # expressed within the server timezone, and expects the clients to
1191 # provide timestamps in the server timezone as well.
1192 # It stores all timestamps in the database in naive format as well,
1193 # which also expresses the time in the server timezone.
1194 # For this reason the server makes its timezone name available via the
1195 # common/timezone_get() rpc method, which clients need to read
1196 # to know the appropriate time offset to use when reading/writing
1198 def get_win32_timezone():
1199 """Attempt to return the "standard name" of the current timezone on a win32 system.
1200 @return: the standard name of the current win32 timezone, or False if it cannot be found.
1203 if (sys.platform == "win32"):
1206 hklm = _winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
1207 current_tz_key = _winreg.OpenKey(hklm, r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation", 0,_winreg.KEY_ALL_ACCESS)
1208 res = str(_winreg.QueryValueEx(current_tz_key,"StandardName")[0]) # [0] is value, [1] is type code
1209 _winreg.CloseKey(current_tz_key)
1210 _winreg.CloseKey(hklm)
1215 def detect_server_timezone():
1216 """Attempt to detect the timezone to use on the server side.
1217 Defaults to UTC if no working timezone can be found.
1218 @return: the timezone identifier as expected by pytz.timezone.
1225 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1226 "Python pytz module is not available. Timezone will be set to UTC by default.")
1229 # Option 1: the configuration option (did not exist before, so no backwards compatibility issue)
1230 # 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
1231 # Option 3: the environment variable TZ
1232 sources = [ (config['timezone'], 'OpenERP configuration'),
1233 (time.tzname[0], 'time.tzname'),
1234 (os.environ.get('TZ',False),'TZ environment variable'), ]
1235 # Option 4: OS-specific: /etc/timezone on Unix
1236 if (os.path.exists("/etc/timezone")):
1239 f = open("/etc/timezone")
1240 tz_value = f.read(128).strip()
1245 sources.append((tz_value,"/etc/timezone file"))
1246 # Option 5: timezone info from registry on Win32
1247 if (sys.platform == "win32"):
1248 # Timezone info is stored in windows registry.
1249 # However this is not likely to work very well as the standard name
1250 # of timezones in windows is rarely something that is known to pytz.
1251 # But that's ok, it is always possible to use a config option to set
1253 sources.append((get_win32_timezone(),"Windows Registry"))
1255 for (value,source) in sources:
1258 tz = pytz.timezone(value)
1259 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_INFO,
1260 "Using timezone %s obtained from %s." % (tz.zone,source))
1262 except pytz.UnknownTimeZoneError:
1263 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1264 "The timezone specified in %s (%s) is invalid, ignoring it." % (source,value))
1266 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1267 "No valid timezone could be detected, using default UTC timezone. You can specify it explicitly with option 'timezone' in the server configuration.")
1271 def split_every(n, iterable, piece_maker=tuple):
1272 """Splits an iterable into length-n pieces. The last piece will be shorter
1273 if ``n`` does not evenly divide the iterable length.
1274 @param ``piece_maker``: function to build the pieces
1275 from the slices (tuple,list,...)
1277 iterator = iter(iterable)
1278 piece = piece_maker(islice(iterator, n))
1281 piece = piece_maker(islice(iterator, n))
1283 if __name__ == '__main__':
1288 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: