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:
419 ssl = config.get('smtp_ssl', False)
421 if not (email_from or config['email_from']):
422 raise ValueError("Sending an email requires either providing a sender "
423 "address or having configured one")
426 email_from = config.get('email_from', False)
434 msg = MIMEMultipart()
436 if not body: body = u''
438 msg = MIMEText(body.encode('utf8'),_subtype=subtype,_charset='utf-8')
439 except UnicodeEncodeError:
440 msg = MIMEText(body,_subtype=subtype,_charset='utf-8')
442 msg['Subject'] = Header(ustr(subject), 'utf-8')
443 msg['From'] = email_from
446 msg['Reply-To'] = reply_to
448 msg['Reply-To'] = msg['From']
449 msg['To'] = COMMASPACE.join(email_to)
451 msg['Cc'] = COMMASPACE.join(email_cc)
453 msg['Bcc'] = COMMASPACE.join(email_bcc)
454 msg['Date'] = formatdate(localtime=True)
456 # Add OpenERP Server information
457 msg['X-Generated-By'] = 'OpenERP (http://www.openerp.com)'
458 msg['X-OpenERP-Server-Host'] = socket.gethostname()
459 msg['X-OpenERP-Server-Version'] = release.version
461 msg['X-Priority'] = priorities.get(priority, '3 (Normal)')
463 # Add dynamic X Header
464 for key, value in x_headers.iteritems():
465 msg['X-OpenERP-%s' % key] = str(value)
468 msg['Message-Id'] = "<%s-openobject-%s@%s>" % (time.time(), openobject_id, socket.gethostname())
472 msg.attach(MIMEText(body.encode('utf8') or '',_subtype=subtype,_charset='utf-8'))
474 msg.attach(MIMEText(body or '', _charset='utf-8', _subtype=subtype) )
475 for (fname,fcontent) in attach:
476 part = MIMEBase('application', "octet-stream")
477 part.set_payload( fcontent )
478 Encoders.encode_base64(part)
479 part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
482 class WriteToLogger(object):
484 self.logger = netsvc.Logger()
487 self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s)
489 smtp_server = config['smtp_server']
490 if smtp_server.startswith('maildir:/'):
491 from mailbox import Maildir
492 maildir_path = smtp_server[8:]
494 mdir = Maildir(maildir_path,factory=None, create = True)
495 mdir.add(msg.as_string(True))
498 netsvc.Logger().notifyChannel('email_send (maildir)', netsvc.LOG_ERROR, e)
502 oldstderr = smtplib.stderr
505 # in case of debug, the messages are printed to stderr.
507 smtplib.stderr = WriteToLogger()
509 s.set_debuglevel(int(bool(debug))) # 0 or 1
510 s.connect(smtp_server, config['smtp_port'])
516 if config['smtp_user'] or config['smtp_password']:
517 s.login(config['smtp_user'], config['smtp_password'])
518 s.sendmail(email_from,
519 flatten([email_to, email_cc, email_bcc]),
525 smtplib.stderr = oldstderr
528 netsvc.Logger().notifyChannel('email_send', netsvc.LOG_ERROR, e)
533 #----------------------------------------------------------
535 #----------------------------------------------------------
536 # text must be latin-1 encoded
537 def sms_send(user, password, api_id, text, to):
539 url = "http://api.urlsms.com/SendSMS.aspx"
540 #url = "http://196.7.150.220/http/sendmsg"
541 params = urllib.urlencode({'UserID': user, 'Password': password, 'SenderID': api_id, 'MsgText': text, 'RecipientMobileNo':to})
542 f = urllib.urlopen(url+"?"+params)
543 # FIXME: Use the logger if there is an error
546 #---------------------------------------------------------
547 # Class that stores an updateable string (used in wizards)
548 #---------------------------------------------------------
549 class UpdateableStr(local):
551 def __init__(self, string=''):
555 return str(self.string)
558 return str(self.string)
560 def __nonzero__(self):
561 return bool(self.string)
564 class UpdateableDict(local):
565 '''Stores an updateable dict to use in wizards'''
567 def __init__(self, dict=None):
573 return str(self.dict)
576 return str(self.dict)
579 return self.dict.clear()
582 return self.dict.keys()
584 def __setitem__(self, i, y):
585 self.dict.__setitem__(i, y)
587 def __getitem__(self, i):
588 return self.dict.__getitem__(i)
591 return self.dict.copy()
594 return self.dict.iteritems()
597 return self.dict.iterkeys()
599 def itervalues(self):
600 return self.dict.itervalues()
602 def pop(self, k, d=None):
603 return self.dict.pop(k, d)
606 return self.dict.popitem()
608 def setdefault(self, k, d=None):
609 return self.dict.setdefault(k, d)
611 def update(self, E, **F):
612 return self.dict.update(E, F)
615 return self.dict.values()
617 def get(self, k, d=None):
618 return self.dict.get(k, d)
620 def has_key(self, k):
621 return self.dict.has_key(k)
624 return self.dict.items()
626 def __cmp__(self, y):
627 return self.dict.__cmp__(y)
629 def __contains__(self, k):
630 return self.dict.__contains__(k)
632 def __delitem__(self, y):
633 return self.dict.__delitem__(y)
636 return self.dict.__eq__(y)
639 return self.dict.__ge__(y)
642 return self.dict.__gt__(y)
645 return self.dict.__hash__()
648 return self.dict.__iter__()
651 return self.dict.__le__(y)
654 return self.dict.__len__()
657 return self.dict.__lt__(y)
660 return self.dict.__ne__(y)
663 # Don't use ! Use res.currency.round()
664 class currency(float):
666 def __init__(self, value, accuracy=2, rounding=None):
668 rounding=10**-accuracy
669 self.rounding=rounding
670 self.accuracy=accuracy
672 def __new__(cls, value, accuracy=2, rounding=None):
673 return float.__new__(cls, round(value, accuracy))
676 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
677 # return str(display_value)
689 Use it as a decorator of the function you plan to cache
690 Timeout: 0 = no timeout, otherwise in seconds
695 def __init__(self, timeout=None, skiparg=2, multi=None):
696 assert skiparg >= 2 # at least self and cr
698 self.timeout = config['cache_timeout']
700 self.timeout = timeout
701 self.skiparg = skiparg
703 self.lasttime = time.time()
706 cache.__caches.append(self)
709 def _generate_keys(self, dbname, kwargs2):
711 Generate keys depending of the arguments and the self.mutli value
716 pairs.sort(key=lambda (k,v): k)
717 for i, (k, v) in enumerate(pairs):
718 if isinstance(v, dict):
719 pairs[i] = (k, to_tuple(v))
720 if isinstance(v, (list, set)):
721 pairs[i] = (k, tuple(v))
722 elif not is_hashable(v):
723 pairs[i] = (k, repr(v))
727 key = (('dbname', dbname),) + to_tuple(kwargs2)
730 multis = kwargs2[self.multi][:]
732 kwargs2[self.multi] = (id,)
733 key = (('dbname', dbname),) + to_tuple(kwargs2)
736 def _unify_args(self, *args, **kwargs):
737 # Update named arguments with positional argument values (without self and cr)
738 kwargs2 = self.fun_default_values.copy()
739 kwargs2.update(kwargs)
740 kwargs2.update(dict(zip(self.fun_arg_names, args[self.skiparg-2:])))
743 def clear(self, dbname, *args, **kwargs):
744 """clear the cache for database dbname
745 if *args and **kwargs are both empty, clear all the keys related to this database
747 if not args and not kwargs:
748 keys_to_del = [key for key in self.cache if key[0][1] == dbname]
750 kwargs2 = self._unify_args(*args, **kwargs)
751 keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache]
753 for key in keys_to_del:
757 def clean_caches_for_db(cls, dbname):
758 for c in cls.__caches:
761 def __call__(self, fn):
762 if self.fun is not None:
763 raise Exception("Can not use a cache instance on more than one function")
766 argspec = inspect.getargspec(fn)
767 self.fun_arg_names = argspec[0][self.skiparg:]
768 self.fun_default_values = {}
770 self.fun_default_values = dict(zip(self.fun_arg_names[-len(argspec[3]):], argspec[3]))
772 def cached_result(self2, cr, *args, **kwargs):
773 if time.time()-int(self.timeout) > self.lasttime:
774 self.lasttime = time.time()
775 t = time.time()-int(self.timeout)
776 old_keys = [key for key in self.cache if self.cache[key][1] < t]
780 kwargs2 = self._unify_args(*args, **kwargs)
784 for key, id in self._generate_keys(cr.dbname, kwargs2):
785 if key in self.cache:
786 result[id] = self.cache[key][0]
792 kwargs2[self.multi] = notincache.keys()
794 result2 = fn(self2, cr, *args[:self.skiparg-2], **kwargs2)
796 key = notincache[None]
797 self.cache[key] = (result2, time.time())
798 result[None] = result2
802 self.cache[key] = (result2[id], time.time())
803 result.update(result2)
809 cached_result.clear_cache = self.clear
813 return s.replace('&','&').replace('<','<').replace('>','>')
816 """This method is similar to the builtin `str` method, except
817 it will return Unicode string.
819 @param value: the value to convert
822 @return: unicode string
825 if isinstance(value, unicode):
828 if hasattr(value, '__unicode__'):
829 return unicode(value)
831 if not isinstance(value, str):
834 try: # first try utf-8
835 return unicode(value, 'utf-8')
839 try: # then extened iso-8858
840 return unicode(value, 'iso-8859-15')
844 # else use default system locale
845 from locale import getlocale
846 return unicode(value, getlocale()[1])
848 def exception_to_unicode(e):
849 if (sys.version_info[:2] < (2,6)) and hasattr(e, 'message'):
850 return ustr(e.message)
851 if hasattr(e, 'args'):
852 return "\n".join((ustr(a) for a in e.args))
856 return u"Unknown message"
859 # to be compatible with python 2.4
861 if not hasattr(__builtin__, 'all'):
863 for element in iterable:
868 __builtin__.all = all
871 if not hasattr(__builtin__, 'any'):
873 for element in iterable:
878 __builtin__.any = any
881 get_iso = {'ca_ES':'ca',
894 def get_iso_codes(lang):
897 elif lang.find('_') != -1:
898 if lang.split('_')[0] == lang.split('_')[1].lower():
899 lang = lang.split('_')[0]
904 'ar_AR': u'Arabic / الْعَرَبيّة',
905 'bg_BG': u'Bulgarian / български',
906 'bs_BS': u'Bosnian / bosanski jezik',
907 'ca_ES': u'Catalan / Català',
908 'cs_CZ': u'Czech / Čeština',
909 'da_DK': u'Danish / Dansk',
910 'de_DE': u'German / Deutsch',
911 'el_GR': u'Greek / Ελληνικά',
912 'en_CA': u'English (CA)',
913 'en_GB': u'English (UK)',
914 'en_US': u'English (US)',
915 'es_AR': u'Spanish (AR) / Español (AR)',
916 'es_ES': u'Spanish / Español',
917 'et_EE': u'Estonian / Eesti keel',
918 'fi_FI': u'Finland / Suomi',
919 'fr_BE': u'French (BE) / Français (BE)',
920 'fr_CH': u'French (CH) / Français (CH)',
921 'fr_FR': u'French / Français',
922 'hr_HR': u'Croatian / hrvatski jezik',
923 'hu_HU': u'Hungarian / Magyar',
924 'id_ID': u'Indonesian / Bahasa Indonesia',
925 'it_IT': u'Italian / Italiano',
926 'lt_LT': u'Lithuanian / Lietuvių kalba',
927 'nl_NL': u'Dutch / Nederlands',
928 'nl_BE': u'Dutch (Belgium) / Nederlands (Belgïe)',
929 'pl_PL': u'Polish / Język polski',
930 'pt_BR': u'Portugese (BR) / português (BR)',
931 'pt_PT': u'Portugese / português',
932 'ro_RO': u'Romanian / limba română',
933 'ru_RU': u'Russian / русский язык',
934 'sl_SL': u'Slovenian / slovenščina',
935 'sq_AL': u'Albanian / Shqipëri',
936 'sv_SE': u'Swedish / svenska',
937 'tr_TR': u'Turkish / Türkçe',
938 'vi_VN': u'Vietnam / Cộng hòa xã hội chủ nghĩa Việt Nam',
939 'uk_UA': u'Ukrainian / украї́нська мо́ва',
940 'zh_CN': u'Chinese (CN) / 简体中文',
941 'zh_TW': u'Chinese (TW) / 正體字',
942 'th_TH': u'Thai / ภาษาไทย',
943 'tlh_TLH': u'Klingon',
947 def scan_languages():
949 # 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'))]
950 # ret = [(lang, lang_dict.get(lang, lang)) for lang in file_list]
951 # Now it will take all languages from get languages function without filter it with base module languages
952 lang_dict = get_languages()
953 ret = [(lang, lang_dict.get(lang, lang)) for lang in list(lang_dict)]
954 ret.sort(key=lambda k:k[1])
958 def get_user_companies(cr, user):
959 def _get_company_children(cr, ids):
962 cr.execute('SELECT id FROM res_company WHERE parent_id = ANY (%s)', (ids,))
963 res=[x[0] for x in cr.fetchall()]
964 res.extend(_get_company_children(cr, res))
966 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,))
967 compids=[cr.fetchone()[0]]
968 compids.extend(_get_company_children(cr, compids))
973 Input number : account or invoice number
974 Output return: the same number completed with the recursive mod10
977 codec=[0,9,4,6,8,2,7,1,3,5]
983 report = codec[ (int(digit) + report) % 10 ]
984 return result + str((10 - report) % 10)
989 Return the size in a human readable format
993 units = ('bytes', 'Kb', 'Mb', 'Gb')
994 if isinstance(sz,basestring):
997 while s >= 1024 and i < len(units)-1:
1000 return "%0.2f %s" % (s, units[i])
1003 from tools.func import wraps
1006 def wrapper(*args, **kwargs):
1008 from pprint import pformat
1010 vector = ['Call -> function: %r' % f]
1011 for i, arg in enumerate(args):
1012 vector.append(' arg %02d: %s' % (i, pformat(arg)))
1013 for key, value in kwargs.items():
1014 vector.append(' kwarg %10s: %s' % (key, pformat(value)))
1016 timeb4 = time.time()
1017 res = f(*args, **kwargs)
1019 vector.append(' result: %s' % pformat(res))
1020 vector.append(' time delta: %s' % (time.time() - timeb4))
1021 netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector))
1026 class profile(object):
1027 def __init__(self, fname=None):
1030 def __call__(self, f):
1031 from tools.func import wraps
1034 def wrapper(*args, **kwargs):
1035 class profile_wrapper(object):
1039 self.result = f(*args, **kwargs)
1040 pw = profile_wrapper()
1042 fname = self.fname or ("%s.cprof" % (f.func_name,))
1043 cProfile.runctx('pw()', globals(), locals(), filename=fname)
1050 This method allow you to debug your code without print
1052 >>> def func_foo(bar)
1055 ... qnx = (baz, bar)
1060 This will output on the logger:
1062 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42
1063 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42)
1065 To view the DEBUG lines in the logger you must start the server with the option
1070 from inspect import stack
1072 from pprint import pformat
1074 param = re.split("debug *\((.+)\)", st[4][0].strip())[1].strip()
1075 while param.count(')') > param.count('('): param = param[:param.rfind(')')]
1076 what = pformat(what)
1078 what = "%s = %s" % (param, what)
1079 netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what)
1082 icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
1083 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
1084 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
1085 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
1086 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
1087 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
1088 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
1089 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
1090 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
1091 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
1092 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
1093 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
1094 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
1095 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
1096 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
1097 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
1098 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
1099 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
1100 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
1101 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
1102 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
1103 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
1104 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
1105 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
1106 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
1107 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
1110 def extract_zip_file(zip_file, outdirectory):
1114 zf = zipfile.ZipFile(zip_file, 'r')
1116 for path in zf.namelist():
1117 tgt = os.path.join(out, path)
1118 tgtdir = os.path.dirname(tgt)
1119 if not os.path.exists(tgtdir):
1122 if not tgt.endswith(os.sep):
1123 fp = open(tgt, 'wb')
1124 fp.write(zf.read(path))
1128 def detect_ip_addr():
1129 def _detect_ip_addr():
1130 from array import array
1132 from struct import pack, unpack
1141 if not fcntl: # not UNIX:
1142 host = socket.gethostname()
1143 ip_addr = socket.gethostbyname(host)
1145 # get all interfaces:
1147 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1148 names = array('B', '\0' * nbytes)
1149 #print 'names: ', names
1150 outbytes = unpack('iL', fcntl.ioctl( s.fileno(), 0x8912, pack('iL', nbytes, names.buffer_info()[0])))[0]
1151 namestr = names.tostring()
1153 # try 64 bit kernel:
1154 for i in range(0, outbytes, 40):
1155 name = namestr[i:i+16].split('\0', 1)[0]
1157 ip_addr = socket.inet_ntoa(namestr[i+20:i+24])
1160 # try 32 bit kernel:
1162 ifaces = filter(None, [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)])
1164 for ifname in [iface for iface in ifaces if iface != 'lo']:
1165 ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24])
1168 return ip_addr or 'localhost'
1171 ip_addr = _detect_ip_addr()
1173 ip_addr = 'localhost'
1176 # RATIONALE BEHIND TIMESTAMP CALCULATIONS AND TIMEZONE MANAGEMENT:
1177 # The server side never does any timestamp calculation, always
1178 # sends them in a naive (timezone agnostic) format supposed to be
1179 # expressed within the server timezone, and expects the clients to
1180 # provide timestamps in the server timezone as well.
1181 # It stores all timestamps in the database in naive format as well,
1182 # which also expresses the time in the server timezone.
1183 # For this reason the server makes its timezone name available via the
1184 # common/timezone_get() rpc method, which clients need to read
1185 # to know the appropriate time offset to use when reading/writing
1187 def get_win32_timezone():
1188 """Attempt to return the "standard name" of the current timezone on a win32 system.
1189 @return: the standard name of the current win32 timezone, or False if it cannot be found.
1192 if (sys.platform == "win32"):
1195 hklm = _winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
1196 current_tz_key = _winreg.OpenKey(hklm, r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation", 0,_winreg.KEY_ALL_ACCESS)
1197 res = str(_winreg.QueryValueEx(current_tz_key,"StandardName")[0]) # [0] is value, [1] is type code
1198 _winreg.CloseKey(current_tz_key)
1199 _winreg.CloseKey(hklm)
1204 def detect_server_timezone():
1205 """Attempt to detect the timezone to use on the server side.
1206 Defaults to UTC if no working timezone can be found.
1207 @return: the timezone identifier as expected by pytz.timezone.
1214 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1215 "Python pytz module is not available. Timezone will be set to UTC by default.")
1218 # Option 1: the configuration option (did not exist before, so no backwards compatibility issue)
1219 # 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
1220 # Option 3: the environment variable TZ
1221 sources = [ (config['timezone'], 'OpenERP configuration'),
1222 (time.tzname[0], 'time.tzname'),
1223 (os.environ.get('TZ',False),'TZ environment variable'), ]
1224 # Option 4: OS-specific: /etc/timezone on Unix
1225 if (os.path.exists("/etc/timezone")):
1228 f = open("/etc/timezone")
1229 tz_value = f.read(128).strip()
1234 sources.append((tz_value,"/etc/timezone file"))
1235 # Option 5: timezone info from registry on Win32
1236 if (sys.platform == "win32"):
1237 # Timezone info is stored in windows registry.
1238 # However this is not likely to work very well as the standard name
1239 # of timezones in windows is rarely something that is known to pytz.
1240 # But that's ok, it is always possible to use a config option to set
1242 sources.append((get_win32_timezone(),"Windows Registry"))
1244 for (value,source) in sources:
1247 tz = pytz.timezone(value)
1248 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_INFO,
1249 "Using timezone %s obtained from %s." % (tz.zone,source))
1251 except pytz.UnknownTimeZoneError:
1252 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1253 "The timezone specified in %s (%s) is invalid, ignoring it." % (source,value))
1255 netsvc.Logger().notifyChannel("detect_server_timezone", netsvc.LOG_WARNING,
1256 "No valid timezone could be detected, using default UTC timezone. You can specify it explicitly with option 'timezone' in the server configuration.")
1260 if __name__ == '__main__':
1265 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: