1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (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 General Public License for more details.
18 # You should have received a copy of the GNU 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 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(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) and not os.path.isfile(mod_path+'.zip'):
59 info = eval(file(terp_file).read())
60 elif zipfile.is_zipfile(mod_path+'.zip'):
61 zfile = zipfile.ZipFile(mod_path+'.zip')
62 i = os.path.splitext(i)[0]
63 info = eval(zfile.read(os.path.join(i, '__terp__.py')))
65 categs = info.get('category', 'Uncategorized').split('/')
69 cr.execute('select id \
70 from ir_module_category \
71 where name=%s and parent_id=%s', (categs[0], p_id))
73 cr.execute('select id \
74 from ir_module_category \
75 where name=%s and parent_id is NULL', (categs[0],))
78 cr.execute('select nextval(\'ir_module_category_id_seq\')')
79 c_id = cr.fetchone()[0]
80 cr.execute('insert into ir_module_category \
81 (id, name, parent_id) \
82 values (%s, %s, %s)', (c_id, categs[0], p_id))
88 active = info.get('active', False)
89 installable = info.get('installable', True)
96 state = 'uninstallable'
97 cr.execute('select nextval(\'ir_module_module_id_seq\')')
99 cr.execute('insert into ir_module_module \
100 (id, author, website, name, shortdesc, description, \
101 category_id, state) \
102 values (%s, %s, %s, %s, %s, %s, %s, %s)', (
103 id, info.get('author', ''),
104 info.get('website', ''), i, info.get('name', False),
105 info.get('description', ''), p_id, state))
106 dependencies = info.get('depends', [])
107 for d in dependencies:
108 cr.execute('insert into ir_module_module_dependency \
109 (module_id,name) values (%s, %s)', (id, d))
112 def find_in_path(name):
117 path = [dir for dir in os.environ['PATH'].split(sep)
118 if os.path.isdir(dir)]
120 val = os.path.join(dir, name)
121 if os.path.isfile(val) or os.path.islink(val):
125 def find_pg_tool(name):
126 if config['pg_path'] and config['pg_path'] != 'None':
127 return os.path.join(config['pg_path'], name)
129 return find_in_path(name)
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 adp = os.path.normcase(os.path.abspath(config['addons_path']))
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
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'):
228 zfile = zipfile.ZipFile(head+'.zip')
230 fo = cStringIO.StringIO(zfile.read(os.path.join(
231 os.path.basename(head), zipname).replace(
238 name2 = os.path.normpath(os.path.join(head + '.zip', zipname))
240 for i in (name2, name):
241 if i and os.path.isfile(i):
246 if os.path.splitext(name)[1] == '.rml':
247 raise IOError, 'Report %s doesn\'t exist or deleted : ' %str(name)
248 raise IOError, 'File not found : '+str(name)
251 #----------------------------------------------------------
253 #----------------------------------------------------------
255 """Flatten a list of elements into a uniqu list
256 Author: Christophe Simonis (christophe@tinyerp.com)
265 >>> flatten( [[], [[]]] )
267 >>> flatten( [[['a','b'], 'c'], 'd', ['e', [], 'f']] )
268 ['a', 'b', 'c', 'd', 'e', 'f']
269 >>> t = (1,2,(3,), [4, 5, [6, [7], (8, 9), ([10, 11, (12, 13)]), [14, [], (15,)], []]])
271 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
275 return hasattr(x, "__iter__")
280 map(r.append, flatten(e))
285 def reverse_enumerate(l):
286 """Like enumerate but in the other sens
287 >>> a = ['a', 'b', 'c']
288 >>> it = reverse_enumerate(a)
296 Traceback (most recent call last):
297 File "<stdin>", line 1, in <module>
300 return izip(xrange(len(l)-1, -1, -1), reversed(l))
302 #----------------------------------------------------------
304 #----------------------------------------------------------
305 def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False, attach=None, tinycrm=False, ssl=False, debug=False,subtype='plain'):
308 from email.MIMEText import MIMEText
309 from email.MIMEBase import MIMEBase
310 from email.MIMEMultipart import MIMEMultipart
311 from email.Header import Header
312 from email.Utils import formatdate, COMMASPACE
313 from email.Utils import formatdate, COMMASPACE
314 from email import Encoders
317 ssl = config.get('smtp_ssl', False)
325 msg = MIMEText(body or '',_subtype=subtype,_charset='utf-8')
327 msg = MIMEMultipart()
329 msg['Subject'] = Header(subject.decode('utf8'), 'utf-8')
330 msg['From'] = email_from
333 msg['Reply-To'] = msg['From']+', '+reply_to
334 msg['To'] = COMMASPACE.join(email_to)
336 msg['Cc'] = COMMASPACE.join(email_cc)
338 msg['Bcc'] = COMMASPACE.join(email_bcc)
339 msg['Date'] = formatdate(localtime=True)
342 msg['Message-Id'] = "<%s-tinycrm-%s@%s>" % (time.time(), tinycrm, socket.gethostname())
345 msg.attach( MIMEText(body or '', _charset='utf-8', _subtype=subtype) )
347 for (fname,fcontent) in attach:
348 part = MIMEBase('application', "octet-stream")
349 part.set_payload( fcontent )
350 Encoders.encode_base64(part)
351 part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
358 s.connect(config['smtp_server'], config['smtp_port'])
364 if config['smtp_user'] or config['smtp_password']:
365 s.login(config['smtp_user'], config['smtp_password'])
367 s.sendmail(email_from,
368 flatten([email_to, email_cc, email_bcc]),
374 logging.getLogger().error(str(e))
378 #----------------------------------------------------------
380 #----------------------------------------------------------
381 # text must be latin-1 encoded
382 def sms_send(user, password, api_id, text, to):
384 params = urllib.urlencode({'user': user, 'password': password, 'api_id': api_id, 'text': text, 'to':to})
385 #f = urllib.urlopen("http://api.clickatell.com/http/sendmsg", params)
386 f = urllib.urlopen("http://196.7.150.220/http/sendmsg", params)
387 # FIXME: Use the logger if there is an error
390 #---------------------------------------------------------
391 # Class that stores an updateable string (used in wizards)
392 #---------------------------------------------------------
393 class UpdateableStr(local):
395 def __init__(self, string=''):
399 return str(self.string)
402 return str(self.string)
404 def __nonzero__(self):
405 return bool(self.string)
408 class UpdateableDict(local):
409 '''Stores an updateable dict to use in wizards'''
411 def __init__(self, dict=None):
417 return str(self.dict)
420 return str(self.dict)
423 return self.dict.clear()
426 return self.dict.keys()
428 def __setitem__(self, i, y):
429 self.dict.__setitem__(i, y)
431 def __getitem__(self, i):
432 return self.dict.__getitem__(i)
435 return self.dict.copy()
438 return self.dict.iteritems()
441 return self.dict.iterkeys()
443 def itervalues(self):
444 return self.dict.itervalues()
446 def pop(self, k, d=None):
447 return self.dict.pop(k, d)
450 return self.dict.popitem()
452 def setdefault(self, k, d=None):
453 return self.dict.setdefault(k, d)
455 def update(self, E, **F):
456 return self.dict.update(E, F)
459 return self.dict.values()
461 def get(self, k, d=None):
462 return self.dict.get(k, d)
464 def has_key(self, k):
465 return self.dict.has_key(k)
468 return self.dict.items()
470 def __cmp__(self, y):
471 return self.dict.__cmp__(y)
473 def __contains__(self, k):
474 return self.dict.__contains__(k)
476 def __delitem__(self, y):
477 return self.dict.__delitem__(y)
480 return self.dict.__eq__(y)
483 return self.dict.__ge__(y)
485 def __getitem__(self, y):
486 return self.dict.__getitem__(y)
489 return self.dict.__gt__(y)
492 return self.dict.__hash__()
495 return self.dict.__iter__()
498 return self.dict.__le__(y)
501 return self.dict.__len__()
504 return self.dict.__lt__(y)
507 return self.dict.__ne__(y)
510 # Don't use ! Use res.currency.round()
511 class currency(float):
513 def __init__(self, value, accuracy=2, rounding=None):
515 rounding=10**-accuracy
516 self.rounding=rounding
517 self.accuracy=accuracy
519 def __new__(cls, value, accuracy=2, rounding=None):
520 return float.__new__(cls, round(value, accuracy))
523 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
524 # return str(display_value)
536 Use it as a decorator of the function you plan to cache
537 Timeout: 0 = no timeout, otherwise in seconds
542 def __init__(self, timeout=None, skiparg=2, multi=None):
543 assert skiparg >= 2 # at least self and cr
545 self.timeout = config['cache_timeout']
547 self.timeout = timeout
548 self.skiparg = skiparg
550 self.lasttime = time.time()
553 cache.__caches.append(self)
556 def _generate_keys(self, dbname, kwargs2):
558 Generate keys depending of the arguments and the self.mutli value
567 key = (('dbname', dbname),) + to_tuple(kwargs2)
570 multis = kwargs2[self.multi][:]
572 kwargs2[self.multi] = (id,)
573 key = (('dbname', dbname),) + to_tuple(kwargs2)
576 def _unify_args(self, *args, **kwargs):
577 # Update named arguments with positional argument values (without self and cr)
578 kwargs2 = self.fun_default_values.copy()
579 kwargs2.update(kwargs)
580 kwargs2.update(dict(zip(self.fun_arg_names, args[self.skiparg-2:])))
582 if isinstance(kwargs2[k], (list, dict, set)):
583 kwargs2[k] = tuple(kwargs2[k])
584 elif not is_hashable(kwargs2[k]):
585 kwargs2[k] = repr(kwargs2[k])
589 def clear(self, dbname, *args, **kwargs):
590 """clear the cache for database dbname
591 if *args and **kwargs are both empty, clear all the keys related to this database
593 if not args and not kwargs:
594 keys_to_del = [key for key in self.cache if key[0][1] == dbname]
596 kwargs2 = self._unify_args(*args, **kwargs)
597 keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache]
599 for key in keys_to_del:
603 def clean_caches_for_db(cls, dbname):
604 for c in cls.__caches:
607 def __call__(self, fn):
608 if self.fun is not None:
609 raise Exception("Can not use a cache instance on more than one function")
612 argspec = inspect.getargspec(fn)
613 self.fun_arg_names = argspec[0][self.skiparg:]
614 self.fun_default_values = {}
616 self.fun_default_values = dict(zip(self.fun_arg_names[-len(argspec[3]):], argspec[3]))
618 def cached_result(self2, cr, *args, **kwargs):
619 if time.time()-self.timeout > self.lasttime:
620 self.lasttime = time.time()
621 t = time.time()-self.timeout
622 for key in self.cache.keys():
623 if self.cache[key][1]<t:
626 kwargs2 = self._unify_args(*args, **kwargs)
630 for key, id in self._generate_keys(cr.dbname, kwargs2):
631 if key in self.cache:
632 result[id] = self.cache[key][0]
638 kwargs2[self.multi] = notincache.keys()
640 result2 = fn(self2, cr, *args[2:self.skiparg], **kwargs2)
642 key = notincache[None]
643 self.cache[key] = (result2, time.time())
644 result[None] = result2
648 self.cache[key] = (result2[id], time.time())
649 result.update(result2)
655 cached_result.clear_cache = self.clear
659 return s.replace('&','&').replace('<','<').replace('>','>')
662 """This method is similar to the builtin `str` method, except
663 it will return Unicode string.
665 @param value: the value to convert
668 @return: unicode string
671 if isinstance(value, unicode):
674 if hasattr(value, '__unicode__'):
675 return unicode(value)
677 if not isinstance(value, str):
680 return unicode(value, 'utf-8')
682 def exception_to_unicode(e):
683 if hasattr(e, 'message'):
684 return ustr(e.message)
685 if hasattr(e, 'args'):
686 return "\n".join((ustr(a) for a in e.args))
690 return u"Unknow message"
693 # to be compatible with python 2.4
695 if not hasattr(__builtin__, 'all'):
697 for element in iterable:
702 __builtin__.all = all
705 if not hasattr(__builtin__, 'any'):
707 for element in iterable:
712 __builtin__.any = any
719 'ar_AR': u'Arabic / الْعَرَبيّة',
720 'bg_BG': u'Bulgarian / български',
721 'bs_BS': u'Bosnian / bosanski jezik',
722 'ca_ES': u'Catalan / Català',
723 'cs_CZ': u'Czech / Čeština',
724 'de_DE': u'German / Deutsch',
725 'en_CA': u'English (CA)',
726 'en_EN': u'English (default)',
727 'en_GB': u'English (UK)',
728 'en_US': u'English (US)',
729 'es_AR': u'Spanish (AR) / Español (AR)',
730 'es_ES': u'Spanish / Español',
731 'et_ET': u'Estonian / Eesti keel',
732 'fr_BE': u'French (BE) / Français (BE)',
733 'fr_CH': u'French (CH) / Français (CH)',
734 'fr_FR': u'French / Français',
735 'hr_HR': u'Croatian / hrvatski jezik',
736 'hu_HU': u'Hungarian / Magyar',
737 'it_IT': u'Italian / Italiano',
738 'lt_LT': u'Lithuanian / Lietuvių kalba',
739 'nl_NL': u'Dutch / Nederlands',
740 'pl_PL': u'Polish / Język polski',
741 'pt_BR': u'Portugese (BR) / português (BR)',
742 'pt_PT': u'Portugese / português',
743 'ro_RO': u'Romanian / limba română',
744 'ru_RU': u'Russian / русский язык',
745 'sl_SL': u'Slovenian / slovenščina',
746 'sv_SE': u'Swedish / svenska',
747 'tr_TR': u'Turkish / Türkçe',
748 'uk_UK': u'Ukrainian / украї́нська мо́ва',
749 'zh_CN': u'Chinese (CN) / 简体中文' ,
750 'zh_TW': u'Chinese (TW) / 正體字',
754 def scan_languages():
756 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'))]
757 lang_dict = get_languages()
758 ret = [(lang, lang_dict.get(lang, lang)) for lang in file_list]
759 ret.sort(key=lambda k:k[1])
763 def get_user_companies(cr, user):
764 def _get_company_children(cr, ids):
767 cr.execute('SELECT id FROM res_company WHERE parent_id = any(array[%s])' %(','.join([str(x) for x in ids]),))
768 res=[x[0] for x in cr.fetchall()]
769 res.extend(_get_company_children(cr, res))
771 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,))
772 compids=[cr.fetchone()[0]]
773 compids.extend(_get_company_children(cr, compids))
778 Input number : account or invoice number
779 Output return: the same number completed with the recursive mod10
782 codec=[0,9,4,6,8,2,7,1,3,5]
788 report = codec[ (int(digit) + report) % 10 ]
789 return result + str((10 - report) % 10)
794 Return the size in a human readable format
798 units = ('bytes', 'Kb', 'Mb', 'Gb')
799 if isinstance(sz,basestring):
802 while s >= 1024 and i < len(units)-1:
805 return "%0.2f %s" % (s, units[i])
808 from tools.func import wraps
811 def wrapper(*args, **kwargs):
813 from pprint import pformat
815 vector = ['Call -> function: %r' % f]
816 for i, arg in enumerate(args):
817 vector.append(' arg %02d: %s' % (i, pformat(arg)))
818 for key, value in kwargs.items():
819 vector.append(' kwarg %10s: %s' % (key, pformat(value)))
822 res = f(*args, **kwargs)
824 vector.append(' result: %s' % pformat(res))
825 vector.append(' time delta: %s' % (time.time() - timeb4))
826 netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector))
833 This method allow you to debug your code without print
835 >>> def func_foo(bar)
843 This will output on the logger:
845 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42
846 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42)
848 To view the DEBUG lines in the logger you must start the server with the option
853 from inspect import stack
855 from pprint import pformat
857 param = re.split("debug *\((.+)\)", st[4][0].strip())[1].strip()
858 while param.count(')') > param.count('('): param = param[:param.rfind(')')]
861 what = "%s = %s" % (param, what)
862 netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what)
865 icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
866 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
867 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
868 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
869 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
870 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
871 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
872 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
873 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
874 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
875 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
876 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
877 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
878 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
879 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
880 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
881 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
882 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
883 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
884 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
885 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
886 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
887 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
888 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
889 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
890 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
895 if __name__ == '__main__':
901 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: