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 # initialize a database with base/base.sql
54 f = addons.get_module_resource('base', 'base.sql')
55 for line in file(f).read().split(';'):
56 if (len(line)>0) and (not line.isspace()):
60 for i in addons.get_modules():
61 terp_file = addons.get_module_resource(i, '__terp__.py')
62 mod_path = addons.get_module_path(i)
64 if os.path.isfile(terp_file) and not os.path.isfile(mod_path+'.zip'):
65 info = eval(file(terp_file).read())
66 elif zipfile.is_zipfile(mod_path+'.zip'):
67 zfile = zipfile.ZipFile(mod_path+'.zip')
68 i = os.path.splitext(i)[0]
69 info = eval(zfile.read(os.path.join(i, '__terp__.py')))
71 categs = info.get('category', 'Uncategorized').split('/')
75 cr.execute('select id \
76 from ir_module_category \
77 where name=%s and parent_id=%d', (categs[0], p_id))
79 cr.execute('select id \
80 from ir_module_category \
81 where name=%s and parent_id is NULL', (categs[0],))
84 cr.execute('select nextval(\'ir_module_category_id_seq\')')
85 c_id = cr.fetchone()[0]
86 cr.execute('insert into ir_module_category \
87 (id, name, parent_id) \
88 values (%d, %s, %d)', (c_id, categs[0], p_id))
94 active = info.get('active', False)
95 installable = info.get('installable', True)
100 state = 'uninstalled'
102 state = 'uninstallable'
103 cr.execute('select nextval(\'ir_module_module_id_seq\')')
104 id = cr.fetchone()[0]
105 cr.execute('insert into ir_module_module \
106 (id, author, latest_version, website, name, shortdesc, description, \
107 category_id, state) \
108 values (%d, %s, %s, %s, %s, %s, %s, %d, %s)', (
109 id, info.get('author', ''),
110 release.version.rsplit('.', 1)[0] + '.' + info.get('version', ''),
111 info.get('website', ''), i, info.get('name', False),
112 info.get('description', ''), p_id, state))
113 dependencies = info.get('depends', [])
114 for d in dependencies:
115 cr.execute('insert into ir_module_module_dependency \
116 (module_id,name) values (%s, %s)', (id, d))
119 def find_in_path(name):
124 path = [dir for dir in os.environ['PATH'].split(sep)
125 if os.path.isdir(dir)]
127 val = os.path.join(dir, name)
128 if os.path.isfile(val) or os.path.islink(val):
132 def find_pg_tool(name):
133 if config['pg_path'] and config['pg_path'] != 'None':
134 return os.path.join(config['pg_path'], name)
136 return find_in_path(name)
138 def exec_pg_command(name, *args):
139 prog = find_pg_tool(name)
141 raise Exception('Couldn\'t find %s' % name)
142 args2 = (os.path.basename(prog),) + args
143 return os.spawnv(os.P_WAIT, prog, args2)
145 def exec_pg_command_pipe(name, *args):
146 prog = find_pg_tool(name)
148 raise Exception('Couldn\'t find %s' % name)
150 cmd = '"' + prog + '" ' + ' '.join(args)
152 cmd = prog + ' ' + ' '.join(args)
153 return os.popen2(cmd, 'b')
155 def exec_command_pipe(name, *args):
156 prog = find_in_path(name)
158 raise Exception('Couldn\'t find %s' % name)
160 cmd = '"'+prog+'" '+' '.join(args)
162 cmd = prog+' '+' '.join(args)
163 return os.popen2(cmd, 'b')
165 #----------------------------------------------------------
167 #----------------------------------------------------------
168 #file_path_root = os.getcwd()
169 #file_path_addons = os.path.join(file_path_root, 'addons')
171 def file_open(name, mode="r", subdir='addons', pathinfo=False):
172 """Open a file from the Tiny ERP root, using a subdir folder.
174 >>> file_open('hr/report/timesheer.xsl')
175 >>> file_open('addons/hr/report/timesheet.xsl')
176 >>> file_open('../../base/report/rml_template.xsl', subdir='addons/hr/report', pathinfo=True)
178 @param name: name of the file
179 @param mode: file open mode
180 @param subdir: subdirectory
181 @param pathinfo: if True returns tupple (fileobject, filepath)
183 @return: fileobject if pathinfo is False else (fileobject, filepath)
186 adp = os.path.normcase(os.path.abspath(config['addons_path']))
187 rtp = os.path.normcase(os.path.abspath(config['root_path']))
189 if name.replace(os.path.sep, '/').startswith('addons/'):
193 # First try to locate in addons_path
196 if subdir2.replace(os.path.sep, '/').startswith('addons/'):
197 subdir2 = subdir2[7:]
199 subdir2 = (subdir2 != 'addons' or None) and subdir2
203 fn = os.path.join(adp, subdir2, name)
205 fn = os.path.join(adp, name)
206 fn = os.path.normpath(fn)
207 fo = file_open(fn, mode=mode, subdir=None, pathinfo=pathinfo)
215 name = os.path.join(rtp, subdir, name)
217 name = os.path.join(rtp, name)
219 name = os.path.normpath(name)
221 # Check for a zipfile in the path
226 head, tail = os.path.split(head)
230 zipname = os.path.join(tail, zipname)
233 if zipfile.is_zipfile(head+'.zip'):
235 zfile = zipfile.ZipFile(head+'.zip')
237 fo = StringIO.StringIO(zfile.read(os.path.join(
238 os.path.basename(head), zipname).replace(
245 name2 = os.path.normpath(os.path.join(head + '.zip', zipname))
247 for i in (name2, name):
248 if i and os.path.isfile(i):
254 raise IOError, 'File not found : '+str(name)
257 #----------------------------------------------------------
259 #----------------------------------------------------------
261 """Flatten a list of elements into a uniqu list
262 Author: Christophe Simonis (christophe@tinyerp.com)
271 >>> flatten( [[], [[]]] )
273 >>> flatten( [[['a','b'], 'c'], 'd', ['e', [], 'f']] )
274 ['a', 'b', 'c', 'd', 'e', 'f']
275 >>> t = (1,2,(3,), [4, 5, [6, [7], (8, 9), ([10, 11, (12, 13)]), [14, [], (15,)], []]])
277 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
281 return hasattr(x, "__iter__")
286 map(r.append, flatten(e))
293 #----------------------------------------------------------
295 #----------------------------------------------------------
296 def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, on_error=False, reply_to=False, tinycrm=False):
303 from email.MIMEText import MIMEText
304 from email.MIMEMultipart import MIMEMultipart
305 from email.Header import Header
306 from email.Utils import formatdate, COMMASPACE
308 msg = MIMEText(body or '', _charset='utf-8')
309 msg['Subject'] = Header(subject.decode('utf8'), 'utf-8')
310 msg['From'] = email_from
313 msg['Reply-To'] = msg['From']+', '+reply_to
314 msg['To'] = COMMASPACE.join(email_to)
316 msg['Cc'] = COMMASPACE.join(email_cc)
318 msg['Bcc'] = COMMASPACE.join(email_bcc)
319 msg['Date'] = formatdate(localtime=True)
321 msg['Message-Id'] = '<'+str(time.time())+'-tinycrm-'+str(tinycrm)+'@'+socket.gethostname()+'>'
324 s.connect(config['smtp_server'])
325 if config['smtp_user'] or config['smtp_password']:
326 s.login(config['smtp_user'], config['smtp_password'])
327 s.sendmail(email_from, flatten([email_to, email_cc, email_bcc]), msg.as_string())
331 logging.getLogger().error(str(e))
335 #----------------------------------------------------------
337 #----------------------------------------------------------
338 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):
347 from email.MIMEText import MIMEText
348 from email.MIMEBase import MIMEBase
349 from email.MIMEMultipart import MIMEMultipart
350 from email.Header import Header
351 from email.Utils import formatdate, COMMASPACE
352 from email import Encoders
354 msg = MIMEMultipart()
356 msg['Subject'] = Header(subject.decode('utf8'), 'utf-8')
357 msg['From'] = email_from
360 msg['Reply-To'] = reply_to
361 msg['To'] = COMMASPACE.join(email_to)
363 msg['Cc'] = COMMASPACE.join(email_cc)
365 msg['Bcc'] = COMMASPACE.join(email_bcc)
367 msg['Message-Id'] = '<'+str(time.time())+'-tinycrm-'+str(tinycrm)+'@'+socket.gethostname()+'>'
368 msg['Date'] = formatdate(localtime=True)
369 msg.attach( MIMEText(body or '', _charset='utf-8') )
370 for (fname,fcontent) in attach:
371 part = MIMEBase('application', "octet-stream")
372 part.set_payload( fcontent )
373 Encoders.encode_base64(part)
374 part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
378 s.connect(config['smtp_server'])
379 if config['smtp_user'] or config['smtp_password']:
380 s.login(config['smtp_user'], config['smtp_password'])
381 s.sendmail(email_from, flatten([email_to, email_cc, email_bcc]), msg.as_string())
385 logging.getLogger().error(str(e))
388 #----------------------------------------------------------
390 #----------------------------------------------------------
391 # text must be latin-1 encoded
392 def sms_send(user, password, api_id, text, to):
394 params = urllib.urlencode({'user': user, 'password': password, 'api_id': api_id, 'text': text, 'to':to})
395 #f = urllib.urlopen("http://api.clickatell.com/http/sendmsg", params)
396 f = urllib.urlopen("http://196.7.150.220/http/sendmsg", params)
400 #---------------------------------------------------------
401 # Class that stores an updateable string (used in wizards)
402 #---------------------------------------------------------
403 class UpdateableStr(local):
405 def __init__(self, string=''):
409 return str(self.string)
412 return str(self.string)
414 def __nonzero__(self):
415 return bool(self.string)
418 class UpdateableDict(local):
419 '''Stores an updateable dict to use in wizards'''
421 def __init__(self, dict=None):
427 return str(self.dict)
430 return str(self.dict)
433 return self.dict.clear()
436 return self.dict.keys()
438 def __setitem__(self, i, y):
439 self.dict.__setitem__(i, y)
441 def __getitem__(self, i):
442 return self.dict.__getitem__(i)
445 return self.dict.copy()
448 return self.dict.iteritems()
451 return self.dict.iterkeys()
453 def itervalues(self):
454 return self.dict.itervalues()
456 def pop(self, k, d=None):
457 return self.dict.pop(k, d)
460 return self.dict.popitem()
462 def setdefault(self, k, d=None):
463 return self.dict.setdefault(k, d)
465 def update(self, E, **F):
466 return self.dict.update(E, F)
469 return self.dict.values()
471 def get(self, k, d=None):
472 return self.dict.get(k, d)
474 def has_key(self, k):
475 return self.dict.has_key(k)
478 return self.dict.items()
480 def __cmp__(self, y):
481 return self.dict.__cmp__(y)
483 def __contains__(self, k):
484 return self.dict.__contains__(k)
486 def __delitem__(self, y):
487 return self.dict.__delitem__(y)
490 return self.dict.__eq__(y)
493 return self.dict.__ge__(y)
495 def __getitem__(self, y):
496 return self.dict.__getitem__(y)
499 return self.dict.__gt__(y)
502 return self.dict.__hash__()
505 return self.dict.__iter__()
508 return self.dict.__le__(y)
511 return self.dict.__len__()
514 return self.dict.__lt__(y)
517 return self.dict.__ne__(y)
520 # Don't use ! Use res.currency.round()
521 class currency(float):
523 def __init__(self, value, accuracy=2, rounding=None):
525 rounding=10**-accuracy
526 self.rounding=rounding
527 self.accuracy=accuracy
529 def __new__(cls, value, accuracy=2, rounding=None):
530 return float.__new__(cls, round(value, accuracy))
533 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
534 # return str(display_value)
538 # Use it as a decorator of the function you plan to cache
539 # Timeout: 0 = no timeout, otherwise in seconds
542 def __init__(self, timeout=10000, skiparg=2):
543 self.timeout = timeout
546 def __call__(self, fn):
547 arg_names = inspect.getargspec(fn)[0][2:]
548 def cached_result(self2, cr=None, *args, **kwargs):
553 # Update named arguments with positional argument values
554 kwargs.update(dict(zip(arg_names, args)))
555 kwargs = kwargs.items()
558 # Work out key as a tuple of ('argname', value) pairs
559 key = (('dbname', cr.dbname),) + tuple(kwargs)
561 # Check cache and return cached value if possible
562 if key in self.cache:
563 (value, last_time) = self.cache[key]
564 mintime = time.time() - self.timeout
565 if self.timeout <= 0 or mintime <= last_time:
568 # Work out new value, cache it and return it
569 # Should copy() this value to avoid futur modf of the cacle ?
570 result = fn(self2,cr,**dict(kwargs))
572 self.cache[key] = (result, time.time())
577 return s.replace('&','&').replace('<','<').replace('>','>')
581 'zh_CN': 'Chinese (CN)',
582 'zh_TW': 'Chinese (TW)',
585 'es_AR': 'Español (Argentina)',
586 'es_ES': 'Español (España)',
588 'fr_CH': 'Français (Suisse)',
589 'en_EN': 'English (default)',
590 'hu_HU': 'Hungarian',
592 'pt_BR': 'Portugese (Brasil)',
593 'pt_PT': 'Portugese (Portugal)',
594 'nl_NL': 'Nederlands',
601 def scan_languages():
603 file_list = [os.path.splitext(os.path.basename(f))[0] for f in glob.glob(os.path.join(config['root_path'], 'i18n', '*.csv'))]
604 lang_dict = get_languages()
605 return [(lang, lang_dict.get(lang, lang)) for lang in file_list]
608 def get_user_companies(cr, user):
609 def _get_company_children(cr, ids):
612 cr.execute('SELECT id FROM res_company WHERE parent_id = any(array[%s])' %(','.join([str(x) for x in ids]),))
613 res=[x[0] for x in cr.fetchall()]
614 res.extend(_get_company_children(cr, res))
616 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,))
617 compids=[cr.fetchone()[0]]
618 compids.extend(_get_company_children(cr, compids))
623 Input number : account or invoice number
624 Output return: the same number completed with the recursive mod10
627 codec=[0,9,4,6,8,2,7,1,3,5]
633 report = codec[ (int(digit) + report) % 10 ]
634 return result + str((10 - report) % 10)
639 if __name__ == '__main__':