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 mod_path = addons.get_module_path(i)
57 info = addons.load_information_from_description_file(i)
61 categs = info.get('category', 'Uncategorized').split('/')
65 cr.execute('select id \
66 from ir_module_category \
67 where name=%s and parent_id=%s', (categs[0], p_id))
69 cr.execute('select id \
70 from ir_module_category \
71 where name=%s and parent_id is NULL', (categs[0],))
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))
84 active = info.get('active', False)
85 installable = info.get('installable', True)
92 state = 'uninstallable'
93 cr.execute('select nextval(\'ir_module_module_id_seq\')')
95 cr.execute('insert into ir_module_module \
96 (id, author, website, name, shortdesc, description, \
97 category_id, state, certificate) \
98 values (%s, %s, %s, %s, %s, %s, %s, %s, %s)', (
99 id, info.get('author', ''),
100 info.get('website', ''), i, info.get('name', False),
101 info.get('description', ''), p_id, state, info.get('certificate')))
102 cr.execute('insert into ir_model_data \
103 (name,model,module, res_id, noupdate) values (%s,%s,%s,%s,%s)', (
104 'module_meta_information', 'ir.module.module', i, id, True))
105 dependencies = info.get('depends', [])
106 for d in dependencies:
107 cr.execute('insert into ir_module_module_dependency \
108 (module_id,name) values (%s, %s)', (id, d))
111 def find_in_path(name):
116 path = [dir for dir in os.environ['PATH'].split(sep)
117 if os.path.isdir(dir)]
119 val = os.path.join(dir, name)
120 if os.path.isfile(val) or os.path.islink(val):
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)
128 return find_in_path(name)
130 def exec_pg_command(name, *args):
131 prog = find_pg_tool(name)
133 raise Exception('Couldn\'t find %s' % name)
134 args2 = (os.path.basename(prog),) + args
135 return os.spawnv(os.P_WAIT, prog, args2)
137 def exec_pg_command_pipe(name, *args):
138 prog = find_pg_tool(name)
140 raise Exception('Couldn\'t find %s' % name)
142 cmd = '"' + prog + '" ' + ' '.join(args)
144 cmd = prog + ' ' + ' '.join(args)
145 return os.popen2(cmd, 'b')
147 def exec_command_pipe(name, *args):
148 prog = find_in_path(name)
150 raise Exception('Couldn\'t find %s' % name)
152 cmd = '"'+prog+'" '+' '.join(args)
154 cmd = prog+' '+' '.join(args)
155 return os.popen2(cmd, 'b')
157 #----------------------------------------------------------
159 #----------------------------------------------------------
160 #file_path_root = os.getcwd()
161 #file_path_addons = os.path.join(file_path_root, 'addons')
163 def file_open(name, mode="r", subdir='addons', pathinfo=False):
164 """Open a file from the OpenERP root, using a subdir folder.
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)
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)
175 @return: fileobject if pathinfo is False else (fileobject, filepath)
178 adps = addons.ad_paths
179 rtp = os.path.normcase(os.path.abspath(config['root_path']))
181 if name.replace(os.path.sep, '/').startswith('addons/'):
185 # First try to locate in addons_path
188 if subdir2.replace(os.path.sep, '/').startswith('addons/'):
189 subdir2 = subdir2[7:]
191 subdir2 = (subdir2 != 'addons' or None) and subdir2
196 fn = os.path.join(adp, subdir2, name)
198 fn = os.path.join(adp, name)
199 fn = os.path.normpath(fn)
200 fo = file_open(fn, mode=mode, subdir=None, pathinfo=pathinfo)
208 name = os.path.join(rtp, subdir, name)
210 name = os.path.join(rtp, name)
212 name = os.path.normpath(name)
214 # Check for a zipfile in the path
219 head, tail = os.path.split(head)
223 zipname = os.path.join(tail, zipname)
226 if zipfile.is_zipfile(head+'.zip'):
227 from cStringIO import StringIO
228 zfile = zipfile.ZipFile(head+'.zip')
231 fo.write(zfile.read(os.path.join(
232 os.path.basename(head), zipname).replace(
239 name2 = os.path.normpath(os.path.join(head + '.zip', zipname))
241 for i in (name2, name):
242 if i and os.path.isfile(i):
247 if os.path.splitext(name)[1] == '.rml':
248 raise IOError, 'Report %s doesn\'t exist or deleted : ' %str(name)
249 raise IOError, 'File not found : '+str(name)
252 #----------------------------------------------------------
254 #----------------------------------------------------------
256 """Flatten a list of elements into a uniqu list
257 Author: Christophe Simonis (christophe@tinyerp.com)
266 >>> flatten( [[], [[]]] )
268 >>> flatten( [[['a','b'], 'c'], 'd', ['e', [], 'f']] )
269 ['a', 'b', 'c', 'd', 'e', 'f']
270 >>> t = (1,2,(3,), [4, 5, [6, [7], (8, 9), ([10, 11, (12, 13)]), [14, [], (15,)], []]])
272 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
276 return hasattr(x, "__iter__")
281 map(r.append, flatten(e))
286 def reverse_enumerate(l):
287 """Like enumerate but in the other sens
288 >>> a = ['a', 'b', 'c']
289 >>> it = reverse_enumerate(a)
297 Traceback (most recent call last):
298 File "<stdin>", line 1, in <module>
301 return izip(xrange(len(l)-1, -1, -1), reversed(l))
303 #----------------------------------------------------------
305 #----------------------------------------------------------
306 email_re = re.compile(r"""
307 ([a-zA-Z][\w\.-]*[a-zA-Z0-9] # username part
309 [a-zA-Z0-9][\w\.-]* # domain must start with a letter ... Ged> why do we include a 0-9 then?
314 res_re = re.compile(r"\[([0-9]+)\]", re.UNICODE)
315 command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
316 reference_re = re.compile("<.*-openobject-(\\d+)@(.*)>", re.UNICODE)
326 def html2plaintext(html, body_id=None, encoding='utf-8'):
327 ## (c) Fry-IT, www.fry-it.com, 2007
328 ## <peter@fry-it.com>
329 ## download here: http://www.peterbe.com/plog/html2plaintext
332 """ from an HTML text, convert the HTML to plain text.
333 If @body_id is provided then this is the tag where the
334 body (not necessarily <body>) starts.
337 from BeautifulSoup import BeautifulSoup, SoupStrainer, Comment
342 if body_id is not None:
343 strainer = SoupStrainer(id=body_id)
345 strainer = SoupStrainer('body')
347 soup = BeautifulSoup(html, parseOnlyThese=strainer, fromEncoding=encoding)
348 for link in soup.findAll('a'):
349 title = link.renderContents()
350 for url in [x[1] for x in link.attrs if x[0]=='href']:
351 urls.append(dict(url=url, tag=str(link), title=title))
353 html = soup.__str__()
358 if d['title'] == d['url'] or 'http://'+d['title'] == d['url']:
359 html = html.replace(d['tag'], d['url'])
362 html = html.replace(d['tag'], '%s [%s]' % (d['title'], i))
363 url_index.append(d['url'])
365 html = html.replace('<strong>','*').replace('</strong>','*')
366 html = html.replace('<b>','*').replace('</b>','*')
367 html = html.replace('<h3>','*').replace('</h3>','*')
368 html = html.replace('<h2>','**').replace('</h2>','**')
369 html = html.replace('<h1>','**').replace('</h1>','**')
370 html = html.replace('<em>','/').replace('</em>','/')
373 # the only line breaks we respect is those of ending tags and
376 html = html.replace('\n',' ')
377 html = html.replace('<br>', '\n')
378 html = html.replace('<tr>', '\n')
379 html = html.replace('</p>', '\n\n')
380 html = re.sub('<br\s*/>', '\n', html)
381 html = html.replace(' ' * 2, ' ')
384 # for all other tags we failed to clean up, just remove then and
385 # complain about them on the stderr
386 def desperate_fixer(g):
387 #print >>sys.stderr, "failed to clean up %s" % str(g.group())
390 html = re.sub('<.*?>', desperate_fixer, html)
393 html = '\n'.join([x.lstrip() for x in html.splitlines()])
395 for i, url in enumerate(url_index):
398 html += '[%s] %s\n' % (i+1, url)
401 def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
402 attach=None, openobject_id=False, ssl=False, debug=False, subtype='plain', x_headers=None, priority='3'):
408 `email_from`: A string used to fill the `From` header, if falsy,
409 config['email_from'] is used instead. Also used for
410 the `Reply-To` header if `reply_to` is not provided
412 `email_to`: a sequence of addresses to send the mail to.
415 from email.MIMEText import MIMEText
416 from email.MIMEBase import MIMEBase
417 from email.MIMEMultipart import MIMEMultipart
418 from email.Header import Header
419 from email.Utils import formatdate, COMMASPACE
420 from email.Utils import formatdate, COMMASPACE
421 from email import Encoders
424 if x_headers is None:
427 if not ssl: ssl = config.get('smtp_ssl', False)
429 if not (email_from or config['email_from']):
430 raise ValueError("Sending an email requires either providing a sender "
431 "address or having configured one")
433 if not email_from: email_from = config.get('email_from', False)
435 if not email_cc: email_cc = []
436 if not email_bcc: email_bcc = []
437 if not body: body = u''
438 try: email_body = body.encode('utf-8')
439 except (UnicodeEncodeError, UnicodeDecodeError):
443 email_text = MIMEText(email_body.encode('utf8') or '',_subtype=subtype,_charset='utf-8')
445 email_text = MIMEText(email_body or '',_subtype=subtype,_charset='utf-8')
447 if attach: msg = MIMEMultipart()
448 else: msg = email_text
450 msg['Subject'] = Header(ustr(subject), 'utf-8')
451 msg['From'] = email_from
454 msg['Reply-To'] = reply_to
456 msg['Reply-To'] = msg['From']
457 msg['To'] = COMMASPACE.join(email_to)
459 msg['Cc'] = COMMASPACE.join(email_cc)
461 msg['Bcc'] = COMMASPACE.join(email_bcc)
462 msg['Date'] = formatdate(localtime=True)
464 # Add OpenERP Server information
465 msg['X-Generated-By'] = 'OpenERP (http://www.openerp.com)'
466 msg['X-OpenERP-Server-Host'] = socket.gethostname()
467 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)
476 msg['Message-Id'] = "<%s-openobject-%s@%s>" % (time.time(), openobject_id, socket.gethostname())
479 msg.attach(email_text)
480 for (fname,fcontent) in attach:
481 part = MIMEBase('application', "octet-stream")
482 part.set_payload( fcontent )
483 Encoders.encode_base64(part)
484 part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
487 class WriteToLogger(object):
489 self.logger = netsvc.Logger()
492 self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s)
494 smtp_server = config['smtp_server']
495 if smtp_server.startswith('maildir:/'):
496 from mailbox import Maildir
497 maildir_path = smtp_server[8:]
499 mdir = Maildir(maildir_path,factory=None, create = True)
500 mdir.add(msg.as_string(True))
503 netsvc.Logger().notifyChannel('email_send (maildir)', netsvc.LOG_ERROR, e)
507 oldstderr = smtplib.stderr
510 # in case of debug, the messages are printed to stderr.
512 smtplib.stderr = WriteToLogger()
514 s.set_debuglevel(int(bool(debug))) # 0 or 1
515 s.connect(smtp_server, config['smtp_port'])
521 if config['smtp_user'] or config['smtp_password']:
522 s.login(config['smtp_user'], config['smtp_password'])
523 s.sendmail(email_from,
524 flatten([email_to, email_cc, email_bcc]),
530 smtplib.stderr = oldstderr
533 netsvc.Logger().notifyChannel('email_send', netsvc.LOG_ERROR, e)
538 #----------------------------------------------------------
540 #----------------------------------------------------------
541 # text must be latin-1 encoded
542 def sms_send(user, password, api_id, text, to):
544 url = "http://api.urlsms.com/SendSMS.aspx"
545 #url = "http://196.7.150.220/http/sendmsg"
546 params = urllib.urlencode({'UserID': user, 'Password': password, 'SenderID': api_id, 'MsgText': text, 'RecipientMobileNo':to})
547 f = urllib.urlopen(url+"?"+params)
548 # FIXME: Use the logger if there is an error
551 #---------------------------------------------------------
552 # Class that stores an updateable string (used in wizards)
553 #---------------------------------------------------------
554 class UpdateableStr(local):
556 def __init__(self, string=''):
560 return str(self.string)
563 return str(self.string)
565 def __nonzero__(self):
566 return bool(self.string)
569 class UpdateableDict(local):
570 '''Stores an updateable dict to use in wizards'''
572 def __init__(self, dict=None):
578 return str(self.dict)
581 return str(self.dict)
584 return self.dict.clear()
587 return self.dict.keys()
589 def __setitem__(self, i, y):
590 self.dict.__setitem__(i, y)
592 def __getitem__(self, i):
593 return self.dict.__getitem__(i)
596 return self.dict.copy()
599 return self.dict.iteritems()
602 return self.dict.iterkeys()
604 def itervalues(self):
605 return self.dict.itervalues()
607 def pop(self, k, d=None):
608 return self.dict.pop(k, d)
611 return self.dict.popitem()
613 def setdefault(self, k, d=None):
614 return self.dict.setdefault(k, d)
616 def update(self, E, **F):
617 return self.dict.update(E, F)
620 return self.dict.values()
622 def get(self, k, d=None):
623 return self.dict.get(k, d)
625 def has_key(self, k):
626 return self.dict.has_key(k)
629 return self.dict.items()
631 def __cmp__(self, y):
632 return self.dict.__cmp__(y)
634 def __contains__(self, k):
635 return self.dict.__contains__(k)
637 def __delitem__(self, y):
638 return self.dict.__delitem__(y)
641 return self.dict.__eq__(y)
644 return self.dict.__ge__(y)
647 return self.dict.__gt__(y)
650 return self.dict.__hash__()
653 return self.dict.__iter__()
656 return self.dict.__le__(y)
659 return self.dict.__len__()
662 return self.dict.__lt__(y)
665 return self.dict.__ne__(y)
668 # Don't use ! Use res.currency.round()
669 class currency(float):
671 def __init__(self, value, accuracy=2, rounding=None):
673 rounding=10**-accuracy
674 self.rounding=rounding
675 self.accuracy=accuracy
677 def __new__(cls, value, accuracy=2, rounding=None):
678 return float.__new__(cls, round(value, accuracy))
681 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
682 # return str(display_value)
694 Use it as a decorator of the function you plan to cache
695 Timeout: 0 = no timeout, otherwise in seconds
700 def __init__(self, timeout=None, skiparg=2, multi=None):
701 assert skiparg >= 2 # at least self and cr
703 self.timeout = config['cache_timeout']
705 self.timeout = timeout
706 self.skiparg = skiparg
708 self.lasttime = time.time()
711 cache.__caches.append(self)
714 def _generate_keys(self, dbname, kwargs2):
716 Generate keys depending of the arguments and the self.mutli value
721 pairs.sort(key=lambda (k,v): k)
722 for i, (k, v) in enumerate(pairs):
723 if isinstance(v, dict):
724 pairs[i] = (k, to_tuple(v))
725 if isinstance(v, (list, set)):
726 pairs[i] = (k, tuple(v))
727 elif not is_hashable(v):
728 pairs[i] = (k, repr(v))
732 key = (('dbname', dbname),) + to_tuple(kwargs2)
735 multis = kwargs2[self.multi][:]
737 kwargs2[self.multi] = (id,)
738 key = (('dbname', dbname),) + to_tuple(kwargs2)
741 def _unify_args(self, *args, **kwargs):
742 # Update named arguments with positional argument values (without self and cr)
743 kwargs2 = self.fun_default_values.copy()
744 kwargs2.update(kwargs)
745 kwargs2.update(dict(zip(self.fun_arg_names, args[self.skiparg-2:])))
748 def clear(self, dbname, *args, **kwargs):
749 """clear the cache for database dbname
750 if *args and **kwargs are both empty, clear all the keys related to this database
752 if not args and not kwargs:
753 keys_to_del = [key for key in self.cache.keys() if key[0][1] == dbname]
755 kwargs2 = self._unify_args(*args, **kwargs)
756 keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache.keys()]
758 for key in keys_to_del:
762 def clean_caches_for_db(cls, dbname):
763 for c in cls.__caches:
766 def __call__(self, fn):
767 if self.fun is not None:
768 raise Exception("Can not use a cache instance on more than one function")
771 argspec = inspect.getargspec(fn)
772 self.fun_arg_names = argspec[0][self.skiparg:]
773 self.fun_default_values = {}
775 self.fun_default_values = dict(zip(self.fun_arg_names[-len(argspec[3]):], argspec[3]))
777 def cached_result(self2, cr, *args, **kwargs):
778 if time.time()-int(self.timeout) > self.lasttime:
779 self.lasttime = time.time()
780 t = time.time()-int(self.timeout)
781 old_keys = [key for key in self.cache.keys() if self.cache[key][1] < t]
785 kwargs2 = self._unify_args(*args, **kwargs)
789 for key, id in self._generate_keys(cr.dbname, kwargs2):
790 if key in self.cache:
791 result[id] = self.cache[key][0]
797 kwargs2[self.multi] = notincache.keys()
799 result2 = fn(self2, cr, *args[:self.skiparg-2], **kwargs2)
801 key = notincache[None]
802 self.cache[key] = (result2, time.time())
803 result[None] = result2
807 self.cache[key] = (result2[id], time.time())
808 result.update(result2)
814 cached_result.clear_cache = self.clear
818 return s.replace('&','&').replace('<','<').replace('>','>')
821 """This method is similar to the builtin `str` method, except
822 it will return Unicode string.
824 @param value: the value to convert
827 @return: unicode string
830 if isinstance(value, unicode):
833 if hasattr(value, '__unicode__'):
834 return unicode(value)
836 if not isinstance(value, str):
839 try: # first try utf-8
840 return unicode(value, 'utf-8')
844 try: # then extened iso-8858
845 return unicode(value, 'iso-8859-15')
849 # else use default system locale
850 from locale import getlocale
851 return unicode(value, getlocale()[1])
853 def exception_to_unicode(e):
854 if (sys.version_info[:2] < (2,6)) and hasattr(e, 'message'):
855 return ustr(e.message)
856 if hasattr(e, 'args'):
857 return "\n".join((ustr(a) for a in e.args))
861 return u"Unknown message"
864 # to be compatible with python 2.4
866 if not hasattr(__builtin__, 'all'):
868 for element in iterable:
873 __builtin__.all = all
876 if not hasattr(__builtin__, 'any'):
878 for element in iterable:
883 __builtin__.any = any
886 def get_iso_codes(lang):
887 if lang.find('_') != -1:
888 if lang.split('_')[0] == lang.split('_')[1].lower():
889 lang = lang.split('_')[0]
894 'ab_RU': u'Abkhazian (RU)',
895 'ar_AR': u'Arabic / الْعَرَبيّة',
896 'bg_BG': u'Bulgarian / български',
897 'bs_BS': u'Bosnian / bosanski jezik',
898 'ca_ES': u'Catalan / Català',
899 'cs_CZ': u'Czech / Čeština',
900 'da_DK': u'Danish / Dansk',
901 'de_DE': u'German / Deutsch',
902 'el_GR': u'Greek / Ελληνικά',
903 'en_CA': u'English (CA)',
904 'en_GB': u'English (UK)',
905 'en_US': u'English (US)',
906 'es_AR': u'Spanish (AR) / Español (AR)',
907 'es_ES': u'Spanish / Español',
908 'et_EE': u'Estonian / Eesti keel',
909 'fa_IR': u'Persian / Iran, Islamic Republic of',
910 'fi_FI': u'Finland / Suomi',
911 'fr_BE': u'French (BE) / Français (BE)',
912 'fr_CH': u'French (CH) / Français (CH)',
913 'fr_FR': u'French / Français',
914 'gl_ES': u'Galician / Spain',
915 'gu_IN': u'Gujarati / India',
916 'hi_IN': u'Hindi / India',
917 'hr_HR': u'Croatian / hrvatski jezik',
918 'hu_HU': u'Hungarian / Magyar',
919 'id_ID': u'Indonesian / Bahasa Indonesia',
920 'it_IT': u'Italian / Italiano',
921 'iu_CA': u'Inuktitut / Canada',
922 'ja_JP': u'Japanese / Japan',
923 'ko_KP': u'Korean / Korea, Democratic Peoples Republic of',
924 'ko_KR': u'Korean / Korea, Republic of',
925 'lt_LT': u'Lithuanian / Lietuvių kalba',
926 'lv_LV': u'Latvian / Latvia',
927 'ml_IN': u'Malayalam / India',
928 'mn_MN': u'Mongolian / Mongolia',
929 'nb_NO': u'Norwegian Bokmål / Norway',
930 'nl_NL': u'Dutch / Nederlands',
931 'nl_BE': u'Dutch (Belgium) / Nederlands (Belgïe)',
932 'oc_FR': u'Occitan (post 1500) / France',
933 'pl_PL': u'Polish / Język polski',
934 'pt_BR': u'Portugese (BR) / português (BR)',
935 'pt_PT': u'Portugese / português',
936 'ro_RO': u'Romanian / limba română',
937 'ru_RU': u'Russian / русский язык',
938 'si_LK': u'Sinhalese / Sri Lanka',
939 'sk_SK': u'Slovak / Slovakia',
940 'sl_SL': u'Slovenian / slovenščina',
941 'sq_AL': u'Albanian / Shqipëri',
942 'sr_RS': u'Serbian / Serbia',
943 'sv_SE': u'Swedish / svenska',
944 'te_IN': u'Telugu / India',
945 'tr_TR': u'Turkish / Türkçe',
946 'vi_VN': u'Vietnam / Cộng hòa xã hội chủ nghĩa Việt Nam',
947 'uk_UA': u'Ukrainian / украї́нська мо́ва',
948 'ur_PK': u'Urdu / Pakistan',
949 'zh_CN': u'Chinese (CN) / 简体中文',
950 'zh_HK': u'Chinese (HK)',
951 'zh_TW': u'Chinese (TW) / 正體字',
952 'th_TH': u'Thai / ภาษาไทย',
953 'tlh_TLH': u'Klingon',
957 def scan_languages():
959 # 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'))]
960 # ret = [(lang, lang_dict.get(lang, lang)) for lang in file_list]
961 # Now it will take all languages from get languages function without filter it with base module languages
962 lang_dict = get_languages()
963 ret = [(lang, lang_dict.get(lang, lang)) for lang in list(lang_dict)]
964 ret.sort(key=lambda k:k[1])
968 def get_user_companies(cr, user):
969 def _get_company_children(cr, ids):
972 cr.execute('SELECT id FROM res_company WHERE parent_id = ANY (%s)', (ids,))
973 res=[x[0] for x in cr.fetchall()]
974 res.extend(_get_company_children(cr, res))
976 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,))
977 compids=[cr.fetchone()[0]]
978 compids.extend(_get_company_children(cr, compids))
983 Input number : account or invoice number
984 Output return: the same number completed with the recursive mod10
987 codec=[0,9,4,6,8,2,7,1,3,5]
993 report = codec[ (int(digit) + report) % 10 ]
994 return result + str((10 - report) % 10)
999 Return the size in a human readable format
1003 units = ('bytes', 'Kb', 'Mb', 'Gb')
1004 if isinstance(sz,basestring):
1007 while s >= 1024 and i < len(units)-1:
1010 return "%0.2f %s" % (s, units[i])
1013 from tools.func import wraps
1016 def wrapper(*args, **kwargs):
1018 from pprint import pformat
1020 vector = ['Call -> function: %r' % f]
1021 for i, arg in enumerate(args):
1022 vector.append(' arg %02d: %s' % (i, pformat(arg)))
1023 for key, value in kwargs.items():
1024 vector.append(' kwarg %10s: %s' % (key, pformat(value)))
1026 timeb4 = time.time()
1027 res = f(*args, **kwargs)
1029 vector.append(' result: %s' % pformat(res))
1030 vector.append(' time delta: %s' % (time.time() - timeb4))
1031 netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector))
1036 class profile(object):
1037 def __init__(self, fname=None):
1040 def __call__(self, f):
1041 from tools.func import wraps
1044 def wrapper(*args, **kwargs):
1045 class profile_wrapper(object):
1049 self.result = f(*args, **kwargs)
1050 pw = profile_wrapper()
1052 fname = self.fname or ("%s.cprof" % (f.func_name,))
1053 cProfile.runctx('pw()', globals(), locals(), filename=fname)
1060 This method allow you to debug your code without print
1062 >>> def func_foo(bar)
1065 ... qnx = (baz, bar)
1070 This will output on the logger:
1072 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42
1073 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42)
1075 To view the DEBUG lines in the logger you must start the server with the option
1080 from inspect import stack
1082 from pprint import pformat
1084 param = re.split("debug *\((.+)\)", st[4][0].strip())[1].strip()
1085 while param.count(')') > param.count('('): param = param[:param.rfind(')')]
1086 what = pformat(what)
1088 what = "%s = %s" % (param, what)
1089 netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what)
1092 icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
1093 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
1094 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
1095 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
1096 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
1097 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
1098 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
1099 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
1100 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
1101 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
1102 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
1103 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
1104 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
1105 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
1106 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
1107 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
1108 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
1109 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
1110 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
1111 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
1112 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
1113 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
1114 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
1115 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
1116 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
1117 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
1120 def extract_zip_file(zip_file, outdirectory):
1124 zf = zipfile.ZipFile(zip_file, 'r')
1126 for path in zf.namelist():
1127 tgt = os.path.join(out, path)
1128 tgtdir = os.path.dirname(tgt)
1129 if not os.path.exists(tgtdir):
1132 if not tgt.endswith(os.sep):
1133 fp = open(tgt, 'wb')
1134 fp.write(zf.read(path))
1138 def detect_ip_addr():
1139 def _detect_ip_addr():
1140 from array import array
1142 from struct import pack, unpack
1151 if not fcntl: # not UNIX:
1152 host = socket.gethostname()
1153 ip_addr = socket.gethostbyname(host)
1155 # get all interfaces:
1157 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1158 names = array('B', '\0' * nbytes)
1159 #print 'names: ', names
1160 outbytes = unpack('iL', fcntl.ioctl( s.fileno(), 0x8912, pack('iL', nbytes, names.buffer_info()[0])))[0]
1161 namestr = names.tostring()
1163 # try 64 bit kernel:
1164 for i in range(0, outbytes, 40):
1165 name = namestr[i:i+16].split('\0', 1)[0]
1167 ip_addr = socket.inet_ntoa(namestr[i+20:i+24])
1170 # try 32 bit kernel:
1172 ifaces = filter(None, [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)])
1174 for ifname in [iface for iface in ifaces if iface != 'lo']:
1175 ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24])
1178 return ip_addr or 'localhost'
1181 ip_addr = _detect_ip_addr()
1183 ip_addr = 'localhost'
1186 # RATIONALE BEHIND TIMESTAMP CALCULATIONS AND TIMEZONE MANAGEMENT:
1187 # The server side never does any timestamp calculation, always
1188 # sends them in a naive (timezone agnostic) format supposed to be
1189 # expressed within the server timezone, and expects the clients to
1190 # provide timestamps in the server timezone as well.
1191 # It stores all timestamps in the database in naive format as well,
1192 # which also expresses the time in the server timezone.
1193 # For this reason the server makes its timezone name available via the
1194 # common/timezone_get() rpc method, which clients need to read
1195 # to know the appropriate time offset to use when reading/writing
1197 def get_win32_timezone():
1198 """Attempt to return the "standard name" of the current timezone on a win32 system.
1199 @return: the standard name of the current win32 timezone, or False if it cannot be found.
1202 if (sys.platform == "win32"):
1205 hklm = _winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
1206 current_tz_key = _winreg.OpenKey(hklm, r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation", 0,_winreg.KEY_ALL_ACCESS)
1207 res = str(_winreg.QueryValueEx(current_tz_key,"StandardName")[0]) # [0] is value, [1] is type code
1208 _winreg.CloseKey(current_tz_key)
1209 _winreg.CloseKey(hklm)
1214 def detect_server_timezone():
1215 """Attempt to detect the timezone to use on the server side.
1216 Defaults to UTC if no working timezone can be found.
1217 @return: the timezone identifier as expected by pytz.timezone.
1224 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1225 "Python pytz module is not available. Timezone will be set to UTC by default.")
1228 # Option 1: the configuration option (did not exist before, so no backwards compatibility issue)
1229 # 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
1230 # Option 3: the environment variable TZ
1231 sources = [ (config['timezone'], 'OpenERP configuration'),
1232 (time.tzname[0], 'time.tzname'),
1233 (os.environ.get('TZ',False),'TZ environment variable'), ]
1234 # Option 4: OS-specific: /etc/timezone on Unix
1235 if (os.path.exists("/etc/timezone")):
1238 f = open("/etc/timezone")
1239 tz_value = f.read(128).strip()
1244 sources.append((tz_value,"/etc/timezone file"))
1245 # Option 5: timezone info from registry on Win32
1246 if (sys.platform == "win32"):
1247 # Timezone info is stored in windows registry.
1248 # However this is not likely to work very well as the standard name
1249 # of timezones in windows is rarely something that is known to pytz.
1250 # But that's ok, it is always possible to use a config option to set
1252 sources.append((get_win32_timezone(),"Windows Registry"))
1254 for (value,source) in sources:
1257 tz = pytz.timezone(value)
1258 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_INFO,
1259 "Using timezone %s obtained from %s." % (tz.zone,source))
1261 except pytz.UnknownTimeZoneError:
1262 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1263 "The timezone specified in %s (%s) is invalid, ignoring it." % (source,value))
1265 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1266 "No valid timezone could be detected, using default UTC timezone. You can specify it explicitly with option 'timezone' in the server configuration.")
1270 if __name__ == '__main__':
1275 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: