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 #----------------------------------------------------------
261 #----------------------------------------------------------
263 """Flatten a list of elements into a uniqu list
264 Author: Christophe Simonis (christophe@tinyerp.com)
273 >>> flatten( [[], [[]]] )
275 >>> flatten( [[['a','b'], 'c'], 'd', ['e', [], 'f']] )
276 ['a', 'b', 'c', 'd', 'e', 'f']
277 >>> t = (1,2,(3,), [4, 5, [6, [7], (8, 9), ([10, 11, (12, 13)]), [14, [], (15,)], []]])
279 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
283 return hasattr(x, "__iter__")
288 map(r.append, flatten(e))
293 def reverse_enumerate(l):
294 """Like enumerate but in the other sens
295 >>> a = ['a', 'b', 'c']
296 >>> it = reverse_enumerate(a)
304 Traceback (most recent call last):
305 File "<stdin>", line 1, in <module>
308 return izip(xrange(len(l)-1, -1, -1), reversed(l))
310 #----------------------------------------------------------
312 #----------------------------------------------------------
313 def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, on_error=False, reply_to=False, tinycrm=False):
320 from email.MIMEText import MIMEText
321 from email.MIMEMultipart import MIMEMultipart
322 from email.Header import Header
323 from email.Utils import formatdate, COMMASPACE
325 msg = MIMEText(body or '', _charset='utf-8')
326 msg['Subject'] = Header(subject.decode('utf8'), 'utf-8')
327 msg['From'] = email_from
330 msg['Reply-To'] = msg['From']+', '+reply_to
331 msg['To'] = COMMASPACE.join(email_to)
333 msg['Cc'] = COMMASPACE.join(email_cc)
335 msg['Bcc'] = COMMASPACE.join(email_bcc)
336 msg['Date'] = formatdate(localtime=True)
338 msg['Message-Id'] = '<'+str(time.time())+'-tinycrm-'+str(tinycrm)+'@'+socket.gethostname()+'>'
349 s.connect(config['smtp_server'], config['smtp_port'])
350 if config['smtp_user'] or config['smtp_password']:
351 s.login(config['smtp_user'], config['smtp_password'])
352 s.sendmail(email_from, flatten([email_to, email_cc, email_bcc]), msg.as_string())
356 logging.getLogger().error(str(e))
360 #----------------------------------------------------------
362 #----------------------------------------------------------
363 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):
372 from email.MIMEText import MIMEText
373 from email.MIMEBase import MIMEBase
374 from email.MIMEMultipart import MIMEMultipart
375 from email.Header import Header
376 from email.Utils import formatdate, COMMASPACE
377 from email import Encoders
379 msg = MIMEMultipart()
382 ssl = config['smtp_ssl']
384 msg['Subject'] = Header(subject.decode('utf8'), 'utf-8')
385 msg['From'] = email_from
388 msg['Reply-To'] = reply_to
389 msg['To'] = COMMASPACE.join(email_to)
391 msg['Cc'] = COMMASPACE.join(email_cc)
393 msg['Bcc'] = COMMASPACE.join(email_bcc)
395 msg['Message-Id'] = '<'+str(time.time())+'-tinycrm-'+str(tinycrm)+'@'+socket.gethostname()+'>'
396 msg['Date'] = formatdate(localtime=True)
397 msg.attach( MIMEText(body or '', _charset='utf-8') )
398 for (fname,fcontent) in attach:
399 part = MIMEBase('application', "octet-stream")
400 part.set_payload( fcontent )
401 Encoders.encode_base64(part)
402 part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
414 s.connect(config['smtp_server'], config['smtp_port'])
415 if config['smtp_user'] or config['smtp_password']:
416 s.login(config['smtp_user'], config['smtp_password'])
417 s.sendmail(email_from, flatten([email_to, email_cc, email_bcc]), msg.as_string())
421 logging.getLogger().error(str(e))
426 #----------------------------------------------------------
428 #----------------------------------------------------------
429 # text must be latin-1 encoded
430 def sms_send(user, password, api_id, text, to):
432 params = urllib.urlencode({'user': user, 'password': password, 'api_id': api_id, 'text': text, 'to':to})
433 #f = urllib.urlopen("http://api.clickatell.com/http/sendmsg", params)
434 f = urllib.urlopen("http://196.7.150.220/http/sendmsg", params)
438 #---------------------------------------------------------
439 # Class that stores an updateable string (used in wizards)
440 #---------------------------------------------------------
441 class UpdateableStr(local):
443 def __init__(self, string=''):
447 return str(self.string)
450 return str(self.string)
452 def __nonzero__(self):
453 return bool(self.string)
456 class UpdateableDict(local):
457 '''Stores an updateable dict to use in wizards'''
459 def __init__(self, dict=None):
465 return str(self.dict)
468 return str(self.dict)
471 return self.dict.clear()
474 return self.dict.keys()
476 def __setitem__(self, i, y):
477 self.dict.__setitem__(i, y)
479 def __getitem__(self, i):
480 return self.dict.__getitem__(i)
483 return self.dict.copy()
486 return self.dict.iteritems()
489 return self.dict.iterkeys()
491 def itervalues(self):
492 return self.dict.itervalues()
494 def pop(self, k, d=None):
495 return self.dict.pop(k, d)
498 return self.dict.popitem()
500 def setdefault(self, k, d=None):
501 return self.dict.setdefault(k, d)
503 def update(self, E, **F):
504 return self.dict.update(E, F)
507 return self.dict.values()
509 def get(self, k, d=None):
510 return self.dict.get(k, d)
512 def has_key(self, k):
513 return self.dict.has_key(k)
516 return self.dict.items()
518 def __cmp__(self, y):
519 return self.dict.__cmp__(y)
521 def __contains__(self, k):
522 return self.dict.__contains__(k)
524 def __delitem__(self, y):
525 return self.dict.__delitem__(y)
528 return self.dict.__eq__(y)
531 return self.dict.__ge__(y)
533 def __getitem__(self, y):
534 return self.dict.__getitem__(y)
537 return self.dict.__gt__(y)
540 return self.dict.__hash__()
543 return self.dict.__iter__()
546 return self.dict.__le__(y)
549 return self.dict.__len__()
552 return self.dict.__lt__(y)
555 return self.dict.__ne__(y)
558 # Don't use ! Use res.currency.round()
559 class currency(float):
561 def __init__(self, value, accuracy=2, rounding=None):
563 rounding=10**-accuracy
564 self.rounding=rounding
565 self.accuracy=accuracy
567 def __new__(cls, value, accuracy=2, rounding=None):
568 return float.__new__(cls, round(value, accuracy))
571 # display_value = int(self*(10**(-self.accuracy))/self.rounding)*self.rounding/(10**(-self.accuracy))
572 # return str(display_value)
576 # Use it as a decorator of the function you plan to cache
577 # Timeout: 0 = no timeout, otherwise in seconds
580 def __init__(self, timeout=10000, skiparg=2):
581 self.timeout = timeout
584 def __call__(self, fn):
585 arg_names = inspect.getargspec(fn)[0][2:]
586 def cached_result(self2, cr=None, *args, **kwargs):
591 # Update named arguments with positional argument values
592 kwargs.update(dict(zip(arg_names, args)))
593 kwargs = kwargs.items()
596 # Work out key as a tuple of ('argname', value) pairs
597 key = (('dbname', cr.dbname),) + tuple(kwargs)
599 # Check cache and return cached value if possible
600 if key in self.cache:
601 (value, last_time) = self.cache[key]
602 mintime = time.time() - self.timeout
603 if self.timeout <= 0 or mintime <= last_time:
606 # Work out new value, cache it and return it
607 # Should copy() this value to avoid futur modf of the cacle ?
608 result = fn(self2,cr,**dict(kwargs))
610 self.cache[key] = (result, time.time())
615 return s.replace('&','&').replace('<','<').replace('>','>')
619 'zh_CN': 'Chinese (CN)',
620 'zh_TW': 'Chinese (TW)',
623 'es_AR': 'Español (Argentina)',
624 'es_ES': 'Español (España)',
626 'fr_CH': 'Français (Suisse)',
627 'en_EN': 'English (default)',
628 'hu_HU': 'Hungarian',
630 'pt_BR': 'Portugese (Brasil)',
631 'pt_PT': 'Portugese (Portugal)',
632 'nl_NL': 'Nederlands',
639 def scan_languages():
641 file_list = [os.path.splitext(os.path.basename(f))[0] for f in glob.glob(os.path.join(config['root_path'], 'i18n', '*.csv'))]
642 lang_dict = get_languages()
643 return [(lang, lang_dict.get(lang, lang)) for lang in file_list]
646 def get_user_companies(cr, user):
647 def _get_company_children(cr, ids):
650 cr.execute('SELECT id FROM res_company WHERE parent_id = any(array[%s])' %(','.join([str(x) for x in ids]),))
651 res=[x[0] for x in cr.fetchall()]
652 res.extend(_get_company_children(cr, res))
654 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,))
655 compids=[cr.fetchone()[0]]
656 compids.extend(_get_company_children(cr, compids))
661 Input number : account or invoice number
662 Output return: the same number completed with the recursive mod10
665 codec=[0,9,4,6,8,2,7,1,3,5]
671 report = codec[ (int(digit) + report) % 10 ]
672 return result + str((10 - report) % 10)
677 if __name__ == '__main__':
683 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: