1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2008 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 = StringIO.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
391 #---------------------------------------------------------
392 # Class that stores an updateable string (used in wizards)
393 #---------------------------------------------------------
394 class UpdateableStr(local):
396 def __init__(self, string=''):
400 return str(self.string)
403 return str(self.string)
405 def __nonzero__(self):
406 return bool(self.string)
409 class UpdateableDict(local):
410 '''Stores an updateable dict to use in wizards'''
412 def __init__(self, dict=None):
418 return str(self.dict)
421 return str(self.dict)
424 return self.dict.clear()
427 return self.dict.keys()
429 def __setitem__(self, i, y):
430 self.dict.__setitem__(i, y)
432 def __getitem__(self, i):
433 return self.dict.__getitem__(i)
436 return self.dict.copy()
439 return self.dict.iteritems()
442 return self.dict.iterkeys()
444 def itervalues(self):
445 return self.dict.itervalues()
447 def pop(self, k, d=None):
448 return self.dict.pop(k, d)
451 return self.dict.popitem()
453 def setdefault(self, k, d=None):
454 return self.dict.setdefault(k, d)
456 def update(self, E, **F):
457 return self.dict.update(E, F)
460 return self.dict.values()
462 def get(self, k, d=None):
463 return self.dict.get(k, d)
465 def has_key(self, k):
466 return self.dict.has_key(k)
469 return self.dict.items()
471 def __cmp__(self, y):
472 return self.dict.__cmp__(y)
474 def __contains__(self, k):
475 return self.dict.__contains__(k)
477 def __delitem__(self, y):
478 return self.dict.__delitem__(y)
481 return self.dict.__eq__(y)
484 return self.dict.__ge__(y)
486 def __getitem__(self, y):
487 return self.dict.__getitem__(y)
490 return self.dict.__gt__(y)
493 return self.dict.__hash__()
496 return self.dict.__iter__()
499 return self.dict.__le__(y)
502 return self.dict.__len__()
505 return self.dict.__lt__(y)
508 return self.dict.__ne__(y)
511 # Don't use ! Use res.currency.round()
512 class currency(float):
514 def __init__(self, value, accuracy=2, rounding=None):
516 rounding=10**-accuracy
517 self.rounding=rounding
518 self.accuracy=accuracy
520 def __new__(cls, value, accuracy=2, rounding=None):
521 return float.__new__(cls, round(value, accuracy))
524 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
525 # 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
540 def __init__(self, timeout=10000, skiparg=2):
541 self.timeout = timeout
544 def __call__(self, fn):
545 arg_names = inspect.getargspec(fn)[0][2:]
546 def cached_result(self2, cr=None, *args, **kwargs):
551 # Update named arguments with positional argument values
552 kwargs.update(dict(zip(arg_names, args)))
554 if isinstance(kwargs[k], (list, dict, set)):
555 kwargs[k] = tuple(kwargs[k])
556 elif not is_hashable(kwargs[k]):
557 kwargs[k] = repr(kwargs[k])
558 kwargs = kwargs.items()
561 # Work out key as a tuple of ('argname', value) pairs
562 key = (('dbname', cr.dbname),) + tuple(kwargs)
564 # Check cache and return cached value if possible
565 if key in self.cache:
566 (value, last_time) = self.cache[key]
567 mintime = time.time() - self.timeout
568 if self.timeout <= 0 or mintime <= last_time:
571 # Work out new value, cache it and return it
572 # FIXME Should copy() this value to avoid futur modifications of the cache ?
573 # FIXME What about exceptions ?
574 result = fn(self2,cr,**dict(kwargs))
576 self.cache[key] = (result, time.time())
581 return s.replace('&','&').replace('<','<').replace('>','>')
584 """This method is similar to the builtin `str` method, except
585 it will return Unicode string.
587 @param value: the value to convert
590 @return: unicode string
593 if isinstance(value, unicode):
596 if hasattr(value, '__unicode__'):
597 return unicode(value)
599 if not isinstance(value, str):
602 return unicode(value, 'utf-8')
607 'bg_BG': u'Bulgarian / български',
608 'ca_ES': u'Catalan / Català',
609 'cs_CZ': u'Czech / Čeština',
610 'de_DE': u'German / Deutsch',
611 'en_CA': u'English (CA)',
612 'en_EN': u'English (default)',
613 'en_GB': u'English (UK)',
614 'en_US': u'English (US)',
615 'es_AR': u'Spanish (AR) / Español (AR)',
616 'es_ES': u'Spanish / Español',
617 'et_ET': u'Estonian / Eesti keel',
618 'fr_BE': u'French (BE) / Français (BE)',
619 'fr_CH': u'French (CH) / Français (CH)',
620 'fr_FR': u'French / Français',
621 'hr_HR': u'Croatian / hrvatski jezik',
622 'hu_HU': u'Hungarian / Magyar',
623 'it_IT': u'Italian / Italiano',
624 'lt_LT': u'Lithuanian / Lietuvių kalba',
625 'nl_NL': u'Dutch / Nederlands',
626 'pt_BR': u'Portugese (BR) / português (BR)',
627 'pt_PT': u'Portugese / português',
628 'ro_RO': u'Romanian / limba română',
629 'ru_RU': u'Russian / русский язык',
630 'sl_SL': u'Slovenian / slovenščina',
631 'sv_SE': u'Swedish / svenska',
632 'uk_UK': u'Ukrainian / украї́нська мо́ва',
633 'zh_CN': u'Chinese (CN) / 简体中文' ,
634 'zh_TW': u'Chinese (TW) / 正體字',
638 def scan_languages():
640 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'))]
641 lang_dict = get_languages()
642 ret = [(lang, lang_dict.get(lang, lang)) for lang in file_list]
643 ret.sort(key=lambda k:k[1])
647 def get_user_companies(cr, user):
648 def _get_company_children(cr, ids):
651 cr.execute('SELECT id FROM res_company WHERE parent_id = any(array[%s])' %(','.join([str(x) for x in ids]),))
652 res=[x[0] for x in cr.fetchall()]
653 res.extend(_get_company_children(cr, res))
655 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,))
656 compids=[cr.fetchone()[0]]
657 compids.extend(_get_company_children(cr, compids))
662 Input number : account or invoice number
663 Output return: the same number completed with the recursive mod10
666 codec=[0,9,4,6,8,2,7,1,3,5]
672 report = codec[ (int(digit) + report) % 10 ]
673 return result + str((10 - report) % 10)
678 Return the size in a human readable format
682 units = ('bytes', 'Kb', 'Mb', 'Gb')
683 if isinstance(sz,basestring):
686 while s >= 1024 and i < len(units)-1:
689 return "%0.2f %s" % (s, units[i])
692 from functools import wraps
695 def wrapper(*args, **kwargs):
697 from pprint import pformat
699 vector = ['Call -> function: %r' % f]
700 for i, arg in enumerate(args):
701 vector.append(' arg %02d: %s' % (i, pformat(arg)))
702 for key, value in kwargs.items():
703 vector.append(' kwarg %10s: %s' % (key, pformat(value)))
706 res = f(*args, **kwargs)
708 vector.append(' result: %s' % pformat(res))
709 vector.append(' time delta: %s' % (time.time() - timeb4))
710 #netsvc.Logger().notifyChannel('logged', netsvc.LOG_DEBUG, '\n'.join(vector))
716 icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
717 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
718 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
719 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
720 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
721 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
722 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
723 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
724 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
725 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
726 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
727 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
728 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
729 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
730 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
731 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
732 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
733 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
734 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
735 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
736 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
737 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
738 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
739 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
740 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
741 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
746 if __name__ == '__main__':
752 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: