[FIX] Set a default value if the x_headers argument has the value 'None'
[odoo/odoo.git] / bin / tools / misc.py
index 1803de2..1ead9ea 100644 (file)
@@ -44,7 +44,7 @@ from itertools import izip
 def init_db(cr):
     import addons
     f = addons.get_module_resource('base', 'base.sql')
-    for line in file(f).read().split(';'):
+    for line in file_open(f).read().split(';'):
         if (len(line)>0) and (not line.isspace()):
             cr.execute(line)
     cr.commit()
@@ -55,12 +55,8 @@ def init_db(cr):
         if not mod_path:
             continue
         info = False
-        if os.path.isfile(terp_file) and not os.path.isfile(mod_path+'.zip'):
-            info = eval(file(terp_file).read())
-        elif zipfile.is_zipfile(mod_path+'.zip'):
-            zfile = zipfile.ZipFile(mod_path+'.zip')
-            i = os.path.splitext(i)[0]
-            info = eval(zfile.read(os.path.join(i, '__terp__.py')))
+        if os.path.isfile(terp_file) or os.path.isfile(mod_path+'.zip'):
+            info = eval(file_open(terp_file).read())
         if info:
             categs = info.get('category', 'Uncategorized').split('/')
             p_id = None
@@ -103,6 +99,9 @@ def init_db(cr):
                 id, info.get('author', ''),
                 info.get('website', ''), i, info.get('name', False),
                 info.get('description', ''), p_id, state))
+            cr.execute('insert into ir_model_data \
+                (name,model,module, res_id) values (%s,%s,%s,%s)', (
+                    'module_meta_information', 'ir.module.module', i, id))
             dependencies = info.get('depends', [])
             for d in dependencies:
                 cr.execute('insert into ir_module_module_dependency \
@@ -224,13 +223,14 @@ def file_open(name, mode="r", subdir='addons', pathinfo=False):
         else:
             zipname = tail
         if zipfile.is_zipfile(head+'.zip'):
-            import cStringIO
+            from cStringIO import StringIO
             zfile = zipfile.ZipFile(head+'.zip')
             try:
-                fo = cStringIO.StringIO(zfile.read(os.path.join(
+                fo = StringIO()
+                fo.write(zfile.read(os.path.join(
                     os.path.basename(head), zipname).replace(
                         os.sep, '/')))
-
+                fo.seek(0)
                 if pathinfo:
                     return fo, name
                 return fo
@@ -302,7 +302,8 @@ def reverse_enumerate(l):
 #----------------------------------------------------------
 # Emails
 #----------------------------------------------------------
-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'):
+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', x_headers=None):
     """Send an email."""
     import smtplib
     from email.MIMEText import MIMEText
@@ -313,9 +314,15 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
     from email.Utils import formatdate, COMMASPACE
     from email import Encoders
 
+    if x_headers is None:
+        x_headers = {}
+
     if not ssl:
         ssl = config.get('smtp_ssl', False)
 
+    if not email_from and not config['email_from']:
+        raise Exception("No Email sender by default, see config file")
+
     if not email_cc:
         email_cc = []
     if not email_bcc:
@@ -326,11 +333,13 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
     else:
         msg = MIMEMultipart()
 
-    msg['Subject'] = Header(subject.decode('utf8'), 'utf-8')
+    msg['Subject'] = Header(ustr(subject), 'utf-8')
     msg['From'] = email_from
     del msg['Reply-To']
     if reply_to:
-        msg['Reply-To'] = msg['From']+', '+reply_to
+        msg['Reply-To'] = reply_to
+    else:
+        msg['Reply-To'] = msg['From']
     msg['To'] = COMMASPACE.join(email_to)
     if email_cc:
         msg['Cc'] = COMMASPACE.join(email_cc)
@@ -338,6 +347,15 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
         msg['Bcc'] = COMMASPACE.join(email_bcc)
     msg['Date'] = formatdate(localtime=True)
 
+    # Add OpenERP Server information
+    msg['X-Generated-By'] = 'OpenERP (http://www.openerp.com)'
+    msg['X-OpenERP-Server-Host'] = socket.gethostname()
+    msg['X-OpenERP-Server-Version'] = release.version
+
+    # Add dynamic X Header
+    for key, value in x_headers.items():
+        msg['X-OpenERP-%s' % key] = str(value)
+
     if tinycrm:
         msg['Message-Id'] = "<%s-tinycrm-%s@%s>" % (time.time(), tinycrm, socket.gethostname())
 
@@ -381,9 +399,10 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
 # text must be latin-1 encoded
 def sms_send(user, password, api_id, text, to):
     import urllib
-    params = urllib.urlencode({'user': user, 'password': password, 'api_id': api_id, 'text': text, 'to':to})
-    #f = urllib.urlopen("http://api.clickatell.com/http/sendmsg", params)
-    f = urllib.urlopen("http://196.7.150.220/http/sendmsg", params)
+    url = "http://api.urlsms.com/SendSMS.aspx"
+    #url = "http://196.7.150.220/http/sendmsg"
+    params = urllib.urlencode({'UserID': user, 'Password': password, 'SenderID': api_id, 'MsgText': text, 'RecipientMobileNo':to})
+    f = urllib.urlopen(url+"?"+params)
     # FIXME: Use the logger if there is an error
     return True
 
@@ -549,26 +568,73 @@ class cache(object):
         self.multi = multi
         self.lasttime = time.time()
         self.cache = {}
-        
+        self.fun = None 
         cache.__caches.append(self)
 
+    
+    def _generate_keys(self, dbname, kwargs2):
+        """
+        Generate keys depending of the arguments and the self.mutli value
+        """
+        
+        def to_tuple(d):
+            i = d.items()
+            i.sort(key=lambda (x,y): x)
+            return tuple(i)
+
+        if not self.multi:
+            key = (('dbname', dbname),) + to_tuple(kwargs2)
+            yield key, None
+        else:
+            multis = kwargs2[self.multi][:]    
+            for id in multis:
+                kwargs2[self.multi] = (id,)
+                key = (('dbname', dbname),) + to_tuple(kwargs2)
+                yield key, id
+    
+    def _unify_args(self, *args, **kwargs):
+        # Update named arguments with positional argument values (without self and cr)
+        kwargs2 = self.fun_default_values.copy()
+        kwargs2.update(kwargs)
+        kwargs2.update(dict(zip(self.fun_arg_names, args[self.skiparg-2:])))
+        for k in kwargs2:
+            if isinstance(kwargs2[k], (list, dict, set)):
+                kwargs2[k] = tuple(kwargs2[k])
+            elif not is_hashable(kwargs2[k]):
+                kwargs2[k] = repr(kwargs2[k])
+
+        return kwargs2
+    
+    def clear(self, dbname, *args, **kwargs):
+        """clear the cache for database dbname
+            if *args and **kwargs are both empty, clear all the keys related to this database
+        """
+        if not args and not kwargs:
+            keys_to_del = [key for key in self.cache if key[0][1] == dbname]
+        else:
+            kwargs2 = self._unify_args(*args, **kwargs)
+            keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache]
+        
+        for key in keys_to_del:
+            del self.cache[key]
+    
     @classmethod
-    def clean_cache_for_db(cls, dbname):
-        def get_dbname_from_key(key):
-            for e in key:
-                if e[0] == 'dbname':
-                    return e[1]
-            return None
-
-        for cache in cls.__caches:
-            keys_to_del = [key for key in cache.cache if get_dbname_from_key(key) == dbname]
-            for key in keys_to_del:
-                del cache.cache[key]
+    def clean_caches_for_db(cls, dbname):
+        for c in cls.__caches:
+            c.clear(dbname)
 
     def __call__(self, fn):
-        arg_names = inspect.getargspec(fn)[0][self.skiparg:]
-
-        def cached_result(self2, cr=None, *args, **kwargs):
+        if self.fun is not None:
+            raise Exception("Can not use a cache instance on more than one function")
+        self.fun = fn
+
+        argspec = inspect.getargspec(fn)
+        self.fun_arg_names = argspec[0][self.skiparg:]
+        self.fun_default_values = {}
+        if argspec[3]:
+            self.fun_default_values = dict(zip(self.fun_arg_names[-len(argspec[3]):], argspec[3]))
+        
+        def cached_result(self2, cr, *args, **kwargs):
             if time.time()-self.timeout > self.lasttime:
                 self.lasttime = time.time()
                 t = time.time()-self.timeout 
@@ -576,64 +642,36 @@ class cache(object):
                     if self.cache[key][1]<t:
                         del self.cache[key]
 
-            if cr is None:
-                self.cache = {}
-                return True
-            if ('clear_keys' in kwargs):
-                if (kwargs['clear_keys'] in self.cache):
-                    del self.cache[kwargs['clear_keys']]
-                return True
-
-            # Update named arguments with positional argument values (without self and cr)
-            kwargs2 = kwargs.copy()
-            kwargs2.update(dict(zip(arg_names, args[self.skiparg-2:])))
-            for k in kwargs2:
-                if isinstance(kwargs2[k], (list, dict, set)):
-                    kwargs2[k] = tuple(kwargs2[k])
-                elif not is_hashable(kwargs2[k]):
-                    kwargs2[k] = repr(kwargs2[k])
-
-            if self.multi:
-                kwargs3 = kwargs2.copy()
-                notincache = []
-                result = {}
-                for id in kwargs3[self.multi]:
-                    kwargs2[self.multi] = [id]
-                    kwargs4 = kwargs2.items()
-                    kwargs4.sort()
-
-                    # Work out key as a tuple of ('argname', value) pairs
-                    key = (('dbname', cr.dbname),) + tuple(kwargs4)
-                    if key in self.cache:
-                        result[id] = self.cache[key][0]
-                    else:
-                        notincache.append(id)
-
-
-
-                if notincache:
-                    kwargs2[self.multi] = notincache
-                    result2 = fn(self2, cr, *args[2:self.skip], **kwargs3)
-                    for id in result2:
-                        kwargs2[self.multi] = [id]
-                        kwargs4 = kwargs2.items()
-                        kwargs4.sort()
-                        key = (('dbname', cr.dbname),) + tuple(kwargs4)
-                        self.cache[key] = result2[id]
-                    result.updat(result2)
-                return result
-
-            kwargs2 = kwargs2.items()
-            kwargs2.sort()
-
-            key = (('dbname', cr.dbname),) + tuple(kwargs2)
-            if key in self.cache:
-                return self.cache[key][0]
-
-            result = fn(self2, cr, *args, **kwargs)
+            kwargs2 = self._unify_args(*args, **kwargs)
 
-            self.cache[key] = (result, time.time())
+            result = {}
+            notincache = {}
+            for key, id in self._generate_keys(cr.dbname, kwargs2):
+                if key in self.cache:
+                    result[id] = self.cache[key][0]
+                else:
+                    notincache[id] = key
+            
+            if notincache:
+                if self.multi:
+                    kwargs2[self.multi] = notincache.keys()
+                
+                result2 = fn(self2, cr, *args[2:self.skiparg], **kwargs2)
+                if not self.multi:
+                    key = notincache[None]
+                    self.cache[key] = (result2, time.time())
+                    result[None] = result2
+                else:
+                    for id in result2:
+                        key = notincache[id]
+                        self.cache[key] = (result2[id], time.time())
+                    result.update(result2)
+                        
+            if not self.multi:
+                return result[None]
             return result
+
+        cached_result.clear_cache = self.clear
         return cached_result
 
 def to_xml(s):
@@ -658,7 +696,30 @@ def ustr(value):
     if not isinstance(value, str):
         value = str(value)
 
-    return unicode(value, 'utf-8')
+    try: # first try utf-8
+        return unicode(value, 'utf-8')
+    except:
+        pass
+
+    try: # then extened iso-8858
+        return unicode(value, 'iso-8859-15')
+    except:
+        pass
+
+    # else use default system locale
+    from locale import getlocale
+    return unicode(value, getlocale()[1])
+
+def exception_to_unicode(e):
+    if hasattr(e, 'message'):
+        return ustr(e.message)
+    if hasattr(e, 'args'):
+        return "\n".join((ustr(a) for a in e.args))
+    try:
+        return ustr(e)
+    except:
+        return u"Unknow message"
+
 
 # to be compatible with python 2.4
 import __builtin__
@@ -691,22 +752,26 @@ def get_languages():
         'bs_BS': u'Bosnian / bosanski jezik',
         'ca_ES': u'Catalan / Català',
         'cs_CZ': u'Czech / Čeština',
+        'da_DK': u'Danish / Dansk',
         'de_DE': u'German / Deutsch',
+        'el_EL': u'Greek / Ελληνικά',
         'en_CA': u'English (CA)',
         'en_EN': u'English (default)',
         'en_GB': u'English (UK)',
         'en_US': u'English (US)',
         'es_AR': u'Spanish (AR) / Español (AR)',
         'es_ES': u'Spanish / Español',
-        'et_ET': u'Estonian / Eesti keel',
+        'et_EE': u'Estonian / Eesti keel',
         'fr_BE': u'French (BE) / Français (BE)',
         'fr_CH': u'French (CH) / Français (CH)',
         'fr_FR': u'French / Français',
         'hr_HR': u'Croatian / hrvatski jezik',
         'hu_HU': u'Hungarian / Magyar',
+        'id_ID': u'Indonesian / Bahasa Indonesia',
         'it_IT': u'Italian / Italiano',
         'lt_LT': u'Lithuanian / Lietuvių kalba',
         'nl_NL': u'Dutch / Nederlands',
+        'nl_BE': u'Dutch (Belgium) / Nederlands (Belgïe)',
         'pl_PL': u'Polish / Język polski',
         'pt_BR': u'Portugese (BR) / português (BR)',
         'pt_PT': u'Portugese / português',
@@ -798,6 +863,28 @@ def logged(f):
 
     return wrapper
 
+class profile(object):
+    def __init__(self, fname=None):
+        self.fname = fname
+
+    def __call__(self, f):
+        from tools.func import wraps
+
+        @wraps(f)
+        def wrapper(*args, **kwargs):
+            class profile_wrapper(object):
+                def __init__(self):
+                    self.result = None
+                def __call__(self):
+                    self.result = f(*args, **kwargs)
+            pw = profile_wrapper()
+            import cProfile
+            fname = self.fname or ("%s.cprof" % (f.func_name,))
+            cProfile.runctx('pw()', globals(), locals(), filename=fname)
+            return pw.result
+
+        return wrapper
+
 def debug(what):
     """
         This method allow you to debug your code without print
@@ -860,6 +947,26 @@ icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_
 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
 ])
 
+def extract_zip_file(zip_file, outdirectory):
+    import zipfile
+    import os
+
+    zf = zipfile.ZipFile(zip_file, 'r')
+    out = outdirectory
+    for path in zf.namelist():
+        tgt = os.path.join(out, path)
+        tgtdir = os.path.dirname(tgt)
+        if not os.path.exists(tgtdir):
+            os.makedirs(tgtdir)
+
+        if not tgt.endswith(os.sep):
+            fp = open(tgt, 'wb')
+            fp.write(zf.read(path))
+            fp.close()
+    zf.close()
+
+
+
 
 
 if __name__ == '__main__':