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.
30 from config import config
37 from itertools import islice
39 from which import which
42 from email.MIMEText import MIMEText
43 from email.MIMEBase import MIMEBase
44 from email.MIMEMultipart import MIMEMultipart
45 from email.Header import Header
46 from email.Utils import formatdate, COMMASPACE
47 from email.Utils import formatdate, COMMASPACE
48 from email import Encoders
52 if sys.version_info[:2] < (2, 4):
53 from threadinglocal import local
55 from threading import local
57 from itertools import izip
59 _logger = logging.getLogger('tools')
61 # initialize a database with base/base.sql
64 f = addons.get_module_resource('base', 'base.sql')
65 for line in file_open(f).read().split(';'):
66 if (len(line)>0) and (not line.isspace()):
70 for i in addons.get_modules():
71 mod_path = addons.get_module_path(i)
75 info = addons.load_information_from_description_file(i)
79 categs = info.get('category', 'Uncategorized').split('/')
83 cr.execute('select id \
84 from ir_module_category \
85 where name=%s and parent_id=%s', (categs[0], p_id))
87 cr.execute('select id \
88 from ir_module_category \
89 where name=%s and parent_id is NULL', (categs[0],))
92 cr.execute('select nextval(\'ir_module_category_id_seq\')')
93 c_id = cr.fetchone()[0]
94 cr.execute('insert into ir_module_category \
95 (id, name, parent_id) \
96 values (%s, %s, %s)', (c_id, categs[0], p_id))
102 active = info.get('active', False)
103 installable = info.get('installable', True)
108 state = 'uninstalled'
110 state = 'uninstallable'
111 cr.execute('select nextval(\'ir_module_module_id_seq\')')
112 id = cr.fetchone()[0]
113 cr.execute('insert into ir_module_module \
114 (id, author, website, name, shortdesc, description, \
115 category_id, state, certificate) \
116 values (%s, %s, %s, %s, %s, %s, %s, %s, %s)', (
117 id, info.get('author', ''),
118 info.get('website', ''), i, info.get('name', False),
119 info.get('description', ''), p_id, state, info.get('certificate') or None))
120 cr.execute('insert into ir_model_data \
121 (name,model,module, res_id, noupdate) values (%s,%s,%s,%s,%s)', (
122 'module_meta_information', 'ir.module.module', i, id, True))
123 dependencies = info.get('depends', [])
124 for d in dependencies:
125 cr.execute('insert into ir_module_module_dependency \
126 (module_id,name) values (%s, %s)', (id, d))
129 def find_in_path(name):
135 def find_pg_tool(name):
137 if config['pg_path'] and config['pg_path'] != 'None':
138 path = config['pg_path']
140 return which(name, path=path)
144 def exec_pg_command(name, *args):
145 prog = find_pg_tool(name)
147 raise Exception('Couldn\'t find %s' % name)
148 args2 = (os.path.basename(prog),) + args
149 return os.spawnv(os.P_WAIT, prog, args2)
151 def exec_pg_command_pipe(name, *args):
152 prog = find_pg_tool(name)
154 raise Exception('Couldn\'t find %s' % name)
156 cmd = '"' + prog + '" ' + ' '.join(args)
158 cmd = prog + ' ' + ' '.join(args)
159 return os.popen2(cmd, 'b')
161 def exec_command_pipe(name, *args):
162 prog = find_in_path(name)
164 raise Exception('Couldn\'t find %s' % name)
166 cmd = '"'+prog+'" '+' '.join(args)
168 cmd = prog+' '+' '.join(args)
169 return os.popen2(cmd, 'b')
171 #----------------------------------------------------------
173 #----------------------------------------------------------
174 #file_path_root = os.getcwd()
175 #file_path_addons = os.path.join(file_path_root, 'addons')
177 def file_open(name, mode="r", subdir='addons', pathinfo=False):
178 """Open a file from the OpenERP root, using a subdir folder.
180 >>> file_open('hr/report/timesheer.xsl')
181 >>> file_open('addons/hr/report/timesheet.xsl')
182 >>> file_open('../../base/report/rml_template.xsl', subdir='addons/hr/report', pathinfo=True)
184 @param name: name of the file
185 @param mode: file open mode
186 @param subdir: subdirectory
187 @param pathinfo: if True returns tupple (fileobject, filepath)
189 @return: fileobject if pathinfo is False else (fileobject, filepath)
192 adps = addons.ad_paths
193 rtp = os.path.normcase(os.path.abspath(config['root_path']))
195 if name.replace(os.path.sep, '/').startswith('addons/'):
199 # First try to locate in addons_path
202 if subdir2.replace(os.path.sep, '/').startswith('addons/'):
203 subdir2 = subdir2[7:]
205 subdir2 = (subdir2 != 'addons' or None) and subdir2
210 fn = os.path.join(adp, subdir2, name)
212 fn = os.path.join(adp, name)
213 fn = os.path.normpath(fn)
214 fo = file_open(fn, mode=mode, subdir=None, pathinfo=pathinfo)
222 name = os.path.join(rtp, subdir, name)
224 name = os.path.join(rtp, name)
226 name = os.path.normpath(name)
228 # Check for a zipfile in the path
233 head, tail = os.path.split(head)
237 zipname = os.path.join(tail, zipname)
240 if zipfile.is_zipfile(head+'.zip'):
241 from cStringIO import StringIO
242 zfile = zipfile.ZipFile(head+'.zip')
245 fo.write(zfile.read(os.path.join(
246 os.path.basename(head), zipname).replace(
253 name2 = os.path.normpath(os.path.join(head + '.zip', zipname))
255 for i in (name2, name):
256 if i and os.path.isfile(i):
261 if os.path.splitext(name)[1] == '.rml':
262 raise IOError, 'Report %s doesn\'t exist or deleted : ' %str(name)
263 raise IOError, 'File not found : '+str(name)
266 #----------------------------------------------------------
268 #----------------------------------------------------------
270 """Flatten a list of elements into a uniqu list
271 Author: Christophe Simonis (christophe@tinyerp.com)
280 >>> flatten( [[], [[]]] )
282 >>> flatten( [[['a','b'], 'c'], 'd', ['e', [], 'f']] )
283 ['a', 'b', 'c', 'd', 'e', 'f']
284 >>> t = (1,2,(3,), [4, 5, [6, [7], (8, 9), ([10, 11, (12, 13)]), [14, [], (15,)], []]])
286 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
290 return hasattr(x, "__iter__")
295 map(r.append, flatten(e))
300 def reverse_enumerate(l):
301 """Like enumerate but in the other sens
302 >>> a = ['a', 'b', 'c']
303 >>> it = reverse_enumerate(a)
311 Traceback (most recent call last):
312 File "<stdin>", line 1, in <module>
315 return izip(xrange(len(l)-1, -1, -1), reversed(l))
317 #----------------------------------------------------------
319 #----------------------------------------------------------
320 email_re = re.compile(r"""
321 ([a-zA-Z][\w\.-]*[a-zA-Z0-9] # username part
323 [a-zA-Z0-9][\w\.-]* # domain must start with a letter ... Ged> why do we include a 0-9 then?
328 res_re = re.compile(r"\[([0-9]+)\]", re.UNICODE)
329 command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
330 reference_re = re.compile("<.*-openobject-(\\d+)@(.*)>", re.UNICODE)
340 def html2plaintext(html, body_id=None, encoding='utf-8'):
341 ## (c) Fry-IT, www.fry-it.com, 2007
342 ## <peter@fry-it.com>
343 ## download here: http://www.peterbe.com/plog/html2plaintext
346 """ from an HTML text, convert the HTML to plain text.
347 If @body_id is provided then this is the tag where the
348 body (not necessarily <body>) starts.
354 from BeautifulSoup import BeautifulSoup, SoupStrainer, Comment
359 if body_id is not None:
360 strainer = SoupStrainer(id=body_id)
362 strainer = SoupStrainer('body')
364 soup = BeautifulSoup(html, parseOnlyThese=strainer, fromEncoding=encoding)
365 for link in soup.findAll('a'):
366 title = link.renderContents()
367 for url in [x[1] for x in link.attrs if x[0]=='href']:
368 urls.append(dict(url=ustr(url), tag=ustr(link), title=ustr(title)))
370 html = ustr(soup.__str__())
375 if d['title'] == d['url'] or 'http://'+d['title'] == d['url']:
376 html = html.replace(d['tag'], d['url'])
379 html = html.replace(d['tag'], '%s [%s]' % (d['title'], i))
380 url_index.append(d['url'])
382 html = html.replace('<strong>','*').replace('</strong>','*')
383 html = html.replace('<b>','*').replace('</b>','*')
384 html = html.replace('<h3>','*').replace('</h3>','*')
385 html = html.replace('<h2>','**').replace('</h2>','**')
386 html = html.replace('<h1>','**').replace('</h1>','**')
387 html = html.replace('<em>','/').replace('</em>','/')
390 html = html.replace('<br>', '\n')
391 html = html.replace('<tr>', '\n')
392 html = html.replace('</p>', '\n\n')
393 html = re.sub('<br\s*/>', '\n', html)
394 html = html.replace(' ' * 2, ' ')
397 # for all other tags we failed to clean up, just remove then and
398 # complain about them on the stderr
399 def desperate_fixer(g):
400 #print >>sys.stderr, "failed to clean up %s" % str(g.group())
403 html = re.sub('<.*?>', desperate_fixer, html)
406 html = '\n'.join([x.lstrip() for x in html.splitlines()])
408 for i, url in enumerate(url_index):
411 html += ustr('[%s] %s\n') % (i+1, url)
415 def _email_send(message, openobject_id=None, debug=False):
416 """Low-level method to send directly a Message through the configured smtp server.
417 :param message: an email.message.Message to send
418 :param debug: True if messages should be output to stderr before being sent,
419 and smtplib.SMTP put into debug mode.
420 :return: True if the mail was delivered successfully to the smtp,
421 else False (+ exception logged)
424 message['Message-Id'] = "<%s-openobject-%s@%s>" % (time.time(), openobject_id, socket.gethostname())
427 oldstderr = smtplib.stderr
430 # in case of debug, the messages are printed to stderr.
432 smtplib.stderr = WriteToLogger()
434 s.set_debuglevel(int(bool(debug))) # 0 or 1
435 s.connect(smtp_server, config['smtp_port'])
441 if config['smtp_user'] or config['smtp_password']:
442 s.login(config['smtp_user'], config['smtp_password'])
443 s.sendmail(email_from,
444 flatten([email_to, email_cc, email_bcc]),
449 smtplib.stderr = oldstderr
452 _logger.error('could not deliver email', exc_info=True)
458 def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
459 attach=None, openobject_id=False, ssl=False, debug=False, subtype='plain', x_headers=None, priority='3'):
465 `email_from`: A string used to fill the `From` header, if falsy,
466 config['email_from'] is used instead. Also used for
467 the `Reply-To` header if `reply_to` is not provided
469 `email_to`: a sequence of addresses to send the mail to.
471 if x_headers is None:
474 if not ssl: ssl = config.get('smtp_ssl', False)
476 if not (email_from or config['email_from']):
477 raise ValueError("Sending an email requires either providing a sender "
478 "address or having configured one")
480 if not email_from: email_from = config.get('email_from', False)
482 if not email_cc: email_cc = []
483 if not email_bcc: email_bcc = []
484 if not body: body = u''
485 try: email_body = body.encode('utf-8')
486 except (UnicodeEncodeError, UnicodeDecodeError):
490 email_text = MIMEText(email_body.encode('utf8') or '',_subtype=subtype,_charset='utf-8')
492 email_text = MIMEText(email_body or '',_subtype=subtype,_charset='utf-8')
494 if attach: msg = MIMEMultipart()
495 else: msg = email_text
497 msg['Subject'] = Header(ustr(subject), 'utf-8')
498 msg['From'] = email_from
501 msg['Reply-To'] = reply_to
503 msg['Reply-To'] = msg['From']
504 msg['To'] = COMMASPACE.join(email_to)
506 msg['Cc'] = COMMASPACE.join(email_cc)
508 msg['Bcc'] = COMMASPACE.join(email_bcc)
509 msg['Date'] = formatdate(localtime=True)
511 msg['X-Priority'] = priorities.get(priority, '3 (Normal)')
513 # Add dynamic X Header
514 for key, value in x_headers.iteritems():
515 msg['%s' % key] = str(value)
518 msg.attach(email_text)
519 for (fname,fcontent) in attach:
520 part = MIMEBase('application', "octet-stream")
521 part.set_payload( fcontent )
522 Encoders.encode_base64(part)
523 part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
526 class WriteToLogger(object):
528 self.logger = netsvc.Logger()
531 self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s)
533 smtp_server = config['smtp_server']
534 if smtp_server.startswith('maildir:/'):
535 from mailbox import Maildir
536 maildir_path = smtp_server[8:]
538 mdir = Maildir(maildir_path,factory=None, create = True)
539 mdir.add(msg.as_string(True))
542 netsvc.Logger().notifyChannel('email_send (maildir)', netsvc.LOG_ERROR, e)
545 return _email_send(msg, openobject_id=openobject_id, debug=debug)
547 #----------------------------------------------------------
549 #----------------------------------------------------------
550 # text must be latin-1 encoded
551 def sms_send(user, password, api_id, text, to):
553 url = "http://api.urlsms.com/SendSMS.aspx"
554 #url = "http://196.7.150.220/http/sendmsg"
555 params = urllib.urlencode({'UserID': user, 'Password': password, 'SenderID': api_id, 'MsgText': text, 'RecipientMobileNo':to})
556 f = urllib.urlopen(url+"?"+params)
557 # FIXME: Use the logger if there is an error
560 #---------------------------------------------------------
561 # Class that stores an updateable string (used in wizards)
562 #---------------------------------------------------------
563 class UpdateableStr(local):
565 def __init__(self, string=''):
569 return str(self.string)
572 return str(self.string)
574 def __nonzero__(self):
575 return bool(self.string)
578 class UpdateableDict(local):
579 '''Stores an updateable dict to use in wizards'''
581 def __init__(self, dict=None):
587 return str(self.dict)
590 return str(self.dict)
593 return self.dict.clear()
596 return self.dict.keys()
598 def __setitem__(self, i, y):
599 self.dict.__setitem__(i, y)
601 def __getitem__(self, i):
602 return self.dict.__getitem__(i)
605 return self.dict.copy()
608 return self.dict.iteritems()
611 return self.dict.iterkeys()
613 def itervalues(self):
614 return self.dict.itervalues()
616 def pop(self, k, d=None):
617 return self.dict.pop(k, d)
620 return self.dict.popitem()
622 def setdefault(self, k, d=None):
623 return self.dict.setdefault(k, d)
625 def update(self, E, **F):
626 return self.dict.update(E, F)
629 return self.dict.values()
631 def get(self, k, d=None):
632 return self.dict.get(k, d)
634 def has_key(self, k):
635 return self.dict.has_key(k)
638 return self.dict.items()
640 def __cmp__(self, y):
641 return self.dict.__cmp__(y)
643 def __contains__(self, k):
644 return self.dict.__contains__(k)
646 def __delitem__(self, y):
647 return self.dict.__delitem__(y)
650 return self.dict.__eq__(y)
653 return self.dict.__ge__(y)
656 return self.dict.__gt__(y)
659 return self.dict.__hash__()
662 return self.dict.__iter__()
665 return self.dict.__le__(y)
668 return self.dict.__len__()
671 return self.dict.__lt__(y)
674 return self.dict.__ne__(y)
677 # Don't use ! Use res.currency.round()
678 class currency(float):
680 def __init__(self, value, accuracy=2, rounding=None):
682 rounding=10**-accuracy
683 self.rounding=rounding
684 self.accuracy=accuracy
686 def __new__(cls, value, accuracy=2, rounding=None):
687 return float.__new__(cls, round(value, accuracy))
690 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
691 # return str(display_value)
703 Use it as a decorator of the function you plan to cache
704 Timeout: 0 = no timeout, otherwise in seconds
709 def __init__(self, timeout=None, skiparg=2, multi=None):
710 assert skiparg >= 2 # at least self and cr
712 self.timeout = config['cache_timeout']
714 self.timeout = timeout
715 self.skiparg = skiparg
717 self.lasttime = time.time()
720 cache.__caches.append(self)
723 def _generate_keys(self, dbname, kwargs2):
725 Generate keys depending of the arguments and the self.mutli value
730 pairs.sort(key=lambda (k,v): k)
731 for i, (k, v) in enumerate(pairs):
732 if isinstance(v, dict):
733 pairs[i] = (k, to_tuple(v))
734 if isinstance(v, (list, set)):
735 pairs[i] = (k, tuple(v))
736 elif not is_hashable(v):
737 pairs[i] = (k, repr(v))
741 key = (('dbname', dbname),) + to_tuple(kwargs2)
744 multis = kwargs2[self.multi][:]
746 kwargs2[self.multi] = (id,)
747 key = (('dbname', dbname),) + to_tuple(kwargs2)
750 def _unify_args(self, *args, **kwargs):
751 # Update named arguments with positional argument values (without self and cr)
752 kwargs2 = self.fun_default_values.copy()
753 kwargs2.update(kwargs)
754 kwargs2.update(dict(zip(self.fun_arg_names, args[self.skiparg-2:])))
757 def clear(self, dbname, *args, **kwargs):
758 """clear the cache for database dbname
759 if *args and **kwargs are both empty, clear all the keys related to this database
761 if not args and not kwargs:
762 keys_to_del = [key for key in self.cache.keys() if key[0][1] == dbname]
764 kwargs2 = self._unify_args(*args, **kwargs)
765 keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache.keys()]
767 for key in keys_to_del:
771 def clean_caches_for_db(cls, dbname):
772 for c in cls.__caches:
775 def __call__(self, fn):
776 if self.fun is not None:
777 raise Exception("Can not use a cache instance on more than one function")
780 argspec = inspect.getargspec(fn)
781 self.fun_arg_names = argspec[0][self.skiparg:]
782 self.fun_default_values = {}
784 self.fun_default_values = dict(zip(self.fun_arg_names[-len(argspec[3]):], argspec[3]))
786 def cached_result(self2, cr, *args, **kwargs):
787 if time.time()-int(self.timeout) > self.lasttime:
788 self.lasttime = time.time()
789 t = time.time()-int(self.timeout)
790 old_keys = [key for key in self.cache.keys() if self.cache[key][1] < t]
794 kwargs2 = self._unify_args(*args, **kwargs)
798 for key, id in self._generate_keys(cr.dbname, kwargs2):
799 if key in self.cache:
800 result[id] = self.cache[key][0]
806 kwargs2[self.multi] = notincache.keys()
808 result2 = fn(self2, cr, *args[:self.skiparg-2], **kwargs2)
810 key = notincache[None]
811 self.cache[key] = (result2, time.time())
812 result[None] = result2
816 self.cache[key] = (result2[id], time.time())
817 result.update(result2)
823 cached_result.clear_cache = self.clear
827 return s.replace('&','&').replace('<','<').replace('>','>')
831 from locale import getpreferredencoding
832 prefenc = getpreferredencoding()
838 'iso-8859-1': 'iso8859-15',
840 }.get(prefenc.lower())
846 """This method is similar to the builtin `str` method, except
847 it will return Unicode string.
849 @param value: the value to convert
852 @return: unicode string
855 if isinstance(value, Exception):
856 return exception_to_unicode(value)
858 if isinstance(value, unicode):
862 return unicode(value)
866 for ln in get_encodings():
868 return unicode(value, ln)
871 raise UnicodeError('unable de to convert %r' % (orig,))
874 def exception_to_unicode(e):
875 if (sys.version_info[:2] < (2,6)) and hasattr(e, 'message'):
876 return ustr(e.message)
877 if hasattr(e, 'args'):
878 return "\n".join((ustr(a) for a in e.args))
882 return u"Unknown message"
885 # to be compatible with python 2.4
887 if not hasattr(__builtin__, 'all'):
889 for element in iterable:
894 __builtin__.all = all
897 if not hasattr(__builtin__, 'any'):
899 for element in iterable:
904 __builtin__.any = any
907 def get_iso_codes(lang):
908 if lang.find('_') != -1:
909 if lang.split('_')[0] == lang.split('_')[1].lower():
910 lang = lang.split('_')[0]
915 'ab_RU': u'Abkhazian (RU)',
916 'ar_AR': u'Arabic / الْعَرَبيّة',
917 'bg_BG': u'Bulgarian / български',
918 'bs_BS': u'Bosnian / bosanski jezik',
919 'ca_ES': u'Catalan / Català',
920 'cs_CZ': u'Czech / Čeština',
921 'da_DK': u'Danish / Dansk',
922 'de_DE': u'German / Deutsch',
923 'el_GR': u'Greek / Ελληνικά',
924 'en_CA': u'English (CA)',
925 'en_GB': u'English (UK)',
926 'en_US': u'English (US)',
927 'es_AR': u'Spanish (AR) / Español (AR)',
928 'es_ES': u'Spanish / Español',
929 'et_EE': u'Estonian / Eesti keel',
930 'fa_IR': u'Persian / فارس',
931 'fi_FI': u'Finland / Suomi',
932 'fr_BE': u'French (BE) / Français (BE)',
933 'fr_CH': u'French (CH) / Français (CH)',
934 'fr_FR': u'French / Français',
935 'gl_ES': u'Galician / Galego',
936 'gu_IN': u'Gujarati / India',
937 'hi_IN': u'Hindi / India',
938 'hr_HR': u'Croatian / hrvatski jezik',
939 'hu_HU': u'Hungarian / Magyar',
940 'id_ID': u'Indonesian / Bahasa Indonesia',
941 'it_IT': u'Italian / Italiano',
942 'iu_CA': u'Inuktitut / Canada',
943 'ja_JP': u'Japanese / Japan',
944 'ko_KP': u'Korean / Korea, Democratic Peoples Republic of',
945 'ko_KR': u'Korean / Korea, Republic of',
946 'lt_LT': u'Lithuanian / Lietuvių kalba',
947 'lv_LV': u'Latvian / Latvia',
948 'ml_IN': u'Malayalam / India',
949 'mn_MN': u'Mongolian / Mongolia',
950 'nb_NO': u'Norwegian Bokmål / Norway',
951 'nl_NL': u'Dutch / Nederlands',
952 'nl_BE': u'Dutch (Belgium) / Nederlands (Belgïe)',
953 'oc_FR': u'Occitan (post 1500) / France',
954 'pl_PL': u'Polish / Język polski',
955 'pt_BR': u'Portugese (BR) / português (BR)',
956 'pt_PT': u'Portugese / português',
957 'ro_RO': u'Romanian / limba română',
958 'ru_RU': u'Russian / русский язык',
959 'si_LK': u'Sinhalese / Sri Lanka',
960 'sl_SI': u'Slovenian / slovenščina',
961 'sk_SK': u'Slovak / Slovenský jazyk',
962 'sq_AL': u'Albanian / Shqipëri',
963 'sr_RS': u'Serbian / Serbia',
964 'sv_SE': u'Swedish / svenska',
965 'te_IN': u'Telugu / India',
966 'tr_TR': u'Turkish / Türkçe',
967 'vi_VN': u'Vietnam / Cộng hòa xã hội chủ nghĩa Việt Nam',
968 'uk_UA': u'Ukrainian / украї́нська мо́ва',
969 'ur_PK': u'Urdu / Pakistan',
970 'zh_CN': u'Chinese (CN) / 简体中文',
971 'zh_HK': u'Chinese (HK)',
972 'zh_TW': u'Chinese (TW) / 正體字',
973 'th_TH': u'Thai / ภาษาไทย',
974 'tlh_TLH': u'Klingon',
978 def scan_languages():
980 # 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'))]
981 # ret = [(lang, lang_dict.get(lang, lang)) for lang in file_list]
982 # Now it will take all languages from get languages function without filter it with base module languages
983 lang_dict = get_languages()
984 ret = [(lang, lang_dict.get(lang, lang)) for lang in list(lang_dict)]
985 ret.sort(key=lambda k:k[1])
989 def get_user_companies(cr, user):
990 def _get_company_children(cr, ids):
993 cr.execute('SELECT id FROM res_company WHERE parent_id IN %s', (tuple(ids),))
994 res = [x[0] for x in cr.fetchall()]
995 res.extend(_get_company_children(cr, res))
997 cr.execute('SELECT company_id FROM res_users WHERE id=%s', (user,))
998 user_comp = cr.fetchone()[0]
1001 return [user_comp] + _get_company_children(cr, [user_comp])
1005 Input number : account or invoice number
1006 Output return: the same number completed with the recursive mod10
1009 codec=[0,9,4,6,8,2,7,1,3,5]
1012 for digit in number:
1015 report = codec[ (int(digit) + report) % 10 ]
1016 return result + str((10 - report) % 10)
1021 Return the size in a human readable format
1025 units = ('bytes', 'Kb', 'Mb', 'Gb')
1026 if isinstance(sz,basestring):
1029 while s >= 1024 and i < len(units)-1:
1032 return "%0.2f %s" % (s, units[i])
1035 from tools.func import wraps
1038 def wrapper(*args, **kwargs):
1040 from pprint import pformat
1042 vector = ['Call -> function: %r' % f]
1043 for i, arg in enumerate(args):
1044 vector.append(' arg %02d: %s' % (i, pformat(arg)))
1045 for key, value in kwargs.items():
1046 vector.append(' kwarg %10s: %s' % (key, pformat(value)))
1048 timeb4 = time.time()
1049 res = f(*args, **kwargs)
1051 vector.append(' result: %s' % pformat(res))
1052 vector.append(' time delta: %s' % (time.time() - timeb4))
1053 netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector))
1058 class profile(object):
1059 def __init__(self, fname=None):
1062 def __call__(self, f):
1063 from tools.func import wraps
1066 def wrapper(*args, **kwargs):
1067 class profile_wrapper(object):
1071 self.result = f(*args, **kwargs)
1072 pw = profile_wrapper()
1074 fname = self.fname or ("%s.cprof" % (f.func_name,))
1075 cProfile.runctx('pw()', globals(), locals(), filename=fname)
1082 This method allow you to debug your code without print
1084 >>> def func_foo(bar)
1087 ... qnx = (baz, bar)
1092 This will output on the logger:
1094 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42
1095 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42)
1097 To view the DEBUG lines in the logger you must start the server with the option
1102 from inspect import stack
1104 from pprint import pformat
1106 param = re.split("debug *\((.+)\)", st[4][0].strip())[1].strip()
1107 while param.count(')') > param.count('('): param = param[:param.rfind(')')]
1108 what = pformat(what)
1110 what = "%s = %s" % (param, what)
1111 netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what)
1114 icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
1115 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
1116 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
1117 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
1118 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
1119 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
1120 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
1121 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
1122 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
1123 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
1124 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
1125 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
1126 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
1127 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
1128 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
1129 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
1130 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
1131 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
1132 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
1133 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
1134 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
1135 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
1136 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
1137 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
1138 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
1139 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
1140 'terp-check','terp-go-month','terp-go-year','terp-go-today','terp-document-new','terp-camera_test',
1141 'terp-emblem-important','terp-gtk-media-pause','terp-gtk-stop','terp-gnome-cpu-frequency-applet+',
1142 'terp-dialog-close','terp-gtk-jump-to-rtl','terp-gtk-jump-to-ltr','terp-accessories-archiver',
1143 'terp-stock_align_left_24','terp-stock_effects-object-colorize','terp-go-home','terp-gtk-go-back-rtl',
1144 'terp-gtk-go-back-ltr','terp-personal','terp-personal-','terp-personal+','terp-accessories-archiver-minus',
1145 'terp-accessories-archiver+','terp-stock_symbol-selection','terp-call-start','terp-dolar',
1146 'terp-face-plain','terp-folder-blue','terp-folder-green','terp-folder-orange','terp-folder-yellow',
1147 'terp-gdu-smart-failing','terp-go-week','terp-gtk-select-all','terp-locked','terp-mail-forward',
1148 'terp-mail-message-new','terp-mail-replied','terp-rating-rated','terp-stage','terp-stock_format-scientific',
1149 'terp-dolar_ok!','terp-idea','terp-stock_format-default','terp-mail-','terp-mail_delete'
1152 def extract_zip_file(zip_file, outdirectory):
1156 zf = zipfile.ZipFile(zip_file, 'r')
1158 for path in zf.namelist():
1159 tgt = os.path.join(out, path)
1160 tgtdir = os.path.dirname(tgt)
1161 if not os.path.exists(tgtdir):
1164 if not tgt.endswith(os.sep):
1165 fp = open(tgt, 'wb')
1166 fp.write(zf.read(path))
1170 def detect_ip_addr():
1171 """Try a very crude method to figure out a valid external
1172 IP or hostname for the current machine. Don't rely on this
1173 for binding to an interface, but it could be used as basis
1174 for constructing a remote URL to the server.
1176 def _detect_ip_addr():
1177 from array import array
1179 from struct import pack, unpack
1188 if not fcntl: # not UNIX:
1189 host = socket.gethostname()
1190 ip_addr = socket.gethostbyname(host)
1192 # get all interfaces:
1194 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1195 names = array('B', '\0' * nbytes)
1196 #print 'names: ', names
1197 outbytes = unpack('iL', fcntl.ioctl( s.fileno(), 0x8912, pack('iL', nbytes, names.buffer_info()[0])))[0]
1198 namestr = names.tostring()
1200 # try 64 bit kernel:
1201 for i in range(0, outbytes, 40):
1202 name = namestr[i:i+16].split('\0', 1)[0]
1204 ip_addr = socket.inet_ntoa(namestr[i+20:i+24])
1207 # try 32 bit kernel:
1209 ifaces = filter(None, [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)])
1211 for ifname in [iface for iface in ifaces if iface != 'lo']:
1212 ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24])
1215 return ip_addr or 'localhost'
1218 ip_addr = _detect_ip_addr()
1220 ip_addr = 'localhost'
1223 # RATIONALE BEHIND TIMESTAMP CALCULATIONS AND TIMEZONE MANAGEMENT:
1224 # The server side never does any timestamp calculation, always
1225 # sends them in a naive (timezone agnostic) format supposed to be
1226 # expressed within the server timezone, and expects the clients to
1227 # provide timestamps in the server timezone as well.
1228 # It stores all timestamps in the database in naive format as well,
1229 # which also expresses the time in the server timezone.
1230 # For this reason the server makes its timezone name available via the
1231 # common/timezone_get() rpc method, which clients need to read
1232 # to know the appropriate time offset to use when reading/writing
1234 def get_win32_timezone():
1235 """Attempt to return the "standard name" of the current timezone on a win32 system.
1236 @return: the standard name of the current win32 timezone, or False if it cannot be found.
1239 if (sys.platform == "win32"):
1242 hklm = _winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
1243 current_tz_key = _winreg.OpenKey(hklm, r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation", 0,_winreg.KEY_ALL_ACCESS)
1244 res = str(_winreg.QueryValueEx(current_tz_key,"StandardName")[0]) # [0] is value, [1] is type code
1245 _winreg.CloseKey(current_tz_key)
1246 _winreg.CloseKey(hklm)
1251 def detect_server_timezone():
1252 """Attempt to detect the timezone to use on the server side.
1253 Defaults to UTC if no working timezone can be found.
1254 @return: the timezone identifier as expected by pytz.timezone.
1261 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1262 "Python pytz module is not available. Timezone will be set to UTC by default.")
1265 # Option 1: the configuration option (did not exist before, so no backwards compatibility issue)
1266 # 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
1267 # Option 3: the environment variable TZ
1268 sources = [ (config['timezone'], 'OpenERP configuration'),
1269 (time.tzname[0], 'time.tzname'),
1270 (os.environ.get('TZ',False),'TZ environment variable'), ]
1271 # Option 4: OS-specific: /etc/timezone on Unix
1272 if (os.path.exists("/etc/timezone")):
1275 f = open("/etc/timezone")
1276 tz_value = f.read(128).strip()
1281 sources.append((tz_value,"/etc/timezone file"))
1282 # Option 5: timezone info from registry on Win32
1283 if (sys.platform == "win32"):
1284 # Timezone info is stored in windows registry.
1285 # However this is not likely to work very well as the standard name
1286 # of timezones in windows is rarely something that is known to pytz.
1287 # But that's ok, it is always possible to use a config option to set
1289 sources.append((get_win32_timezone(),"Windows Registry"))
1291 for (value,source) in sources:
1294 tz = pytz.timezone(value)
1295 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_INFO,
1296 "Using timezone %s obtained from %s." % (tz.zone,source))
1298 except pytz.UnknownTimeZoneError:
1299 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1300 "The timezone specified in %s (%s) is invalid, ignoring it." % (source,value))
1302 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1303 "No valid timezone could be detected, using default UTC timezone. You can specify it explicitly with option 'timezone' in the server configuration.")
1307 def split_every(n, iterable, piece_maker=tuple):
1308 """Splits an iterable into length-n pieces. The last piece will be shorter
1309 if ``n`` does not evenly divide the iterable length.
1310 @param ``piece_maker``: function to build the pieces
1311 from the slices (tuple,list,...)
1313 iterator = iter(iterable)
1314 piece = piece_maker(islice(iterator, n))
1317 piece = piece_maker(islice(iterator, n))
1319 if __name__ == '__main__':
1323 class upload_data_thread(threading.Thread):
1324 def __init__(self, email, data, type):
1325 self.args = [('email',email),('type',type),('data',data)]
1326 super(upload_data_thread,self).__init__()
1330 args = urllib.urlencode(self.args)
1331 fp = urllib.urlopen('http://www.openerp.com/scripts/survey.php', args)
1337 def upload_data(email, data, type='SURVEY'):
1338 a = upload_data_thread(email, data, type)
1341 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: