1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 # Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
24 Miscelleanous tools used by OpenERP.
37 from datetime import datetime
38 from email.MIMEText import MIMEText
39 from email.MIMEBase import MIMEBase
40 from email.MIMEMultipart import MIMEMultipart
41 from email.Header import Header
42 from email.Utils import formatdate, COMMASPACE
43 from email import Encoders
44 from itertools import islice, izip
45 from which import which
46 if sys.version_info[:2] < (2, 4):
47 from threadinglocal import local
49 from threading import local
52 from config import config
54 _logger = logging.getLogger('tools')
56 # initialize a database with base/base.sql
59 f = addons.get_module_resource('base', 'base.sql')
60 cr.execute(file_open(f).read())
63 for i in addons.get_modules():
64 mod_path = addons.get_module_path(i)
68 info = addons.load_information_from_description_file(i)
72 categs = info.get('category', 'Uncategorized').split('/')
76 cr.execute('select id \
77 from ir_module_category \
78 where name=%s and parent_id=%s', (categs[0], p_id))
80 cr.execute('select id \
81 from ir_module_category \
82 where name=%s and parent_id is NULL', (categs[0],))
85 cr.execute('select nextval(\'ir_module_category_id_seq\')')
86 c_id = cr.fetchone()[0]
87 cr.execute('insert into ir_module_category \
88 (id, name, parent_id) \
89 values (%s, %s, %s)', (c_id, categs[0], p_id))
95 active = info.get('active', False)
96 installable = info.get('installable', True)
101 state = 'uninstalled'
103 state = 'uninstallable'
104 cr.execute('select nextval(\'ir_module_module_id_seq\')')
105 id = cr.fetchone()[0]
106 cr.execute('insert into ir_module_module \
107 (id, author, website, name, shortdesc, description, \
108 category_id, state, certificate, web) \
109 values (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)', (
110 id, info.get('author', ''),
111 info.get('website', ''), i, info.get('name', False),
112 info.get('description', ''), p_id, state, info.get('certificate') or None,
113 info.get('web') or False))
114 cr.execute('insert into ir_model_data \
115 (name,model,module, res_id, noupdate) values (%s,%s,%s,%s,%s)', (
116 'module_meta_information', 'ir.module.module', i, id, True))
117 dependencies = info.get('depends', [])
118 for d in dependencies:
119 cr.execute('insert into ir_module_module_dependency \
120 (module_id,name) values (%s, %s)', (id, d))
123 def find_in_path(name):
129 def find_pg_tool(name):
131 if config['pg_path'] and config['pg_path'] != 'None':
132 path = config['pg_path']
134 return which(name, path=path)
138 def exec_pg_command(name, *args):
139 prog = find_pg_tool(name)
141 raise Exception('Couldn\'t find %s' % name)
142 args2 = (os.path.basename(prog),) + args
143 return os.spawnv(os.P_WAIT, prog, args2)
145 def exec_pg_command_pipe(name, *args):
146 prog = find_pg_tool(name)
148 raise Exception('Couldn\'t find %s' % name)
150 cmd = '"' + prog + '" ' + ' '.join(args)
152 cmd = prog + ' ' + ' '.join(args)
153 return os.popen2(cmd, 'b')
155 def exec_command_pipe(name, *args):
156 prog = find_in_path(name)
158 raise Exception('Couldn\'t find %s' % name)
160 cmd = '"'+prog+'" '+' '.join(args)
162 cmd = prog+' '+' '.join(args)
163 return os.popen2(cmd, 'b')
165 #----------------------------------------------------------
167 #----------------------------------------------------------
168 #file_path_root = os.getcwd()
169 #file_path_addons = os.path.join(file_path_root, 'addons')
171 def file_open(name, mode="r", subdir='addons', pathinfo=False):
172 """Open a file from the OpenERP root, using a subdir folder.
174 >>> file_open('hr/report/timesheer.xsl')
175 >>> file_open('addons/hr/report/timesheet.xsl')
176 >>> file_open('../../base/report/rml_template.xsl', subdir='addons/hr/report', pathinfo=True)
178 @param name: name of the file
179 @param mode: file open mode
180 @param subdir: subdirectory
181 @param pathinfo: if True returns tupple (fileobject, filepath)
183 @return: fileobject if pathinfo is False else (fileobject, filepath)
186 adps = addons.ad_paths
187 rtp = os.path.normcase(os.path.abspath(config['root_path']))
189 if name.replace(os.path.sep, '/').startswith('addons/'):
193 # First try to locate in addons_path
196 if subdir2.replace(os.path.sep, '/').startswith('addons/'):
197 subdir2 = subdir2[7:]
199 subdir2 = (subdir2 != 'addons' or None) and subdir2
204 fn = os.path.join(adp, subdir2, name)
206 fn = os.path.join(adp, name)
207 fn = os.path.normpath(fn)
208 fo = file_open(fn, mode=mode, subdir=None, pathinfo=pathinfo)
216 name = os.path.join(rtp, subdir, name)
218 name = os.path.join(rtp, name)
220 name = os.path.normpath(name)
222 # Check for a zipfile in the path
227 head, tail = os.path.split(head)
231 zipname = os.path.join(tail, zipname)
234 if zipfile.is_zipfile(head+'.zip'):
235 from cStringIO import StringIO
236 zfile = zipfile.ZipFile(head+'.zip')
239 fo.write(zfile.read(os.path.join(
240 os.path.basename(head), zipname).replace(
247 name2 = os.path.normpath(os.path.join(head + '.zip', zipname))
249 for i in (name2, name):
250 if i and os.path.isfile(i):
255 if os.path.splitext(name)[1] == '.rml':
256 raise IOError, 'Report %s doesn\'t exist or deleted : ' %str(name)
257 raise IOError, 'File not found : '+str(name)
260 #----------------------------------------------------------
262 #----------------------------------------------------------
264 """Flatten a list of elements into a uniqu list
265 Author: Christophe Simonis (christophe@tinyerp.com)
274 >>> flatten( [[], [[]]] )
276 >>> flatten( [[['a','b'], 'c'], 'd', ['e', [], 'f']] )
277 ['a', 'b', 'c', 'd', 'e', 'f']
278 >>> t = (1,2,(3,), [4, 5, [6, [7], (8, 9), ([10, 11, (12, 13)]), [14, [], (15,)], []]])
280 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
284 return hasattr(x, "__iter__")
289 map(r.append, flatten(e))
294 def reverse_enumerate(l):
295 """Like enumerate but in the other sens
296 >>> a = ['a', 'b', 'c']
297 >>> it = reverse_enumerate(a)
305 Traceback (most recent call last):
306 File "<stdin>", line 1, in <module>
309 return izip(xrange(len(l)-1, -1, -1), reversed(l))
311 #----------------------------------------------------------
313 #----------------------------------------------------------
314 email_re = re.compile(r"""
315 ([a-zA-Z][\w\.-]*[a-zA-Z0-9] # username part
317 [a-zA-Z0-9][\w\.-]* # domain must start with a letter ... Ged> why do we include a 0-9 then?
322 res_re = re.compile(r"\[([0-9]+)\]", re.UNICODE)
323 command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
324 reference_re = re.compile("<.*-openobject-(\\d+)@(.*)>", re.UNICODE)
334 def html2plaintext(html, body_id=None, encoding='utf-8'):
335 ## (c) Fry-IT, www.fry-it.com, 2007
336 ## <peter@fry-it.com>
337 ## download here: http://www.peterbe.com/plog/html2plaintext
340 """ from an HTML text, convert the HTML to plain text.
341 If @body_id is provided then this is the tag where the
342 body (not necessarily <body>) starts.
347 from lxml.etree import tostring
349 from lxml.html.soupparser import fromstring
352 _logger.debug('tools.misc.html2plaintext: cannot use BeautifulSoup, fallback to lxml.etree.HTMLParser')
353 from lxml.etree import fromstring, HTMLParser
354 kwargs = dict(parser=HTMLParser())
356 tree = fromstring(html, **kwargs)
358 if body_id is not None:
359 source = tree.xpath('//*[@id=%s]'%(body_id,))
361 source = tree.xpath('//body')
367 for link in tree.findall('.//a'):
368 url = link.get('href')
372 link.text = '%s [%s]' % (link.text, i)
373 url_index.append(url)
375 html = ustr(tostring(tree, encoding=encoding))
377 html = html.replace('<strong>','*').replace('</strong>','*')
378 html = html.replace('<b>','*').replace('</b>','*')
379 html = html.replace('<h3>','*').replace('</h3>','*')
380 html = html.replace('<h2>','**').replace('</h2>','**')
381 html = html.replace('<h1>','**').replace('</h1>','**')
382 html = html.replace('<em>','/').replace('</em>','/')
383 html = html.replace('<tr>', '\n')
384 html = html.replace('</p>', '\n')
385 html = re.sub('<br\s*/?>', '\n', html)
386 html = re.sub('<.*?>', ' ', html)
387 html = html.replace(' ' * 2, ' ')
390 html = '\n'.join([x.strip() for x in html.splitlines()])
391 html = html.replace('\n' * 2, '\n')
393 for i, url in enumerate(url_index):
396 html += ustr('[%s] %s\n') % (i+1, url)
400 def generate_tracking_message_id(openobject_id):
401 """Returns a string that can be used in the Message-ID RFC822 header field so we
402 can track the replies related to a given object thanks to the "In-Reply-To" or
403 "References" fields that Mail User Agents will set.
405 return "<%s-openobject-%s@%s>" % (time.time(), openobject_id, socket.gethostname())
407 def _email_send(smtp_from, smtp_to_list, message, openobject_id=None, ssl=False, debug=False):
408 """Low-level method to send directly a Message through the configured smtp server.
409 :param smtp_from: RFC-822 envelope FROM (not displayed to recipient)
410 :param smtp_to_list: RFC-822 envelope RCPT_TOs (not displayed to recipient)
411 :param message: an email.message.Message to send
412 :param debug: True if messages should be output to stderr before being sent,
413 and smtplib.SMTP put into debug mode.
414 :return: True if the mail was delivered successfully to the smtp,
415 else False (+ exception logged)
417 class WriteToLogger(object):
419 self.logger = netsvc.Logger()
422 self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s)
425 message['Message-Id'] = generate_tracking_message_id(openobject_id)
428 smtp_server = config['smtp_server']
430 if smtp_server.startswith('maildir:/'):
431 from mailbox import Maildir
432 maildir_path = smtp_server[8:]
433 mdir = Maildir(maildir_path,factory=None, create = True)
434 mdir.add(message.as_string(True))
437 oldstderr = smtplib.stderr
438 if not ssl: ssl = config.get('smtp_ssl', False)
441 # in case of debug, the messages are printed to stderr.
443 smtplib.stderr = WriteToLogger()
445 s.set_debuglevel(int(bool(debug))) # 0 or 1
446 s.connect(smtp_server, config['smtp_port'])
452 if config['smtp_user'] or config['smtp_password']:
453 s.login(config['smtp_user'], config['smtp_password'])
455 s.sendmail(smtp_from, smtp_to_list, message.as_string())
460 smtplib.stderr = oldstderr
462 # ignored, just a consequence of the previous exception
466 _logger.error('could not deliver email', exc_info=True)
472 def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
473 attach=None, openobject_id=False, ssl=False, debug=False, subtype='plain', x_headers=None, priority='3'):
479 `email_from`: A string used to fill the `From` header, if falsy,
480 config['email_from'] is used instead. Also used for
481 the `Reply-To` header if `reply_to` is not provided
483 `email_to`: a sequence of addresses to send the mail to.
485 if x_headers is None:
489 if not (email_from or config['email_from']):
490 raise ValueError("Sending an email requires either providing a sender "
491 "address or having configured one")
493 if not email_from: email_from = config.get('email_from', False)
494 email_from = ustr(email_from).encode('utf-8')
496 if not email_cc: email_cc = []
497 if not email_bcc: email_bcc = []
498 if not body: body = u''
500 email_body = ustr(body).encode('utf-8')
501 email_text = MIMEText(email_body or '',_subtype=subtype,_charset='utf-8')
503 if attach: msg = MIMEMultipart()
504 else: msg = email_text
506 msg['Subject'] = Header(ustr(subject), 'utf-8')
507 msg['From'] = email_from
510 msg['Reply-To'] = reply_to
512 msg['Reply-To'] = msg['From']
513 msg['To'] = COMMASPACE.join(email_to)
515 msg['Cc'] = COMMASPACE.join(email_cc)
517 msg['Bcc'] = COMMASPACE.join(email_bcc)
518 msg['Date'] = formatdate(localtime=True)
520 msg['X-Priority'] = priorities.get(priority, '3 (Normal)')
522 # Add dynamic X Header
523 for key, value in x_headers.iteritems():
524 msg['%s' % key] = str(value)
527 msg.attach(email_text)
528 for (fname,fcontent) in attach:
529 part = MIMEBase('application', "octet-stream")
530 part.set_payload( fcontent )
531 Encoders.encode_base64(part)
532 part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
535 return _email_send(email_from, flatten([email_to, email_cc, email_bcc]), msg, openobject_id=openobject_id, ssl=ssl, debug=debug)
537 #----------------------------------------------------------
539 #----------------------------------------------------------
540 # text must be latin-1 encoded
541 def sms_send(user, password, api_id, text, to):
543 url = "http://api.urlsms.com/SendSMS.aspx"
544 #url = "http://196.7.150.220/http/sendmsg"
545 params = urllib.urlencode({'UserID': user, 'Password': password, 'SenderID': api_id, 'MsgText': text, 'RecipientMobileNo':to})
546 urllib.urlopen(url+"?"+params)
547 # FIXME: Use the logger if there is an error
550 #---------------------------------------------------------
551 # Class that stores an updateable string (used in wizards)
552 #---------------------------------------------------------
553 class UpdateableStr(local):
555 def __init__(self, string=''):
559 return str(self.string)
562 return str(self.string)
564 def __nonzero__(self):
565 return bool(self.string)
568 class UpdateableDict(local):
569 '''Stores an updateable dict to use in wizards'''
571 def __init__(self, dict=None):
577 return str(self.dict)
580 return str(self.dict)
583 return self.dict.clear()
586 return self.dict.keys()
588 def __setitem__(self, i, y):
589 self.dict.__setitem__(i, y)
591 def __getitem__(self, i):
592 return self.dict.__getitem__(i)
595 return self.dict.copy()
598 return self.dict.iteritems()
601 return self.dict.iterkeys()
603 def itervalues(self):
604 return self.dict.itervalues()
606 def pop(self, k, d=None):
607 return self.dict.pop(k, d)
610 return self.dict.popitem()
612 def setdefault(self, k, d=None):
613 return self.dict.setdefault(k, d)
615 def update(self, E, **F):
616 return self.dict.update(E, F)
619 return self.dict.values()
621 def get(self, k, d=None):
622 return self.dict.get(k, d)
624 def has_key(self, k):
625 return self.dict.has_key(k)
628 return self.dict.items()
630 def __cmp__(self, y):
631 return self.dict.__cmp__(y)
633 def __contains__(self, k):
634 return self.dict.__contains__(k)
636 def __delitem__(self, y):
637 return self.dict.__delitem__(y)
640 return self.dict.__eq__(y)
643 return self.dict.__ge__(y)
646 return self.dict.__gt__(y)
649 return self.dict.__hash__()
652 return self.dict.__iter__()
655 return self.dict.__le__(y)
658 return self.dict.__len__()
661 return self.dict.__lt__(y)
664 return self.dict.__ne__(y)
667 # Don't use ! Use res.currency.round()
668 class currency(float):
670 def __init__(self, value, accuracy=2, rounding=None):
672 rounding=10**-accuracy
673 self.rounding=rounding
674 self.accuracy=accuracy
676 def __new__(cls, value, accuracy=2, rounding=None):
677 return float.__new__(cls, round(value, accuracy))
680 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
681 # return str(display_value)
693 Use it as a decorator of the function you plan to cache
694 Timeout: 0 = no timeout, otherwise in seconds
699 def __init__(self, timeout=None, skiparg=2, multi=None):
700 assert skiparg >= 2 # at least self and cr
702 self.timeout = config['cache_timeout']
704 self.timeout = timeout
705 self.skiparg = skiparg
707 self.lasttime = time.time()
710 cache.__caches.append(self)
713 def _generate_keys(self, dbname, kwargs2):
715 Generate keys depending of the arguments and the self.mutli value
720 pairs.sort(key=lambda (k,v): k)
721 for i, (k, v) in enumerate(pairs):
722 if isinstance(v, dict):
723 pairs[i] = (k, to_tuple(v))
724 if isinstance(v, (list, set)):
725 pairs[i] = (k, tuple(v))
726 elif not is_hashable(v):
727 pairs[i] = (k, repr(v))
731 key = (('dbname', dbname),) + to_tuple(kwargs2)
734 multis = kwargs2[self.multi][:]
736 kwargs2[self.multi] = (id,)
737 key = (('dbname', dbname),) + to_tuple(kwargs2)
740 def _unify_args(self, *args, **kwargs):
741 # Update named arguments with positional argument values (without self and cr)
742 kwargs2 = self.fun_default_values.copy()
743 kwargs2.update(kwargs)
744 kwargs2.update(dict(zip(self.fun_arg_names, args[self.skiparg-2:])))
747 def clear(self, dbname, *args, **kwargs):
748 """clear the cache for database dbname
749 if *args and **kwargs are both empty, clear all the keys related to this database
751 if not args and not kwargs:
752 keys_to_del = [key for key in self.cache.keys() if key[0][1] == dbname]
754 kwargs2 = self._unify_args(*args, **kwargs)
755 keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache.keys()]
757 for key in keys_to_del:
761 def clean_caches_for_db(cls, dbname):
762 for c in cls.__caches:
765 def __call__(self, fn):
766 if self.fun is not None:
767 raise Exception("Can not use a cache instance on more than one function")
770 argspec = inspect.getargspec(fn)
771 self.fun_arg_names = argspec[0][self.skiparg:]
772 self.fun_default_values = {}
774 self.fun_default_values = dict(zip(self.fun_arg_names[-len(argspec[3]):], argspec[3]))
776 def cached_result(self2, cr, *args, **kwargs):
777 if time.time()-int(self.timeout) > self.lasttime:
778 self.lasttime = time.time()
779 t = time.time()-int(self.timeout)
780 old_keys = [key for key in self.cache.keys() if self.cache[key][1] < t]
784 kwargs2 = self._unify_args(*args, **kwargs)
788 for key, id in self._generate_keys(cr.dbname, kwargs2):
789 if key in self.cache:
790 result[id] = self.cache[key][0]
796 kwargs2[self.multi] = notincache.keys()
798 result2 = fn(self2, cr, *args[:self.skiparg-2], **kwargs2)
800 key = notincache[None]
801 self.cache[key] = (result2, time.time())
802 result[None] = result2
806 self.cache[key] = (result2[id], time.time())
807 result.update(result2)
813 cached_result.clear_cache = self.clear
817 return s.replace('&','&').replace('<','<').replace('>','>')
819 def get_encodings(hint_encoding='utf-8'):
822 'iso-8859-1': 'iso8859-15',
827 if hint_encoding.lower() in fallbacks:
828 yield fallbacks[hint_encoding.lower()]
830 # some defaults (also taking care of pure ASCII)
831 for charset in ['utf8','latin1']:
832 if not (hint_encoding) or (charset.lower() != hint_encoding.lower()):
835 from locale import getpreferredencoding
836 prefenc = getpreferredencoding()
837 if prefenc and prefenc.lower() != 'utf-8':
839 prefenc = fallbacks.get(prefenc.lower())
844 def ustr(value, hint_encoding='utf-8'):
845 """This method is similar to the builtin `str` method, except
846 it will return unicode() string.
848 @param value: the value to convert
849 @param hint_encoding: an optional encoding that was detected
850 upstream and should be tried first to
854 @return: unicode string
856 if isinstance(value, Exception):
857 return exception_to_unicode(value)
859 if isinstance(value, unicode):
862 if not isinstance(value, basestring):
864 return unicode(value)
866 raise UnicodeError('unable to convert %r' % (value,))
868 for ln in get_encodings(hint_encoding):
870 return unicode(value, ln)
873 raise UnicodeError('unable to convert %r' % (value,))
876 def exception_to_unicode(e):
877 if (sys.version_info[:2] < (2,6)) and hasattr(e, 'message'):
878 return ustr(e.message)
879 if hasattr(e, 'args'):
880 return "\n".join((ustr(a) for a in e.args))
884 return u"Unknown message"
887 # to be compatible with python 2.4
889 if not hasattr(__builtin__, 'all'):
891 for element in iterable:
896 __builtin__.all = all
899 if not hasattr(__builtin__, 'any'):
901 for element in iterable:
906 __builtin__.any = any
909 def get_iso_codes(lang):
910 if lang.find('_') != -1:
911 if lang.split('_')[0] == lang.split('_')[1].lower():
912 lang = lang.split('_')[0]
917 'ab_RU': u'Abkhazian / аҧсуа',
918 'ar_AR': u'Arabic / الْعَرَبيّة',
919 'bg_BG': u'Bulgarian / български език',
920 'bs_BS': u'Bosnian / bosanski jezik',
921 'ca_ES': u'Catalan / Català',
922 'cs_CZ': u'Czech / Čeština',
923 'da_DK': u'Danish / Dansk',
924 'de_DE': u'German / Deutsch',
925 'el_GR': u'Greek / Ελληνικά',
926 'en_CA': u'English (CA)',
927 'en_GB': u'English (UK)',
928 'en_US': u'English (US)',
929 'es_AR': u'Spanish (AR) / Español (AR)',
930 'es_BO': u'Spanish (BO) / Español (BO)',
931 'es_CL': u'Spanish (CL) / Español (CL)',
932 'es_CO': u'Spanish (CO) / Español (CO)',
933 'es_CR': u'Spanish (CR) / Español (CR)',
934 'es_DO': u'Spanish (DO) / Español (DO)',
935 'es_EC': u'Spanish (EC) / Español (EC)',
936 'es_ES': u'Spanish / Español',
937 'es_GT': u'Spanish (GT) / Español (GT)',
938 'es_HN': u'Spanish (HN) / Español (HN)',
939 'es_MX': u'Spanish (MX) / Español (MX)',
940 'es_NI': u'Spanish (NI) / Español (NI)',
941 'es_PA': u'Spanish (PA) / Español (PA)',
942 'es_PE': u'Spanish (PE) / Español (PE)',
943 'es_PR': u'Spanish (PR) / Español (PR)',
944 'es_PY': u'Spanish (PY) / Español (PY)',
945 'es_SV': u'Spanish (SV) / Español (SV)',
946 'es_UY': u'Spanish (UY) / Español (UY)',
947 'es_VE': u'Spanish (VE) / Español (VE)',
948 'et_EE': u'Estonian / Eesti keel',
949 'fa_IR': u'Persian / فارس',
950 'fi_FI': u'Finnish / Suomi',
951 'fr_BE': u'French (BE) / Français (BE)',
952 'fr_CH': u'French (CH) / Français (CH)',
953 'fr_FR': u'French / Français',
954 'gl_ES': u'Galician / Galego',
955 'gu_IN': u'Gujarati / ગુજરાતી',
956 'hi_IN': u'Hindi / हिंदी',
957 'hr_HR': u'Croatian / hrvatski jezik',
958 'hu_HU': u'Hungarian / Magyar',
959 'id_ID': u'Indonesian / Bahasa Indonesia',
960 'it_IT': u'Italian / Italiano',
961 'iu_CA': u'Inuktitut / ᐃᓄᒃᑎᑐᑦ',
962 'ja_JP': u'Japanese / 日本語',
963 'ko_KP': u'Korean (KP) / 한국어 (KP)',
964 'ko_KR': u'Korean (KR) / 한국어 (KR)',
965 'lt_LT': u'Lithuanian / Lietuvių kalba',
966 'lv_LV': u'Latvian / latviešu valoda',
967 'ml_IN': u'Malayalam / മലയാളം',
968 'mn_MN': u'Mongolian / монгол',
969 'nb_NO': u'Norwegian Bokmål / Norsk bokmål',
970 'nl_NL': u'Dutch / Nederlands',
971 'nl_BE': u'Flemish (BE) / Vlaams (BE)',
972 'oc_FR': u'Occitan (FR, post 1500) / Occitan',
973 'pl_PL': u'Polish / Język polski',
974 'pt_BR': u'Portugese (BR) / Português (BR)',
975 'pt_PT': u'Portugese / Português',
976 'ro_RO': u'Romanian / română',
977 'ru_RU': u'Russian / русский язык',
978 'si_LK': u'Sinhalese / සිංහල',
979 'sl_SI': u'Slovenian / slovenščina',
980 'sk_SK': u'Slovak / Slovenský jazyk',
981 'sq_AL': u'Albanian / Shqip',
982 'sr_RS': u'Serbian / српски језик',
983 'sv_SE': u'Swedish / svenska',
984 'te_IN': u'Telugu / తెలుగు',
985 'tr_TR': u'Turkish / Türkçe',
986 'vi_VN': u'Vietnamese / Tiếng Việt',
987 'uk_UA': u'Ukrainian / українська',
988 'ur_PK': u'Urdu / اردو',
989 'zh_CN': u'Chinese (CN) / 简体中文',
990 'zh_HK': u'Chinese (HK)',
991 'zh_TW': u'Chinese (TW) / 正體字',
992 'th_TH': u'Thai / ภาษาไทย',
993 'tlh_TLH': u'Klingon',
997 def scan_languages():
998 # Now it will take all languages from get languages function without filter it with base module languages
999 lang_dict = get_languages()
1000 ret = [(lang, lang_dict.get(lang, lang)) for lang in list(lang_dict)]
1001 ret.sort(key=lambda k:k[1])
1005 def get_user_companies(cr, user):
1006 def _get_company_children(cr, ids):
1009 cr.execute('SELECT id FROM res_company WHERE parent_id IN %s', (tuple(ids),))
1010 res = [x[0] for x in cr.fetchall()]
1011 res.extend(_get_company_children(cr, res))
1013 cr.execute('SELECT company_id FROM res_users WHERE id=%s', (user,))
1014 user_comp = cr.fetchone()[0]
1017 return [user_comp] + _get_company_children(cr, [user_comp])
1021 Input number : account or invoice number
1022 Output return: the same number completed with the recursive mod10
1025 codec=[0,9,4,6,8,2,7,1,3,5]
1028 for digit in number:
1031 report = codec[ (int(digit) + report) % 10 ]
1032 return result + str((10 - report) % 10)
1037 Return the size in a human readable format
1041 units = ('bytes', 'Kb', 'Mb', 'Gb')
1042 if isinstance(sz,basestring):
1045 while s >= 1024 and i < len(units)-1:
1048 return "%0.2f %s" % (s, units[i])
1051 from tools.func import wraps
1054 def wrapper(*args, **kwargs):
1055 from pprint import pformat
1057 vector = ['Call -> function: %r' % f]
1058 for i, arg in enumerate(args):
1059 vector.append(' arg %02d: %s' % (i, pformat(arg)))
1060 for key, value in kwargs.items():
1061 vector.append(' kwarg %10s: %s' % (key, pformat(value)))
1063 timeb4 = time.time()
1064 res = f(*args, **kwargs)
1066 vector.append(' result: %s' % pformat(res))
1067 vector.append(' time delta: %s' % (time.time() - timeb4))
1068 netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector))
1073 class profile(object):
1074 def __init__(self, fname=None):
1077 def __call__(self, f):
1078 from tools.func import wraps
1081 def wrapper(*args, **kwargs):
1082 class profile_wrapper(object):
1086 self.result = f(*args, **kwargs)
1087 pw = profile_wrapper()
1089 fname = self.fname or ("%s.cprof" % (f.func_name,))
1090 cProfile.runctx('pw()', globals(), locals(), filename=fname)
1097 This method allow you to debug your code without print
1099 >>> def func_foo(bar)
1102 ... qnx = (baz, bar)
1107 This will output on the logger:
1109 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42
1110 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42)
1112 To view the DEBUG lines in the logger you must start the server with the option
1116 from inspect import stack
1117 from pprint import pformat
1119 param = re.split("debug *\((.+)\)", st[4][0].strip())[1].strip()
1120 while param.count(')') > param.count('('): param = param[:param.rfind(')')]
1121 what = pformat(what)
1123 what = "%s = %s" % (param, what)
1124 netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what)
1127 icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
1128 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
1129 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
1130 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
1131 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
1132 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
1133 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
1134 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
1135 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
1136 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
1137 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
1138 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
1139 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
1140 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
1141 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
1142 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
1143 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
1144 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
1145 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
1146 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
1147 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
1148 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
1149 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
1150 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
1151 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
1152 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
1153 'terp-check','terp-go-month','terp-go-year','terp-go-today','terp-document-new','terp-camera_test',
1154 'terp-emblem-important','terp-gtk-media-pause','terp-gtk-stop','terp-gnome-cpu-frequency-applet+',
1155 'terp-dialog-close','terp-gtk-jump-to-rtl','terp-gtk-jump-to-ltr','terp-accessories-archiver',
1156 'terp-stock_align_left_24','terp-stock_effects-object-colorize','terp-go-home','terp-gtk-go-back-rtl',
1157 'terp-gtk-go-back-ltr','terp-personal','terp-personal-','terp-personal+','terp-accessories-archiver-minus',
1158 'terp-accessories-archiver+','terp-stock_symbol-selection','terp-call-start','terp-dolar',
1159 'terp-face-plain','terp-folder-blue','terp-folder-green','terp-folder-orange','terp-folder-yellow',
1160 'terp-gdu-smart-failing','terp-go-week','terp-gtk-select-all','terp-locked','terp-mail-forward',
1161 'terp-mail-message-new','terp-mail-replied','terp-rating-rated','terp-stage','terp-stock_format-scientific',
1162 'terp-dolar_ok!','terp-idea','terp-stock_format-default','terp-mail-','terp-mail_delete'
1165 def extract_zip_file(zip_file, outdirectory):
1166 zf = zipfile.ZipFile(zip_file, 'r')
1168 for path in zf.namelist():
1169 tgt = os.path.join(out, path)
1170 tgtdir = os.path.dirname(tgt)
1171 if not os.path.exists(tgtdir):
1174 if not tgt.endswith(os.sep):
1175 fp = open(tgt, 'wb')
1176 fp.write(zf.read(path))
1180 def detect_ip_addr():
1181 """Try a very crude method to figure out a valid external
1182 IP or hostname for the current machine. Don't rely on this
1183 for binding to an interface, but it could be used as basis
1184 for constructing a remote URL to the server.
1186 def _detect_ip_addr():
1187 from array import array
1188 from struct import pack, unpack
1197 if not fcntl: # not UNIX:
1198 host = socket.gethostname()
1199 ip_addr = socket.gethostbyname(host)
1201 # get all interfaces:
1203 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1204 names = array('B', '\0' * nbytes)
1205 #print 'names: ', names
1206 outbytes = unpack('iL', fcntl.ioctl( s.fileno(), 0x8912, pack('iL', nbytes, names.buffer_info()[0])))[0]
1207 namestr = names.tostring()
1209 # try 64 bit kernel:
1210 for i in range(0, outbytes, 40):
1211 name = namestr[i:i+16].split('\0', 1)[0]
1213 ip_addr = socket.inet_ntoa(namestr[i+20:i+24])
1216 # try 32 bit kernel:
1218 ifaces = filter(None, [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)])
1220 for ifname in [iface for iface in ifaces if iface != 'lo']:
1221 ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24])
1224 return ip_addr or 'localhost'
1227 ip_addr = _detect_ip_addr()
1229 ip_addr = 'localhost'
1232 # RATIONALE BEHIND TIMESTAMP CALCULATIONS AND TIMEZONE MANAGEMENT:
1233 # The server side never does any timestamp calculation, always
1234 # sends them in a naive (timezone agnostic) format supposed to be
1235 # expressed within the server timezone, and expects the clients to
1236 # provide timestamps in the server timezone as well.
1237 # It stores all timestamps in the database in naive format as well,
1238 # which also expresses the time in the server timezone.
1239 # For this reason the server makes its timezone name available via the
1240 # common/timezone_get() rpc method, which clients need to read
1241 # to know the appropriate time offset to use when reading/writing
1243 def get_win32_timezone():
1244 """Attempt to return the "standard name" of the current timezone on a win32 system.
1245 @return: the standard name of the current win32 timezone, or False if it cannot be found.
1248 if (sys.platform == "win32"):
1251 hklm = _winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
1252 current_tz_key = _winreg.OpenKey(hklm, r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation", 0,_winreg.KEY_ALL_ACCESS)
1253 res = str(_winreg.QueryValueEx(current_tz_key,"StandardName")[0]) # [0] is value, [1] is type code
1254 _winreg.CloseKey(current_tz_key)
1255 _winreg.CloseKey(hklm)
1260 def detect_server_timezone():
1261 """Attempt to detect the timezone to use on the server side.
1262 Defaults to UTC if no working timezone can be found.
1263 @return: the timezone identifier as expected by pytz.timezone.
1268 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1269 "Python pytz module is not available. Timezone will be set to UTC by default.")
1272 # Option 1: the configuration option (did not exist before, so no backwards compatibility issue)
1273 # 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
1274 # Option 3: the environment variable TZ
1275 sources = [ (config['timezone'], 'OpenERP configuration'),
1276 (time.tzname[0], 'time.tzname'),
1277 (os.environ.get('TZ',False),'TZ environment variable'), ]
1278 # Option 4: OS-specific: /etc/timezone on Unix
1279 if (os.path.exists("/etc/timezone")):
1282 f = open("/etc/timezone")
1283 tz_value = f.read(128).strip()
1288 sources.append((tz_value,"/etc/timezone file"))
1289 # Option 5: timezone info from registry on Win32
1290 if (sys.platform == "win32"):
1291 # Timezone info is stored in windows registry.
1292 # However this is not likely to work very well as the standard name
1293 # of timezones in windows is rarely something that is known to pytz.
1294 # But that's ok, it is always possible to use a config option to set
1296 sources.append((get_win32_timezone(),"Windows Registry"))
1298 for (value,source) in sources:
1301 tz = pytz.timezone(value)
1302 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_INFO,
1303 "Using timezone %s obtained from %s." % (tz.zone,source))
1305 except pytz.UnknownTimeZoneError:
1306 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1307 "The timezone specified in %s (%s) is invalid, ignoring it." % (source,value))
1309 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1310 "No valid timezone could be detected, using default UTC timezone. You can specify it explicitly with option 'timezone' in the server configuration.")
1313 def get_server_timezone():
1314 # timezone detection is safe in multithread, so lazy init is ok here
1315 if (not config['timezone']):
1316 config['timezone'] = detect_server_timezone()
1317 return config['timezone']
1320 DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d"
1321 DEFAULT_SERVER_TIME_FORMAT = "%H:%M:%S"
1322 DEFAULT_SERVER_DATETIME_FORMAT = "%s %s" % (
1323 DEFAULT_SERVER_DATE_FORMAT,
1324 DEFAULT_SERVER_TIME_FORMAT)
1326 def server_to_local_timestamp(src_tstamp_str, src_format, dst_format, dst_tz_name,
1327 tz_offset=True, ignore_unparsable_time=True):
1329 Convert a source timestamp string into a destination timestamp string, attempting to apply the
1330 correct offset if both the server and local timezone are recognized, or no
1331 offset at all if they aren't or if tz_offset is false (i.e. assuming they are both in the same TZ).
1333 WARNING: This method is here to allow formatting dates correctly for inclusion in strings where
1334 the client would not be able to format/offset it correctly. DO NOT use it for returning
1335 date fields directly, these are supposed to be handled by the client!!
1337 @param src_tstamp_str: the str value containing the timestamp in the server timezone.
1338 @param src_format: the format to use when parsing the server timestamp.
1339 @param dst_format: the format to use when formatting the resulting timestamp for the local/client timezone.
1340 @param dst_tz_name: name of the destination timezone (such as the 'tz' value of the client context)
1341 @param ignore_unparsable_time: if True, return False if src_tstamp_str cannot be parsed
1342 using src_format or formatted using dst_format.
1344 @return: local/client formatted timestamp, expressed in the local/client timezone if possible
1345 and if tz_offset is true, or src_tstamp_str if timezone offset could not be determined.
1347 if not src_tstamp_str:
1350 res = src_tstamp_str
1351 if src_format and dst_format:
1352 # find out server timezone
1353 server_tz = get_server_timezone()
1355 # dt_value needs to be a datetime.datetime object (so no time.struct_time or mx.DateTime.DateTime here!)
1356 dt_value = datetime.strptime(src_tstamp_str, src_format)
1357 if tz_offset and dst_tz_name:
1360 src_tz = pytz.timezone(server_tz)
1361 dst_tz = pytz.timezone(dst_tz_name)
1362 src_dt = src_tz.localize(dt_value, is_dst=True)
1363 dt_value = src_dt.astimezone(dst_tz)
1366 res = dt_value.strftime(dst_format)
1368 # Normal ways to end up here are if strptime or strftime failed
1369 if not ignore_unparsable_time:
1374 def split_every(n, iterable, piece_maker=tuple):
1375 """Splits an iterable into length-n pieces. The last piece will be shorter
1376 if ``n`` does not evenly divide the iterable length.
1377 @param ``piece_maker``: function to build the pieces
1378 from the slices (tuple,list,...)
1380 iterator = iter(iterable)
1381 piece = piece_maker(islice(iterator, n))
1384 piece = piece_maker(islice(iterator, n))
1386 if __name__ == '__main__':
1390 class upload_data_thread(threading.Thread):
1391 def __init__(self, email, data, type):
1392 self.args = [('email',email),('type',type),('data',data)]
1393 super(upload_data_thread,self).__init__()
1397 args = urllib.urlencode(self.args)
1398 fp = urllib.urlopen('http://www.openerp.com/scripts/survey.php', args)
1404 def upload_data(email, data, type='SURVEY'):
1405 a = upload_data_thread(email, data, type)
1410 # port of python 2.6's attrgetter with support for dotted notation
1411 def resolve_attr(obj, attr):
1412 for name in attr.split("."):
1413 obj = getattr(obj, name)
1416 def attrgetter(*items):
1420 return resolve_attr(obj, attr)
1423 return tuple(resolve_attr(obj, attr) for attr in items)
1427 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: