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