from email.Utils import formatdate, COMMASPACE
from email import Encoders
from itertools import islice, izip
+from lxml import etree
from which import which
if sys.version_info[:2] < (2, 4):
from threadinglocal import local
else:
from threading import local
+try:
+ from html2text import html2text
+except ImportError:
+ html2text = None
import netsvc
from config import config
_logger = logging.getLogger('tools')
+# List of etree._Element subclasses that we choose to ignore when parsing XML.
+# We include the *Base ones just in case, currently they seem to be subclasses of the _* ones.
+SKIPPED_ELEMENT_TYPES = (etree._Comment, etree._ProcessingInstruction, etree.CommentBase, etree.PIBase)
+
# initialize a database with base/base.sql
def init_db(cr):
import addons
f = addons.get_module_resource('base', 'base.sql')
- cr.execute(file_open(f).read())
- cr.commit()
+ base_sql_file = file_open(f)
+ try:
+ cr.execute(base_sql_file.read())
+ cr.commit()
+ finally:
+ base_sql_file.close()
for i in addons.get_modules():
mod_path = addons.get_module_path(i)
prog = find_pg_tool(name)
if not prog:
raise Exception('Couldn\'t find %s' % name)
- args2 = (os.path.basename(prog),) + args
+ args2 = (prog,) + args
- return subprocess.call(args2, executable=prog)
+ return subprocess.call(args2)
def exec_pg_command_pipe(name, *args):
prog = find_pg_tool(name)
if not prog:
raise Exception('Couldn\'t find %s' % name)
- pop = subprocess.Popen(args, executable=prog, shell=True, bufsize= -1,
- stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True)
+ # on win32, passing close_fds=True is not compatible
+ # with redirecting std[in/err/out]
+ pop = subprocess.Popen((prog,) + args, bufsize= -1,
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ close_fds=(os.name=="posix"))
return (pop.stdin, pop.stdout)
def exec_command_pipe(name, *args):
prog = find_in_path(name)
if not prog:
raise Exception('Couldn\'t find %s' % name)
- pop = subprocess.Popen(args, executable=prog, shell=True, bufsize= -1,
- stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True)
+ # on win32, passing close_fds=True is not compatible
+ # with redirecting std[in/err/out]
+ pop = subprocess.Popen((prog,) + args, bufsize= -1,
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ close_fds=(os.name=="posix"))
return (pop.stdin, pop.stdout)
#----------------------------------------------------------
return fo
if os.path.splitext(name)[1] == '.rml':
raise IOError, 'Report %s doesn\'t exist or deleted : ' %str(name)
- raise IOError, 'File not found : '+str(name)
+ raise IOError, 'File not found : %s' % name
#----------------------------------------------------------
email_body = ustr(body).encode('utf-8')
email_text = MIMEText(email_body or '',_subtype=subtype,_charset='utf-8')
- if attach: msg = MIMEMultipart()
- else: msg = email_text
+ msg = MIMEMultipart()
msg['Subject'] = Header(ustr(subject), 'utf-8')
msg['From'] = email_from
msg['To'] = COMMASPACE.join(email_to)
if email_cc:
msg['Cc'] = COMMASPACE.join(email_cc)
- if email_bcc:
- msg['Bcc'] = COMMASPACE.join(email_bcc)
msg['Date'] = formatdate(localtime=True)
msg['X-Priority'] = priorities.get(priority, '3 (Normal)')
for key, value in x_headers.iteritems():
msg['%s' % key] = str(value)
- if attach:
+ if html2text and subtype == 'html':
+ text = html2text(email_body.decode('utf-8')).encode('utf-8')
+ alternative_part = MIMEMultipart(_subtype="alternative")
+ alternative_part.attach(MIMEText(text, _charset='utf-8', _subtype='plain'))
+ alternative_part.attach(email_text)
+ msg.attach(alternative_part)
+ else:
msg.attach(email_text)
+
+ if attach:
for (fname,fcontent) in attach:
part = MIMEBase('application', "octet-stream")
part.set_payload( fcontent )
return lang
def get_languages():
+ # The codes below are those from Launchpad's Rosetta, with the exception
+ # of some trivial codes where the Launchpad code is xx and we have xx_XX.
languages={
'ab_RU': u'Abkhazian / аҧсуа',
'ar_AR': u'Arabic / الْعَرَبيّة',
'fr_FR': u'French / Français',
'gl_ES': u'Galician / Galego',
'gu_IN': u'Gujarati / ગુજરાતી',
+ 'he_IL': u'Hebrew / עִבְרִי',
'hi_IN': u'Hindi / हिंदी',
'hr_HR': u'Croatian / hrvatski jezik',
'hu_HU': u'Hungarian / Magyar',
'sl_SI': u'Slovenian / slovenščina',
'sk_SK': u'Slovak / Slovenský jazyk',
'sq_AL': u'Albanian / Shqip',
- 'sr_RS': u'Serbian / српски језик',
+ 'sr_RS': u'Serbian (Cyrillic) / српски',
+ 'sr@latin': u'Serbian (Latin) / srpski',
'sv_SE': u'Swedish / svenska',
'te_IN': u'Telugu / తెలుగు',
'tr_TR': u'Turkish / Türkçe',
DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_TIME_FORMAT)
+# Python's strftime supports only the format directives
+# that are available on the platform's libc, so in order to
+# be cross-platform we map to the directives required by
+# the C standard (1989 version), always available on platforms
+# with a C standard implementation.
+DATETIME_FORMATS_MAP = {
+ '%C': '', # century
+ '%D': '%m/%d/%Y', # modified %y->%Y
+ '%e': '%d',
+ '%E': '', # special modifier
+ '%F': '%Y-%m-%d',
+ '%g': '%Y', # modified %y->%Y
+ '%G': '%Y',
+ '%h': '%b',
+ '%k': '%H',
+ '%l': '%I',
+ '%n': '\n',
+ '%O': '', # special modifier
+ '%P': '%p',
+ '%R': '%H:%M',
+ '%r': '%I:%M:%S %p',
+ '%s': '', #num of seconds since epoch
+ '%T': '%H:%M:%S',
+ '%t': ' ', # tab
+ '%u': ' %w',
+ '%V': '%W',
+ '%y': '%Y', # Even if %y works, it's ambiguous, so we should use %Y
+ '%+': '%Y-%m-%d %H:%M:%S',
+
+ # %Z is a special case that causes 2 problems at least:
+ # - the timezone names we use (in res_user.context_tz) come
+ # from pytz, but not all these names are recognized by
+ # strptime(), so we cannot convert in both directions
+ # when such a timezone is selected and %Z is in the format
+ # - %Z is replaced by an empty string in strftime() when
+ # there is not tzinfo in a datetime value (e.g when the user
+ # did not pick a context_tz). The resulting string does not
+ # parse back if the format requires %Z.
+ # As a consequence, we strip it completely from format strings.
+ # The user can always have a look at the context_tz in
+ # preferences to check the timezone.
+ '%z': '',
+ '%Z': '',
+}
+
def server_to_local_timestamp(src_tstamp_str, src_format, dst_format, dst_tz_name,
tz_offset=True, ignore_unparsable_time=True):
"""