1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
23 Miscelleanous tools used by OpenERP.
29 from config import config
36 if sys.version_info[:2] < (2, 4):
37 from threadinglocal import local
39 from threading import local
41 from itertools import izip
43 # initialize a database with base/base.sql
46 f = addons.get_module_resource('base', 'base.sql')
47 for line in file_open(f).read().split(';'):
48 if (len(line)>0) and (not line.isspace()):
52 for i in addons.get_modules():
53 terp_file = addons.get_module_resource(i, '__terp__.py')
54 mod_path = addons.get_module_path(i)
58 if os.path.isfile(terp_file) or os.path.isfile(mod_path+'.zip'):
59 info = eval(file_open(terp_file).read())
61 categs = info.get('category', 'Uncategorized').split('/')
65 cr.execute('select id \
66 from ir_module_category \
67 where name=%s and parent_id=%s', (categs[0], p_id))
69 cr.execute('select id \
70 from ir_module_category \
71 where name=%s and parent_id is NULL', (categs[0],))
74 cr.execute('select nextval(\'ir_module_category_id_seq\')')
75 c_id = cr.fetchone()[0]
76 cr.execute('insert into ir_module_category \
77 (id, name, parent_id) \
78 values (%s, %s, %s)', (c_id, categs[0], p_id))
84 active = info.get('active', False)
85 installable = info.get('installable', True)
92 state = 'uninstallable'
93 cr.execute('select nextval(\'ir_module_module_id_seq\')')
95 cr.execute('insert into ir_module_module \
96 (id, author, website, name, shortdesc, description, \
97 category_id, state, certificate) \
98 values (%s, %s, %s, %s, %s, %s, %s, %s, %s)', (
99 id, info.get('author', ''),
100 info.get('website', ''), i, info.get('name', False),
101 info.get('description', ''), p_id, state, info.get('certificate')))
102 cr.execute('insert into ir_model_data \
103 (name,model,module, res_id, noupdate) values (%s,%s,%s,%s,%s)', (
104 'module_meta_information', 'ir.module.module', i, id, True))
105 dependencies = info.get('depends', [])
106 for d in dependencies:
107 cr.execute('insert into ir_module_module_dependency \
108 (module_id,name) values (%s, %s)', (id, d))
111 def find_in_path(name):
116 path = [dir for dir in os.environ['PATH'].split(sep)
117 if os.path.isdir(dir)]
119 val = os.path.join(dir, name)
120 if os.path.isfile(val) or os.path.islink(val):
124 def find_pg_tool(name):
125 if config['pg_path'] and config['pg_path'] != 'None':
126 return os.path.join(config['pg_path'], name)
128 return find_in_path(name)
130 def exec_pg_command(name, *args):
131 prog = find_pg_tool(name)
133 raise Exception('Couldn\'t find %s' % name)
134 args2 = (os.path.basename(prog),) + args
135 return os.spawnv(os.P_WAIT, prog, args2)
137 def exec_pg_command_pipe(name, *args):
138 prog = find_pg_tool(name)
140 raise Exception('Couldn\'t find %s' % name)
142 cmd = '"' + prog + '" ' + ' '.join(args)
144 cmd = prog + ' ' + ' '.join(args)
145 return os.popen2(cmd, 'b')
147 def exec_command_pipe(name, *args):
148 prog = find_in_path(name)
150 raise Exception('Couldn\'t find %s' % name)
152 cmd = '"'+prog+'" '+' '.join(args)
154 cmd = prog+' '+' '.join(args)
155 return os.popen2(cmd, 'b')
157 #----------------------------------------------------------
159 #----------------------------------------------------------
160 #file_path_root = os.getcwd()
161 #file_path_addons = os.path.join(file_path_root, 'addons')
163 def file_open(name, mode="r", subdir='addons', pathinfo=False):
164 """Open a file from the OpenERP root, using a subdir folder.
166 >>> file_open('hr/report/timesheer.xsl')
167 >>> file_open('addons/hr/report/timesheet.xsl')
168 >>> file_open('../../base/report/rml_template.xsl', subdir='addons/hr/report', pathinfo=True)
170 @param name: name of the file
171 @param mode: file open mode
172 @param subdir: subdirectory
173 @param pathinfo: if True returns tupple (fileobject, filepath)
175 @return: fileobject if pathinfo is False else (fileobject, filepath)
178 adps = addons.ad_paths
179 rtp = os.path.normcase(os.path.abspath(config['root_path']))
181 if name.replace(os.path.sep, '/').startswith('addons/'):
185 # First try to locate in addons_path
188 if subdir2.replace(os.path.sep, '/').startswith('addons/'):
189 subdir2 = subdir2[7:]
191 subdir2 = (subdir2 != 'addons' or None) and subdir2
196 fn = os.path.join(adp, subdir2, name)
198 fn = os.path.join(adp, name)
199 fn = os.path.normpath(fn)
200 fo = file_open(fn, mode=mode, subdir=None, pathinfo=pathinfo)
208 name = os.path.join(rtp, subdir, name)
210 name = os.path.join(rtp, name)
212 name = os.path.normpath(name)
214 # Check for a zipfile in the path
219 head, tail = os.path.split(head)
223 zipname = os.path.join(tail, zipname)
226 if zipfile.is_zipfile(head+'.zip'):
227 from cStringIO import StringIO
228 zfile = zipfile.ZipFile(head+'.zip')
231 fo.write(zfile.read(os.path.join(
232 os.path.basename(head), zipname).replace(
239 name2 = os.path.normpath(os.path.join(head + '.zip', zipname))
241 for i in (name2, name):
242 if i and os.path.isfile(i):
247 if os.path.splitext(name)[1] == '.rml':
248 raise IOError, 'Report %s doesn\'t exist or deleted : ' %str(name)
249 raise IOError, 'File not found : '+str(name)
252 #----------------------------------------------------------
254 #----------------------------------------------------------
256 """Flatten a list of elements into a uniqu list
257 Author: Christophe Simonis (christophe@tinyerp.com)
266 >>> flatten( [[], [[]]] )
268 >>> flatten( [[['a','b'], 'c'], 'd', ['e', [], 'f']] )
269 ['a', 'b', 'c', 'd', 'e', 'f']
270 >>> t = (1,2,(3,), [4, 5, [6, [7], (8, 9), ([10, 11, (12, 13)]), [14, [], (15,)], []]])
272 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
276 return hasattr(x, "__iter__")
281 map(r.append, flatten(e))
286 def reverse_enumerate(l):
287 """Like enumerate but in the other sens
288 >>> a = ['a', 'b', 'c']
289 >>> it = reverse_enumerate(a)
297 Traceback (most recent call last):
298 File "<stdin>", line 1, in <module>
301 return izip(xrange(len(l)-1, -1, -1), reversed(l))
303 #----------------------------------------------------------
305 #----------------------------------------------------------
306 email_re = re.compile(r"""
307 ([a-zA-Z][\w\.-]*[a-zA-Z0-9] # username part
309 [a-zA-Z0-9][\w\.-]* # domain must start with a letter ... Ged> why do we include a 0-9 then?
314 res_re = re.compile(r"\[([0-9]+)\]", re.UNICODE)
315 command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
316 reference_re = re.compile("<.*-openobject-(\\d+)@(.*)>", re.UNICODE)
326 def html2plaintext(html, body_id=None, encoding='utf-8'):
327 ## (c) Fry-IT, www.fry-it.com, 2007
328 ## <peter@fry-it.com>
329 ## download here: http://www.peterbe.com/plog/html2plaintext
332 """ from an HTML text, convert the HTML to plain text.
333 If @body_id is provided then this is the tag where the
334 body (not necessarily <body>) starts.
337 from BeautifulSoup import BeautifulSoup, SoupStrainer, Comment
342 if body_id is not None:
343 strainer = SoupStrainer(id=body_id)
345 strainer = SoupStrainer('body')
347 soup = BeautifulSoup(html, parseOnlyThese=strainer, fromEncoding=encoding)
348 for link in soup.findAll('a'):
349 title = link.renderContents()
350 for url in [x[1] for x in link.attrs if x[0]=='href']:
351 urls.append(dict(url=url, tag=str(link), title=title))
353 html = soup.__str__()
358 if d['title'] == d['url'] or 'http://'+d['title'] == d['url']:
359 html = html.replace(d['tag'], d['url'])
362 html = html.replace(d['tag'], '%s [%s]' % (d['title'], i))
363 url_index.append(d['url'])
365 html = html.replace('<strong>','*').replace('</strong>','*')
366 html = html.replace('<b>','*').replace('</b>','*')
367 html = html.replace('<h3>','*').replace('</h3>','*')
368 html = html.replace('<h2>','**').replace('</h2>','**')
369 html = html.replace('<h1>','**').replace('</h1>','**')
370 html = html.replace('<em>','/').replace('</em>','/')
373 # the only line breaks we respect is those of ending tags and
376 html = html.replace('\n',' ')
377 html = html.replace('<br>', '\n')
378 html = html.replace('<tr>', '\n')
379 html = html.replace('</p>', '\n\n')
380 html = re.sub('<br\s*/>', '\n', html)
381 html = html.replace(' ' * 2, ' ')
384 # for all other tags we failed to clean up, just remove then and
385 # complain about them on the stderr
386 def desperate_fixer(g):
387 #print >>sys.stderr, "failed to clean up %s" % str(g.group())
390 html = re.sub('<.*?>', desperate_fixer, html)
393 html = '\n'.join([x.lstrip() for x in html.splitlines()])
395 for i, url in enumerate(url_index):
398 html += '[%s] %s\n' % (i+1, url)
401 def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
402 attach=None, openobject_id=False, ssl=False, debug=False, subtype='plain', x_headers=None, priority='3'):
406 from email.MIMEText import MIMEText
407 from email.MIMEBase import MIMEBase
408 from email.MIMEMultipart import MIMEMultipart
409 from email.Header import Header
410 from email.Utils import formatdate, COMMASPACE
411 from email.Utils import formatdate, COMMASPACE
412 from email import Encoders
415 if x_headers is None:
418 if not ssl: ssl = config.get('smtp_ssl', False)
420 if not (email_from or config['email_from']):
421 raise ValueError("Sending an email requires either providing a sender "
422 "address or having configured one")
424 if not email_from: email_from = config.get('email_from', False)
426 if not email_cc: email_cc = []
427 if not email_bcc: email_bcc = []
428 if not body: body = u''
430 email_text = MIMEText(body.encode('utf-8'),_subtype=subtype,
432 except (UnicodeEncodeError, UnicodeDecodeError):
433 email_text = MIMEText(body,_subtype=subtype,_charset='utf-8')
436 msg = MIMEMultipart()
440 msg['Subject'] = Header(ustr(subject), 'utf-8')
441 msg['From'] = email_from
444 msg['Reply-To'] = reply_to
446 msg['Reply-To'] = msg['From']
447 msg['To'] = COMMASPACE.join(email_to)
449 msg['Cc'] = COMMASPACE.join(email_cc)
451 msg['Bcc'] = COMMASPACE.join(email_bcc)
452 msg['Date'] = formatdate(localtime=True)
454 # Add OpenERP Server information
455 msg['X-Generated-By'] = 'OpenERP (http://www.openerp.com)'
456 msg['X-OpenERP-Server-Host'] = socket.gethostname()
457 msg['X-OpenERP-Server-Version'] = release.version
459 msg['X-Priority'] = priorities.get(priority, '3 (Normal)')
461 # Add dynamic X Header
462 for key, value in x_headers.iteritems():
463 msg['X-OpenERP-%s' % key] = str(value)
466 msg['Message-Id'] = "<%s-openobject-%s@%s>" % (time.time(), openobject_id, socket.gethostname())
469 msg.attach(email_text)
470 for (fname,fcontent) in attach:
471 part = MIMEBase('application', "octet-stream")
472 part.set_payload( fcontent )
473 Encoders.encode_base64(part)
474 part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
477 class WriteToLogger(object):
479 self.logger = netsvc.Logger()
482 self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s)
484 smtp_server = config['smtp_server']
485 if smtp_server.startswith('maildir:/'):
486 from mailbox import Maildir
487 maildir_path = smtp_server[8:]
489 mdir = Maildir(maildir_path,factory=None, create = True)
490 mdir.add(msg.as_string(True))
493 netsvc.Logger().notifyChannel('email_send (maildir)', netsvc.LOG_ERROR, e)
497 oldstderr = smtplib.stderr
500 # in case of debug, the messages are printed to stderr.
502 smtplib.stderr = WriteToLogger()
504 s.set_debuglevel(int(bool(debug))) # 0 or 1
505 s.connect(smtp_server, config['smtp_port'])
511 if config['smtp_user'] or config['smtp_password']:
512 s.login(config['smtp_user'], config['smtp_password'])
513 s.sendmail(email_from,
514 flatten([email_to, email_cc, email_bcc]),
520 smtplib.stderr = oldstderr
523 netsvc.Logger().notifyChannel('email_send', netsvc.LOG_ERROR, e)
528 #----------------------------------------------------------
530 #----------------------------------------------------------
531 # text must be latin-1 encoded
532 def sms_send(user, password, api_id, text, to):
534 url = "http://api.urlsms.com/SendSMS.aspx"
535 #url = "http://196.7.150.220/http/sendmsg"
536 params = urllib.urlencode({'UserID': user, 'Password': password, 'SenderID': api_id, 'MsgText': text, 'RecipientMobileNo':to})
537 f = urllib.urlopen(url+"?"+params)
538 # FIXME: Use the logger if there is an error
541 #---------------------------------------------------------
542 # Class that stores an updateable string (used in wizards)
543 #---------------------------------------------------------
544 class UpdateableStr(local):
546 def __init__(self, string=''):
550 return str(self.string)
553 return str(self.string)
555 def __nonzero__(self):
556 return bool(self.string)
559 class UpdateableDict(local):
560 '''Stores an updateable dict to use in wizards'''
562 def __init__(self, dict=None):
568 return str(self.dict)
571 return str(self.dict)
574 return self.dict.clear()
577 return self.dict.keys()
579 def __setitem__(self, i, y):
580 self.dict.__setitem__(i, y)
582 def __getitem__(self, i):
583 return self.dict.__getitem__(i)
586 return self.dict.copy()
589 return self.dict.iteritems()
592 return self.dict.iterkeys()
594 def itervalues(self):
595 return self.dict.itervalues()
597 def pop(self, k, d=None):
598 return self.dict.pop(k, d)
601 return self.dict.popitem()
603 def setdefault(self, k, d=None):
604 return self.dict.setdefault(k, d)
606 def update(self, E, **F):
607 return self.dict.update(E, F)
610 return self.dict.values()
612 def get(self, k, d=None):
613 return self.dict.get(k, d)
615 def has_key(self, k):
616 return self.dict.has_key(k)
619 return self.dict.items()
621 def __cmp__(self, y):
622 return self.dict.__cmp__(y)
624 def __contains__(self, k):
625 return self.dict.__contains__(k)
627 def __delitem__(self, y):
628 return self.dict.__delitem__(y)
631 return self.dict.__eq__(y)
634 return self.dict.__ge__(y)
637 return self.dict.__gt__(y)
640 return self.dict.__hash__()
643 return self.dict.__iter__()
646 return self.dict.__le__(y)
649 return self.dict.__len__()
652 return self.dict.__lt__(y)
655 return self.dict.__ne__(y)
658 # Don't use ! Use res.currency.round()
659 class currency(float):
661 def __init__(self, value, accuracy=2, rounding=None):
663 rounding=10**-accuracy
664 self.rounding=rounding
665 self.accuracy=accuracy
667 def __new__(cls, value, accuracy=2, rounding=None):
668 return float.__new__(cls, round(value, accuracy))
671 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
672 # return str(display_value)
684 Use it as a decorator of the function you plan to cache
685 Timeout: 0 = no timeout, otherwise in seconds
690 def __init__(self, timeout=None, skiparg=2, multi=None):
691 assert skiparg >= 2 # at least self and cr
693 self.timeout = config['cache_timeout']
695 self.timeout = timeout
696 self.skiparg = skiparg
698 self.lasttime = time.time()
701 cache.__caches.append(self)
704 def _generate_keys(self, dbname, kwargs2):
706 Generate keys depending of the arguments and the self.mutli value
711 pairs.sort(key=lambda (k,v): k)
712 for i, (k, v) in enumerate(pairs):
713 if isinstance(v, dict):
714 pairs[i] = (k, to_tuple(v))
715 if isinstance(v, (list, set)):
716 pairs[i] = (k, tuple(v))
717 elif not is_hashable(v):
718 pairs[i] = (k, repr(v))
722 key = (('dbname', dbname),) + to_tuple(kwargs2)
725 multis = kwargs2[self.multi][:]
727 kwargs2[self.multi] = (id,)
728 key = (('dbname', dbname),) + to_tuple(kwargs2)
731 def _unify_args(self, *args, **kwargs):
732 # Update named arguments with positional argument values (without self and cr)
733 kwargs2 = self.fun_default_values.copy()
734 kwargs2.update(kwargs)
735 kwargs2.update(dict(zip(self.fun_arg_names, args[self.skiparg-2:])))
738 def clear(self, dbname, *args, **kwargs):
739 """clear the cache for database dbname
740 if *args and **kwargs are both empty, clear all the keys related to this database
742 if not args and not kwargs:
743 keys_to_del = [key for key in self.cache if key[0][1] == dbname]
745 kwargs2 = self._unify_args(*args, **kwargs)
746 keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache]
748 for key in keys_to_del:
752 def clean_caches_for_db(cls, dbname):
753 for c in cls.__caches:
756 def __call__(self, fn):
757 if self.fun is not None:
758 raise Exception("Can not use a cache instance on more than one function")
761 argspec = inspect.getargspec(fn)
762 self.fun_arg_names = argspec[0][self.skiparg:]
763 self.fun_default_values = {}
765 self.fun_default_values = dict(zip(self.fun_arg_names[-len(argspec[3]):], argspec[3]))
767 def cached_result(self2, cr, *args, **kwargs):
768 if time.time()-int(self.timeout) > self.lasttime:
769 self.lasttime = time.time()
770 t = time.time()-int(self.timeout)
771 old_keys = [key for key in self.cache if self.cache[key][1] < t]
775 kwargs2 = self._unify_args(*args, **kwargs)
779 for key, id in self._generate_keys(cr.dbname, kwargs2):
780 if key in self.cache:
781 result[id] = self.cache[key][0]
787 kwargs2[self.multi] = notincache.keys()
789 result2 = fn(self2, cr, *args[:self.skiparg-2], **kwargs2)
791 key = notincache[None]
792 self.cache[key] = (result2, time.time())
793 result[None] = result2
797 self.cache[key] = (result2[id], time.time())
798 result.update(result2)
804 cached_result.clear_cache = self.clear
808 return s.replace('&','&').replace('<','<').replace('>','>')
811 """This method is similar to the builtin `str` method, except
812 it will return Unicode string.
814 @param value: the value to convert
817 @return: unicode string
820 if isinstance(value, unicode):
823 if hasattr(value, '__unicode__'):
824 return unicode(value)
826 if not isinstance(value, str):
829 try: # first try utf-8
830 return unicode(value, 'utf-8')
834 try: # then extened iso-8858
835 return unicode(value, 'iso-8859-15')
839 # else use default system locale
840 from locale import getlocale
841 return unicode(value, getlocale()[1])
843 def exception_to_unicode(e):
844 if (sys.version_info[:2] < (2,6)) and hasattr(e, 'message'):
845 return ustr(e.message)
846 if hasattr(e, 'args'):
847 return "\n".join((ustr(a) for a in e.args))
851 return u"Unknown message"
854 # to be compatible with python 2.4
856 if not hasattr(__builtin__, 'all'):
858 for element in iterable:
863 __builtin__.all = all
866 if not hasattr(__builtin__, 'any'):
868 for element in iterable:
873 __builtin__.any = any
876 get_iso = {'ca_ES':'ca',
889 def get_iso_codes(lang):
892 elif lang.find('_') != -1:
893 if lang.split('_')[0] == lang.split('_')[1].lower():
894 lang = lang.split('_')[0]
899 'ar_AR': u'Arabic / الْعَرَبيّة',
900 'bg_BG': u'Bulgarian / български',
901 'bs_BS': u'Bosnian / bosanski jezik',
902 'ca_ES': u'Catalan / Català',
903 'cs_CZ': u'Czech / Čeština',
904 'da_DK': u'Danish / Dansk',
905 'de_DE': u'German / Deutsch',
906 'el_GR': u'Greek / Ελληνικά',
907 'en_CA': u'English (CA)',
908 'en_GB': u'English (UK)',
909 'en_US': u'English (US)',
910 'es_AR': u'Spanish (AR) / Español (AR)',
911 'es_ES': u'Spanish / Español',
912 'et_EE': u'Estonian / Eesti keel',
913 'fi_FI': u'Finland / Suomi',
914 'fr_BE': u'French (BE) / Français (BE)',
915 'fr_CH': u'French (CH) / Français (CH)',
916 'fr_FR': u'French / Français',
917 'hr_HR': u'Croatian / hrvatski jezik',
918 'hu_HU': u'Hungarian / Magyar',
919 'id_ID': u'Indonesian / Bahasa Indonesia',
920 'it_IT': u'Italian / Italiano',
921 'lt_LT': u'Lithuanian / Lietuvių kalba',
922 'nl_NL': u'Dutch / Nederlands',
923 'nl_BE': u'Dutch (Belgium) / Nederlands (Belgïe)',
924 'pl_PL': u'Polish / Język polski',
925 'pt_BR': u'Portugese (BR) / português (BR)',
926 'pt_PT': u'Portugese / português',
927 'ro_RO': u'Romanian / limba română',
928 'ru_RU': u'Russian / русский язык',
929 'sl_SL': u'Slovenian / slovenščina',
930 'sq_AL': u'Albanian / Shqipëri',
931 'sv_SE': u'Swedish / svenska',
932 'tr_TR': u'Turkish / Türkçe',
933 'vi_VN': u'Vietnam / Cộng hòa xã hội chủ nghĩa Việt Nam',
934 'uk_UA': u'Ukrainian / украї́нська мо́ва',
935 'zh_CN': u'Chinese (CN) / 简体中文',
936 'zh_TW': u'Chinese (TW) / 正體字',
937 'th_TH': u'Thai / ภาษาไทย',
938 'tlh_TLH': u'Klingon',
942 def scan_languages():
944 # 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'))]
945 # ret = [(lang, lang_dict.get(lang, lang)) for lang in file_list]
946 # Now it will take all languages from get languages function without filter it with base module languages
947 lang_dict = get_languages()
948 ret = [(lang, lang_dict.get(lang, lang)) for lang in list(lang_dict)]
949 ret.sort(key=lambda k:k[1])
953 def get_user_companies(cr, user):
954 def _get_company_children(cr, ids):
957 cr.execute('SELECT id FROM res_company WHERE parent_id = ANY (%s)', (ids,))
958 res=[x[0] for x in cr.fetchall()]
959 res.extend(_get_company_children(cr, res))
961 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,))
962 compids=[cr.fetchone()[0]]
963 compids.extend(_get_company_children(cr, compids))
968 Input number : account or invoice number
969 Output return: the same number completed with the recursive mod10
972 codec=[0,9,4,6,8,2,7,1,3,5]
978 report = codec[ (int(digit) + report) % 10 ]
979 return result + str((10 - report) % 10)
984 Return the size in a human readable format
988 units = ('bytes', 'Kb', 'Mb', 'Gb')
989 if isinstance(sz,basestring):
992 while s >= 1024 and i < len(units)-1:
995 return "%0.2f %s" % (s, units[i])
998 from tools.func import wraps
1001 def wrapper(*args, **kwargs):
1003 from pprint import pformat
1005 vector = ['Call -> function: %r' % f]
1006 for i, arg in enumerate(args):
1007 vector.append(' arg %02d: %s' % (i, pformat(arg)))
1008 for key, value in kwargs.items():
1009 vector.append(' kwarg %10s: %s' % (key, pformat(value)))
1011 timeb4 = time.time()
1012 res = f(*args, **kwargs)
1014 vector.append(' result: %s' % pformat(res))
1015 vector.append(' time delta: %s' % (time.time() - timeb4))
1016 netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector))
1021 class profile(object):
1022 def __init__(self, fname=None):
1025 def __call__(self, f):
1026 from tools.func import wraps
1029 def wrapper(*args, **kwargs):
1030 class profile_wrapper(object):
1034 self.result = f(*args, **kwargs)
1035 pw = profile_wrapper()
1037 fname = self.fname or ("%s.cprof" % (f.func_name,))
1038 cProfile.runctx('pw()', globals(), locals(), filename=fname)
1045 This method allow you to debug your code without print
1047 >>> def func_foo(bar)
1050 ... qnx = (baz, bar)
1055 This will output on the logger:
1057 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42
1058 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42)
1060 To view the DEBUG lines in the logger you must start the server with the option
1065 from inspect import stack
1067 from pprint import pformat
1069 param = re.split("debug *\((.+)\)", st[4][0].strip())[1].strip()
1070 while param.count(')') > param.count('('): param = param[:param.rfind(')')]
1071 what = pformat(what)
1073 what = "%s = %s" % (param, what)
1074 netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what)
1077 icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
1078 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
1079 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
1080 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
1081 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
1082 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
1083 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
1084 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
1085 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
1086 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
1087 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
1088 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
1089 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
1090 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
1091 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
1092 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
1093 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
1094 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
1095 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
1096 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
1097 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
1098 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
1099 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
1100 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
1101 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
1102 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
1105 def extract_zip_file(zip_file, outdirectory):
1109 zf = zipfile.ZipFile(zip_file, 'r')
1111 for path in zf.namelist():
1112 tgt = os.path.join(out, path)
1113 tgtdir = os.path.dirname(tgt)
1114 if not os.path.exists(tgtdir):
1117 if not tgt.endswith(os.sep):
1118 fp = open(tgt, 'wb')
1119 fp.write(zf.read(path))
1123 def detect_ip_addr():
1124 def _detect_ip_addr():
1125 from array import array
1127 from struct import pack, unpack
1136 if not fcntl: # not UNIX:
1137 host = socket.gethostname()
1138 ip_addr = socket.gethostbyname(host)
1140 # get all interfaces:
1142 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1143 names = array('B', '\0' * nbytes)
1144 #print 'names: ', names
1145 outbytes = unpack('iL', fcntl.ioctl( s.fileno(), 0x8912, pack('iL', nbytes, names.buffer_info()[0])))[0]
1146 namestr = names.tostring()
1148 # try 64 bit kernel:
1149 for i in range(0, outbytes, 40):
1150 name = namestr[i:i+16].split('\0', 1)[0]
1152 ip_addr = socket.inet_ntoa(namestr[i+20:i+24])
1155 # try 32 bit kernel:
1157 ifaces = filter(None, [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)])
1159 for ifname in [iface for iface in ifaces if iface != 'lo']:
1160 ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24])
1163 return ip_addr or 'localhost'
1166 ip_addr = _detect_ip_addr()
1168 ip_addr = 'localhost'
1171 # RATIONALE BEHIND TIMESTAMP CALCULATIONS AND TIMEZONE MANAGEMENT:
1172 # The server side never does any timestamp calculation, always
1173 # sends them in a naive (timezone agnostic) format supposed to be
1174 # expressed within the server timezone, and expects the clients to
1175 # provide timestamps in the server timezone as well.
1176 # It stores all timestamps in the database in naive format as well,
1177 # which also expresses the time in the server timezone.
1178 # For this reason the server makes its timezone name available via the
1179 # common/timezone_get() rpc method, which clients need to read
1180 # to know the appropriate time offset to use when reading/writing
1182 def get_win32_timezone():
1183 """Attempt to return the "standard name" of the current timezone on a win32 system.
1184 @return: the standard name of the current win32 timezone, or False if it cannot be found.
1187 if (sys.platform == "win32"):
1190 hklm = _winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
1191 current_tz_key = _winreg.OpenKey(hklm, r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation", 0,_winreg.KEY_ALL_ACCESS)
1192 res = str(_winreg.QueryValueEx(current_tz_key,"StandardName")[0]) # [0] is value, [1] is type code
1193 _winreg.CloseKey(current_tz_key)
1194 _winreg.CloseKey(hklm)
1199 def detect_server_timezone():
1200 """Attempt to detect the timezone to use on the server side.
1201 Defaults to UTC if no working timezone can be found.
1202 @return: the timezone identifier as expected by pytz.timezone.
1209 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1210 "Python pytz module is not available. Timezone will be set to UTC by default.")
1213 # Option 1: the configuration option (did not exist before, so no backwards compatibility issue)
1214 # 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
1215 # Option 3: the environment variable TZ
1216 sources = [ (config['timezone'], 'OpenERP configuration'),
1217 (time.tzname[0], 'time.tzname'),
1218 (os.environ.get('TZ',False),'TZ environment variable'), ]
1219 # Option 4: OS-specific: /etc/timezone on Unix
1220 if (os.path.exists("/etc/timezone")):
1223 f = open("/etc/timezone")
1224 tz_value = f.read(128).strip()
1229 sources.append((tz_value,"/etc/timezone file"))
1230 # Option 5: timezone info from registry on Win32
1231 if (sys.platform == "win32"):
1232 # Timezone info is stored in windows registry.
1233 # However this is not likely to work very well as the standard name
1234 # of timezones in windows is rarely something that is known to pytz.
1235 # But that's ok, it is always possible to use a config option to set
1237 sources.append((get_win32_timezone(),"Windows Registry"))
1239 for (value,source) in sources:
1242 tz = pytz.timezone(value)
1243 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_INFO,
1244 "Using timezone %s obtained from %s." % (tz.zone,source))
1246 except pytz.UnknownTimeZoneError:
1247 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1248 "The timezone specified in %s (%s) is invalid, ignoring it." % (source,value))
1250 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1251 "No valid timezone could be detected, using default UTC timezone. You can specify it explicitly with option 'timezone' in the server configuration.")
1255 if __name__ == '__main__':
1260 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: