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 and not config['email_from']:
422 raise Exception("No Email sender by default, see config file")
425 email_from = config.get('email_from', False)
434 msg = MIMEText(body.encode('utf8') or '',_subtype=subtype,_charset='utf-8')
436 msg = MIMEText(body or '',_subtype=subtype,_charset='utf-8')
438 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.items():
463 msg['X-OpenERP-%s' % key] = str(value)
466 msg['Message-Id'] = "<%s-openobject-%s@%s>" % (time.time(), openobject_id, socket.gethostname())
470 msg.attach(MIMEText(body.encode('utf8') or '',_subtype=subtype,_charset='utf-8'))
472 msg.attach(MIMEText(body or '', _charset='utf-8', _subtype=subtype) )
473 for (fname,fcontent) in attach:
474 part = MIMEBase('application', "octet-stream")
475 part.set_payload( fcontent )
476 Encoders.encode_base64(part)
477 part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
480 class WriteToLogger(object):
482 self.logger = netsvc.Logger()
485 self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s)
487 smtp_server = config['smtp_server']
488 if smtp_server.startswith('maildir:/'):
489 from mailbox import Maildir
490 maildir_path = smtp_server[8:]
492 mdir = Maildir(maildir_path,factory=None, create = True)
493 mdir.add(msg.as_string(True))
496 netsvc.Logger().notifyChannel('email_send (maildir)', netsvc.LOG_ERROR, e)
500 oldstderr = smtplib.stderr
503 # in case of debug, the messages are printed to stderr.
505 smtplib.stderr = WriteToLogger()
507 s.set_debuglevel(int(bool(debug))) # 0 or 1
508 s.connect(smtp_server, config['smtp_port'])
514 if config['smtp_user'] or config['smtp_password']:
515 s.login(config['smtp_user'], config['smtp_password'])
516 s.sendmail(email_from,
517 flatten([email_to, email_cc, email_bcc]),
523 smtplib.stderr = oldstderr
526 netsvc.Logger().notifyChannel('email_send', netsvc.LOG_ERROR, e)
531 #----------------------------------------------------------
533 #----------------------------------------------------------
534 # text must be latin-1 encoded
535 def sms_send(user, password, api_id, text, to):
537 url = "http://api.urlsms.com/SendSMS.aspx"
538 #url = "http://196.7.150.220/http/sendmsg"
539 params = urllib.urlencode({'UserID': user, 'Password': password, 'SenderID': api_id, 'MsgText': text, 'RecipientMobileNo':to})
540 f = urllib.urlopen(url+"?"+params)
541 # FIXME: Use the logger if there is an error
544 #---------------------------------------------------------
545 # Class that stores an updateable string (used in wizards)
546 #---------------------------------------------------------
547 class UpdateableStr(local):
549 def __init__(self, string=''):
553 return str(self.string)
556 return str(self.string)
558 def __nonzero__(self):
559 return bool(self.string)
562 class UpdateableDict(local):
563 '''Stores an updateable dict to use in wizards'''
565 def __init__(self, dict=None):
571 return str(self.dict)
574 return str(self.dict)
577 return self.dict.clear()
580 return self.dict.keys()
582 def __setitem__(self, i, y):
583 self.dict.__setitem__(i, y)
585 def __getitem__(self, i):
586 return self.dict.__getitem__(i)
589 return self.dict.copy()
592 return self.dict.iteritems()
595 return self.dict.iterkeys()
597 def itervalues(self):
598 return self.dict.itervalues()
600 def pop(self, k, d=None):
601 return self.dict.pop(k, d)
604 return self.dict.popitem()
606 def setdefault(self, k, d=None):
607 return self.dict.setdefault(k, d)
609 def update(self, E, **F):
610 return self.dict.update(E, F)
613 return self.dict.values()
615 def get(self, k, d=None):
616 return self.dict.get(k, d)
618 def has_key(self, k):
619 return self.dict.has_key(k)
622 return self.dict.items()
624 def __cmp__(self, y):
625 return self.dict.__cmp__(y)
627 def __contains__(self, k):
628 return self.dict.__contains__(k)
630 def __delitem__(self, y):
631 return self.dict.__delitem__(y)
634 return self.dict.__eq__(y)
637 return self.dict.__ge__(y)
639 def __getitem__(self, y):
640 return self.dict.__getitem__(y)
643 return self.dict.__gt__(y)
646 return self.dict.__hash__()
649 return self.dict.__iter__()
652 return self.dict.__le__(y)
655 return self.dict.__len__()
658 return self.dict.__lt__(y)
661 return self.dict.__ne__(y)
664 # Don't use ! Use res.currency.round()
665 class currency(float):
667 def __init__(self, value, accuracy=2, rounding=None):
669 rounding=10**-accuracy
670 self.rounding=rounding
671 self.accuracy=accuracy
673 def __new__(cls, value, accuracy=2, rounding=None):
674 return float.__new__(cls, round(value, accuracy))
677 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
678 # return str(display_value)
690 Use it as a decorator of the function you plan to cache
691 Timeout: 0 = no timeout, otherwise in seconds
696 def __init__(self, timeout=None, skiparg=2, multi=None):
697 assert skiparg >= 2 # at least self and cr
699 self.timeout = config['cache_timeout']
701 self.timeout = timeout
702 self.skiparg = skiparg
704 self.lasttime = time.time()
707 cache.__caches.append(self)
710 def _generate_keys(self, dbname, kwargs2):
712 Generate keys depending of the arguments and the self.mutli value
717 pairs.sort(key=lambda (k,v): k)
718 for i, (k, v) in enumerate(pairs):
719 if isinstance(v, dict):
720 pairs[i] = (k, to_tuple(v))
721 if isinstance(v, (list, set)):
722 pairs[i] = (k, tuple(v))
723 elif not is_hashable(v):
724 pairs[i] = (k, repr(v))
728 key = (('dbname', dbname),) + to_tuple(kwargs2)
731 multis = kwargs2[self.multi][:]
733 kwargs2[self.multi] = (id,)
734 key = (('dbname', dbname),) + to_tuple(kwargs2)
737 def _unify_args(self, *args, **kwargs):
738 # Update named arguments with positional argument values (without self and cr)
739 kwargs2 = self.fun_default_values.copy()
740 kwargs2.update(kwargs)
741 kwargs2.update(dict(zip(self.fun_arg_names, args[self.skiparg-2:])))
744 def clear(self, dbname, *args, **kwargs):
745 """clear the cache for database dbname
746 if *args and **kwargs are both empty, clear all the keys related to this database
748 if not args and not kwargs:
749 keys_to_del = [key for key in self.cache if key[0][1] == dbname]
751 kwargs2 = self._unify_args(*args, **kwargs)
752 keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache]
754 for key in keys_to_del:
758 def clean_caches_for_db(cls, dbname):
759 for c in cls.__caches:
762 def __call__(self, fn):
763 if self.fun is not None:
764 raise Exception("Can not use a cache instance on more than one function")
767 argspec = inspect.getargspec(fn)
768 self.fun_arg_names = argspec[0][self.skiparg:]
769 self.fun_default_values = {}
771 self.fun_default_values = dict(zip(self.fun_arg_names[-len(argspec[3]):], argspec[3]))
773 def cached_result(self2, cr, *args, **kwargs):
774 if time.time()-int(self.timeout) > self.lasttime:
775 self.lasttime = time.time()
776 t = time.time()-int(self.timeout)
777 old_keys = [key for key in self.cache if self.cache[key][1] < t]
781 kwargs2 = self._unify_args(*args, **kwargs)
785 for key, id in self._generate_keys(cr.dbname, kwargs2):
786 if key in self.cache:
787 result[id] = self.cache[key][0]
793 kwargs2[self.multi] = notincache.keys()
795 result2 = fn(self2, cr, *args[:self.skiparg-2], **kwargs2)
797 key = notincache[None]
798 self.cache[key] = (result2, time.time())
799 result[None] = result2
803 self.cache[key] = (result2[id], time.time())
804 result.update(result2)
810 cached_result.clear_cache = self.clear
814 return s.replace('&','&').replace('<','<').replace('>','>')
817 """This method is similar to the builtin `str` method, except
818 it will return Unicode string.
820 @param value: the value to convert
823 @return: unicode string
826 if isinstance(value, unicode):
829 if hasattr(value, '__unicode__'):
830 return unicode(value)
832 if not isinstance(value, str):
835 try: # first try utf-8
836 return unicode(value, 'utf-8')
840 try: # then extened iso-8858
841 return unicode(value, 'iso-8859-15')
845 # else use default system locale
846 from locale import getlocale
847 return unicode(value, getlocale()[1])
849 def exception_to_unicode(e):
850 if (sys.version_info[:2] < (2,6)) and hasattr(e, 'message'):
851 return ustr(e.message)
852 if hasattr(e, 'args'):
853 return "\n".join((ustr(a) for a in e.args))
857 return u"Unknown message"
860 # to be compatible with python 2.4
862 if not hasattr(__builtin__, 'all'):
864 for element in iterable:
869 __builtin__.all = all
872 if not hasattr(__builtin__, 'any'):
874 for element in iterable:
879 __builtin__.any = any
882 get_iso = {'ca_ES':'ca',
895 def get_iso_codes(lang):
898 elif lang.find('_') != -1:
899 if lang.split('_')[0] == lang.split('_')[1].lower():
900 lang = lang.split('_')[0]
905 'ar_AR': u'Arabic / الْعَرَبيّة',
906 'bg_BG': u'Bulgarian / български',
907 'bs_BS': u'Bosnian / bosanski jezik',
908 'ca_ES': u'Catalan / Català',
909 'cs_CZ': u'Czech / Čeština',
910 'da_DK': u'Danish / Dansk',
911 'de_DE': u'German / Deutsch',
912 'el_GR': u'Greek / Ελληνικά',
913 'en_CA': u'English (CA)',
914 'en_GB': u'English (UK)',
915 'en_US': u'English (US)',
916 'es_AR': u'Spanish (AR) / Español (AR)',
917 'es_ES': u'Spanish / Español',
918 'et_EE': u'Estonian / Eesti keel',
919 'fi_FI': u'Finland / Suomi',
920 'fr_BE': u'French (BE) / Français (BE)',
921 'fr_CH': u'French (CH) / Français (CH)',
922 'fr_FR': u'French / Français',
923 'hr_HR': u'Croatian / hrvatski jezik',
924 'hu_HU': u'Hungarian / Magyar',
925 'id_ID': u'Indonesian / Bahasa Indonesia',
926 'it_IT': u'Italian / Italiano',
927 'lt_LT': u'Lithuanian / Lietuvių kalba',
928 'nl_NL': u'Dutch / Nederlands',
929 'nl_BE': u'Dutch (Belgium) / Nederlands (Belgïe)',
930 'pl_PL': u'Polish / Język polski',
931 'pt_BR': u'Portugese (BR) / português (BR)',
932 'pt_PT': u'Portugese / português',
933 'ro_RO': u'Romanian / limba română',
934 'ru_RU': u'Russian / русский язык',
935 'sl_SL': u'Slovenian / slovenščina',
936 'sq_AL': u'Albanian / Shqipëri',
937 'sv_SE': u'Swedish / svenska',
938 'tr_TR': u'Turkish / Türkçe',
939 'vi_VN': u'Vietnam / Cộng hòa xã hội chủ nghĩa Việt Nam',
940 'uk_UA': u'Ukrainian / украї́нська мо́ва',
941 'zh_CN': u'Chinese (CN) / 简体中文',
942 'zh_TW': u'Chinese (TW) / 正體字',
943 'th_TH': u'Thai / ภาษาไทย',
944 'tlh_TLH': u'Klingon',
948 def scan_languages():
950 # 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'))]
951 # ret = [(lang, lang_dict.get(lang, lang)) for lang in file_list]
952 # Now it will take all languages from get languages function without filter it with base module languages
953 lang_dict = get_languages()
954 ret = [(lang, lang_dict.get(lang, lang)) for lang in list(lang_dict)]
955 ret.sort(key=lambda k:k[1])
959 def get_user_companies(cr, user):
960 def _get_company_children(cr, ids):
963 cr.execute('SELECT id FROM res_company WHERE parent_id = ANY (%s)', (ids,))
964 res=[x[0] for x in cr.fetchall()]
965 res.extend(_get_company_children(cr, res))
967 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,))
968 compids=[cr.fetchone()[0]]
969 compids.extend(_get_company_children(cr, compids))
974 Input number : account or invoice number
975 Output return: the same number completed with the recursive mod10
978 codec=[0,9,4,6,8,2,7,1,3,5]
984 report = codec[ (int(digit) + report) % 10 ]
985 return result + str((10 - report) % 10)
990 Return the size in a human readable format
994 units = ('bytes', 'Kb', 'Mb', 'Gb')
995 if isinstance(sz,basestring):
998 while s >= 1024 and i < len(units)-1:
1001 return "%0.2f %s" % (s, units[i])
1004 from tools.func import wraps
1007 def wrapper(*args, **kwargs):
1009 from pprint import pformat
1011 vector = ['Call -> function: %r' % f]
1012 for i, arg in enumerate(args):
1013 vector.append(' arg %02d: %s' % (i, pformat(arg)))
1014 for key, value in kwargs.items():
1015 vector.append(' kwarg %10s: %s' % (key, pformat(value)))
1017 timeb4 = time.time()
1018 res = f(*args, **kwargs)
1020 vector.append(' result: %s' % pformat(res))
1021 vector.append(' time delta: %s' % (time.time() - timeb4))
1022 netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector))
1027 class profile(object):
1028 def __init__(self, fname=None):
1031 def __call__(self, f):
1032 from tools.func import wraps
1035 def wrapper(*args, **kwargs):
1036 class profile_wrapper(object):
1040 self.result = f(*args, **kwargs)
1041 pw = profile_wrapper()
1043 fname = self.fname or ("%s.cprof" % (f.func_name,))
1044 cProfile.runctx('pw()', globals(), locals(), filename=fname)
1051 This method allow you to debug your code without print
1053 >>> def func_foo(bar)
1056 ... qnx = (baz, bar)
1061 This will output on the logger:
1063 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42
1064 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42)
1066 To view the DEBUG lines in the logger you must start the server with the option
1071 from inspect import stack
1073 from pprint import pformat
1075 param = re.split("debug *\((.+)\)", st[4][0].strip())[1].strip()
1076 while param.count(')') > param.count('('): param = param[:param.rfind(')')]
1077 what = pformat(what)
1079 what = "%s = %s" % (param, what)
1080 netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what)
1083 icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
1084 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
1085 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
1086 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
1087 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
1088 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
1089 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
1090 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
1091 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
1092 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
1093 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
1094 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
1095 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
1096 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
1097 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
1098 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
1099 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
1100 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
1101 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
1102 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
1103 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
1104 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
1105 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
1106 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
1107 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
1108 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
1111 def extract_zip_file(zip_file, outdirectory):
1115 zf = zipfile.ZipFile(zip_file, 'r')
1117 for path in zf.namelist():
1118 tgt = os.path.join(out, path)
1119 tgtdir = os.path.dirname(tgt)
1120 if not os.path.exists(tgtdir):
1123 if not tgt.endswith(os.sep):
1124 fp = open(tgt, 'wb')
1125 fp.write(zf.read(path))
1129 def detect_ip_addr():
1130 def _detect_ip_addr():
1131 from array import array
1133 from struct import pack, unpack
1142 if not fcntl: # not UNIX:
1143 host = socket.gethostname()
1144 ip_addr = socket.gethostbyname(host)
1146 # get all interfaces:
1148 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1149 names = array('B', '\0' * nbytes)
1150 #print 'names: ', names
1151 outbytes = unpack('iL', fcntl.ioctl( s.fileno(), 0x8912, pack('iL', nbytes, names.buffer_info()[0])))[0]
1152 namestr = names.tostring()
1154 # try 64 bit kernel:
1155 for i in range(0, outbytes, 40):
1156 name = namestr[i:i+16].split('\0', 1)[0]
1158 ip_addr = socket.inet_ntoa(namestr[i+20:i+24])
1161 # try 32 bit kernel:
1163 ifaces = filter(None, [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)])
1165 for ifname in [iface for iface in ifaces if iface != 'lo']:
1166 ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24])
1169 return ip_addr or 'localhost'
1172 ip_addr = _detect_ip_addr()
1174 ip_addr = 'localhost'
1178 if __name__ == '__main__':
1183 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: