1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # Copyright (c) 2004-2008 Tiny SPRL (http://tiny.be) All Rights Reserved.
8 # WARNING: This program as such is intended to be used by professional
9 # programmers who take the whole responsability of assessing all potential
10 # consequences resulting from its eventual inadequacies and bugs
11 # End users who are looking for a ready-to-use solution with commercial
12 # garantees and support are strongly adviced to contract a Free Software
15 # This program is Free Software; you can redistribute it and/or
16 # modify it under the terms of the GNU General Public License
17 # as published by the Free Software Foundation; either version 2
18 # of the License, or (at your option) any later version.
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 ###############################################################################
31 Miscelleanous tools used by tiny ERP.
39 from config import config
46 if sys.version_info[:2] < (2, 4):
47 from threadinglocal import local
49 from threading import local
51 from itertools import izip
53 # initialize a database with base/base.sql
56 f = addons.get_module_resource('base', 'base.sql')
57 for line in file(f).read().split(';'):
58 if (len(line)>0) and (not line.isspace()):
62 for i in addons.get_modules():
63 terp_file = addons.get_module_resource(i, '__terp__.py')
64 mod_path = addons.get_module_path(i)
66 if os.path.isfile(terp_file) and not os.path.isfile(mod_path+'.zip'):
67 info = eval(file(terp_file).read())
68 elif zipfile.is_zipfile(mod_path+'.zip'):
69 zfile = zipfile.ZipFile(mod_path+'.zip')
70 i = os.path.splitext(i)[0]
71 info = eval(zfile.read(os.path.join(i, '__terp__.py')))
73 categs = info.get('category', 'Uncategorized').split('/')
77 cr.execute('select id \
78 from ir_module_category \
79 where name=%s and parent_id=%d', (categs[0], p_id))
81 cr.execute('select id \
82 from ir_module_category \
83 where name=%s and parent_id is NULL', (categs[0],))
86 cr.execute('select nextval(\'ir_module_category_id_seq\')')
87 c_id = cr.fetchone()[0]
88 cr.execute('insert into ir_module_category \
89 (id, name, parent_id) \
90 values (%d, %s, %d)', (c_id, categs[0], p_id))
96 active = info.get('active', False)
97 installable = info.get('installable', True)
102 state = 'uninstalled'
104 state = 'uninstallable'
105 cr.execute('select nextval(\'ir_module_module_id_seq\')')
106 id = cr.fetchone()[0]
107 cr.execute('insert into ir_module_module \
108 (id, author, latest_version, website, name, shortdesc, description, \
109 category_id, state) \
110 values (%d, %s, %s, %s, %s, %s, %s, %d, %s)', (
111 id, info.get('author', ''),
112 release.version.rsplit('.', 1)[0] + '.' + info.get('version', ''),
113 info.get('website', ''), i, info.get('name', False),
114 info.get('description', ''), p_id, state))
115 dependencies = info.get('depends', [])
116 for d in dependencies:
117 cr.execute('insert into ir_module_module_dependency \
118 (module_id,name) values (%s, %s)', (id, d))
121 def find_in_path(name):
126 path = [dir for dir in os.environ['PATH'].split(sep)
127 if os.path.isdir(dir)]
129 val = os.path.join(dir, name)
130 if os.path.isfile(val) or os.path.islink(val):
134 def find_pg_tool(name):
135 if config['pg_path'] and config['pg_path'] != 'None':
136 return os.path.join(config['pg_path'], name)
138 return find_in_path(name)
140 def exec_pg_command(name, *args):
141 prog = find_pg_tool(name)
143 raise Exception('Couldn\'t find %s' % name)
144 args2 = (os.path.basename(prog),) + args
145 return os.spawnv(os.P_WAIT, prog, args2)
147 def exec_pg_command_pipe(name, *args):
148 prog = find_pg_tool(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 def exec_command_pipe(name, *args):
158 prog = find_in_path(name)
160 raise Exception('Couldn\'t find %s' % name)
162 cmd = '"'+prog+'" '+' '.join(args)
164 cmd = prog+' '+' '.join(args)
165 return os.popen2(cmd, 'b')
167 #----------------------------------------------------------
169 #----------------------------------------------------------
170 #file_path_root = os.getcwd()
171 #file_path_addons = os.path.join(file_path_root, 'addons')
173 def file_open(name, mode="r", subdir='addons', pathinfo=False):
174 """Open a file from the Tiny ERP root, using a subdir folder.
176 >>> file_open('hr/report/timesheer.xsl')
177 >>> file_open('addons/hr/report/timesheet.xsl')
178 >>> file_open('../../base/report/rml_template.xsl', subdir='addons/hr/report', pathinfo=True)
180 @param name: name of the file
181 @param mode: file open mode
182 @param subdir: subdirectory
183 @param pathinfo: if True returns tupple (fileobject, filepath)
185 @return: fileobject if pathinfo is False else (fileobject, filepath)
188 adp = os.path.normcase(os.path.abspath(config['addons_path']))
189 rtp = os.path.normcase(os.path.abspath(config['root_path']))
191 if name.replace(os.path.sep, '/').startswith('addons/'):
195 # First try to locate in addons_path
198 if subdir2.replace(os.path.sep, '/').startswith('addons/'):
199 subdir2 = subdir2[7:]
201 subdir2 = (subdir2 != 'addons' or None) and subdir2
205 fn = os.path.join(adp, subdir2, name)
207 fn = os.path.join(adp, name)
208 fn = os.path.normpath(fn)
209 fo = file_open(fn, mode=mode, subdir=None, pathinfo=pathinfo)
217 name = os.path.join(rtp, subdir, name)
219 name = os.path.join(rtp, name)
221 name = os.path.normpath(name)
223 # Check for a zipfile in the path
228 head, tail = os.path.split(head)
232 zipname = os.path.join(tail, zipname)
235 if zipfile.is_zipfile(head+'.zip'):
237 zfile = zipfile.ZipFile(head+'.zip')
239 fo = StringIO.StringIO(zfile.read(os.path.join(
240 os.path.basename(head), zipname).replace(
247 name2 = os.path.normpath(os.path.join(head + '.zip', zipname))
249 for i in (name2, name):
250 if i and os.path.isfile(i):
256 raise IOError, 'File not found : '+str(name)
259 def oswalksymlinks(top, topdown=True, onerror=None):
261 same as os.walk but follow symlinks
262 attention: all symlinks are walked before all normals directories
264 for dirpath, dirnames, filenames in os.walk(top, topdown, onerror):
266 yield dirpath, dirnames, filenames
268 symlinks = filter(lambda dirname: os.path.islink(os.path.join(dirpath, dirname)), dirnames)
270 for x in oswalksymlinks(os.path.join(dirpath, s), topdown, onerror):
274 yield dirpath, dirnames, filenames
276 #----------------------------------------------------------
278 #----------------------------------------------------------
280 """Flatten a list of elements into a uniqu list
281 Author: Christophe Simonis (christophe@tinyerp.com)
290 >>> flatten( [[], [[]]] )
292 >>> flatten( [[['a','b'], 'c'], 'd', ['e', [], 'f']] )
293 ['a', 'b', 'c', 'd', 'e', 'f']
294 >>> t = (1,2,(3,), [4, 5, [6, [7], (8, 9), ([10, 11, (12, 13)]), [14, [], (15,)], []]])
296 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
300 return hasattr(x, "__iter__")
305 map(r.append, flatten(e))
310 def reverse_enumerate(l):
311 """Like enumerate but in the other sens
312 >>> a = ['a', 'b', 'c']
313 >>> it = reverse_enumerate(a)
321 Traceback (most recent call last):
322 File "<stdin>", line 1, in <module>
325 return izip(xrange(len(l)-1, -1, -1), reversed(l))
327 #----------------------------------------------------------
329 #----------------------------------------------------------
330 def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, on_error=False, reply_to=False, tinycrm=False):
337 from email.MIMEText import MIMEText
338 from email.MIMEMultipart import MIMEMultipart
339 from email.Header import Header
340 from email.Utils import formatdate, COMMASPACE
342 msg = MIMEText(body or '', _charset='utf-8')
343 msg['Subject'] = Header(subject.decode('utf8'), 'utf-8')
344 msg['From'] = email_from
347 msg['Reply-To'] = msg['From']+', '+reply_to
348 msg['To'] = COMMASPACE.join(email_to)
350 msg['Cc'] = COMMASPACE.join(email_cc)
352 msg['Bcc'] = COMMASPACE.join(email_bcc)
353 msg['Date'] = formatdate(localtime=True)
355 msg['Message-Id'] = '<'+str(time.time())+'-tinycrm-'+str(tinycrm)+'@'+socket.gethostname()+'>'
366 s.connect(config['smtp_server'], config['smtp_port'])
367 if config['smtp_user'] or config['smtp_password']:
368 s.login(config['smtp_user'], config['smtp_password'])
369 s.sendmail(email_from, flatten([email_to, email_cc, email_bcc]), msg.as_string())
373 logging.getLogger().error(str(e))
377 #----------------------------------------------------------
379 #----------------------------------------------------------
380 def email_send_attach(email_from, email_to, subject, body, email_cc=None, email_bcc=None, on_error=False, reply_to=False, attach=None, tinycrm=False, ssl=False, debug=False):
389 from email.MIMEText import MIMEText
390 from email.MIMEBase import MIMEBase
391 from email.MIMEMultipart import MIMEMultipart
392 from email.Header import Header
393 from email.Utils import formatdate, COMMASPACE
394 from email import Encoders
396 msg = MIMEMultipart()
399 ssl = config['smtp_ssl']
401 msg['Subject'] = Header(subject.decode('utf8'), 'utf-8')
402 msg['From'] = email_from
405 msg['Reply-To'] = reply_to
406 msg['To'] = COMMASPACE.join(email_to)
408 msg['Cc'] = COMMASPACE.join(email_cc)
410 msg['Bcc'] = COMMASPACE.join(email_bcc)
412 msg['Message-Id'] = '<'+str(time.time())+'-tinycrm-'+str(tinycrm)+'@'+socket.gethostname()+'>'
413 msg['Date'] = formatdate(localtime=True)
414 msg.attach( MIMEText(body or '', _charset='utf-8', _subtype="html"))
415 for (fname,fcontent) in attach:
416 part = MIMEBase('application', "octet-stream")
417 part.set_payload( fcontent )
418 Encoders.encode_base64(part)
419 part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
431 s.connect(config['smtp_server'], config['smtp_port'])
432 if config['smtp_user'] or config['smtp_password']:
433 s.login(config['smtp_user'], config['smtp_password'])
434 s.sendmail(email_from, flatten([email_to, email_cc, email_bcc]), msg.as_string())
438 logging.getLogger().error(str(e))
443 #----------------------------------------------------------
445 #----------------------------------------------------------
446 # text must be latin-1 encoded
447 def sms_send(user, password, api_id, text, to):
449 params = urllib.urlencode({'user': user, 'password': password, 'api_id': api_id, 'text': text, 'to':to})
450 #f = urllib.urlopen("http://api.clickatell.com/http/sendmsg", params)
451 f = urllib.urlopen("http://196.7.150.220/http/sendmsg", params)
455 #---------------------------------------------------------
456 # Class that stores an updateable string (used in wizards)
457 #---------------------------------------------------------
458 class UpdateableStr(local):
460 def __init__(self, string=''):
464 return str(self.string)
467 return str(self.string)
469 def __nonzero__(self):
470 return bool(self.string)
473 class UpdateableDict(local):
474 '''Stores an updateable dict to use in wizards'''
476 def __init__(self, dict=None):
482 return str(self.dict)
485 return str(self.dict)
488 return self.dict.clear()
491 return self.dict.keys()
493 def __setitem__(self, i, y):
494 self.dict.__setitem__(i, y)
496 def __getitem__(self, i):
497 return self.dict.__getitem__(i)
500 return self.dict.copy()
503 return self.dict.iteritems()
506 return self.dict.iterkeys()
508 def itervalues(self):
509 return self.dict.itervalues()
511 def pop(self, k, d=None):
512 return self.dict.pop(k, d)
515 return self.dict.popitem()
517 def setdefault(self, k, d=None):
518 return self.dict.setdefault(k, d)
520 def update(self, E, **F):
521 return self.dict.update(E, F)
524 return self.dict.values()
526 def get(self, k, d=None):
527 return self.dict.get(k, d)
529 def has_key(self, k):
530 return self.dict.has_key(k)
533 return self.dict.items()
535 def __cmp__(self, y):
536 return self.dict.__cmp__(y)
538 def __contains__(self, k):
539 return self.dict.__contains__(k)
541 def __delitem__(self, y):
542 return self.dict.__delitem__(y)
545 return self.dict.__eq__(y)
548 return self.dict.__ge__(y)
550 def __getitem__(self, y):
551 return self.dict.__getitem__(y)
554 return self.dict.__gt__(y)
557 return self.dict.__hash__()
560 return self.dict.__iter__()
563 return self.dict.__le__(y)
566 return self.dict.__len__()
569 return self.dict.__lt__(y)
572 return self.dict.__ne__(y)
575 # Don't use ! Use res.currency.round()
576 class currency(float):
578 def __init__(self, value, accuracy=2, rounding=None):
580 rounding=10**-accuracy
581 self.rounding=rounding
582 self.accuracy=accuracy
584 def __new__(cls, value, accuracy=2, rounding=None):
585 return float.__new__(cls, round(value, accuracy))
588 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
589 # return str(display_value)
593 # Use it as a decorator of the function you plan to cache
594 # Timeout: 0 = no timeout, otherwise in seconds
597 def __init__(self, timeout=10000, skiparg=2):
598 self.timeout = timeout
601 def __call__(self, fn):
602 arg_names = inspect.getargspec(fn)[0][2:]
603 def cached_result(self2, cr=None, *args, **kwargs):
608 # Update named arguments with positional argument values
609 kwargs.update(dict(zip(arg_names, args)))
610 kwargs = kwargs.items()
613 # Work out key as a tuple of ('argname', value) pairs
614 key = (('dbname', cr.dbname),) + tuple(kwargs)
616 # Check cache and return cached value if possible
617 if key in self.cache:
618 (value, last_time) = self.cache[key]
619 mintime = time.time() - self.timeout
620 if self.timeout <= 0 or mintime <= last_time:
623 # Work out new value, cache it and return it
624 # Should copy() this value to avoid futur modf of the cacle ?
625 result = fn(self2,cr,**dict(kwargs))
627 self.cache[key] = (result, time.time())
632 return s.replace('&','&').replace('<','<').replace('>','>')
636 'zh_CN': 'Chinese (CN)',
637 'zh_TW': 'Chinese (TW)',
640 'es_AR': 'Español (Argentina)',
641 'es_ES': 'Español (España)',
643 'fr_CH': 'Français (Suisse)',
644 'en_EN': 'English (default)',
645 'hu_HU': 'Hungarian',
647 'pt_BR': 'Portugese (Brasil)',
648 'pt_PT': 'Portugese (Portugal)',
649 'nl_NL': 'Nederlands',
656 def scan_languages():
658 file_list = [os.path.splitext(os.path.basename(f))[0] for f in glob.glob(os.path.join(config['addons_path'], 'base', 'i18n', '*.po'))]
659 lang_dict = get_languages()
660 return [(lang, lang_dict.get(lang, lang)) for lang in file_list]
663 def get_user_companies(cr, user):
664 def _get_company_children(cr, ids):
667 cr.execute('SELECT id FROM res_company WHERE parent_id = any(array[%s])' %(','.join([str(x) for x in ids]),))
668 res=[x[0] for x in cr.fetchall()]
669 res.extend(_get_company_children(cr, res))
671 cr.execute('SELECT comp.id FROM res_company AS comp, res_users AS u WHERE u.id = %d AND comp.id = u.company_id' % (user,))
672 compids=[cr.fetchone()[0]]
673 compids.extend(_get_company_children(cr, compids))
678 Input number : account or invoice number
679 Output return: the same number completed with the recursive mod10
682 codec=[0,9,4,6,8,2,7,1,3,5]
688 report = codec[ (int(digit) + report) % 10 ]
689 return result + str((10 - report) % 10)
694 Return the size in a human readable format
698 units = ('bytes', 'Kb', 'Mb', 'Gb')
700 while s >= 1024 and i < len(units)-1:
703 return "%0.2f %s" % (s, units[i])
706 def log(f, res, *args, **kwargs):
707 vector = ['Call -> function: %s' % f]
708 for i, arg in enumerate(args):
709 vector.append( ' arg %02d: %r' % ( i, arg ) )
710 for key, value in kwargs.items():
711 vector.append( ' kwarg %10s: %r' % ( key, value ) )
712 vector.append( ' result: %r' % res )
713 print "\n".join(vector)
716 def wrapper(*args, **kwargs):
717 res = f(*args, **kwargs)
718 log(f, res, *args, **kwargs)
723 def wrapper(*args, **kwargs):
727 res = f(*args, **kwargs)
730 log(f, res, *args, **kwargs)
731 print " time delta: %s" % (time.time() - now)
735 return { "pre" : pre_logged, "post" : post_logged}[when]
737 raise ValueError(e), "must to be 'pre' or 'post'"
739 if __name__ == '__main__':
745 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: