summaryrefslogtreecommitdiff
path: root/odoo/tools
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-10-09 02:08:17 +0200
committerChristophe Simonis <chs@odoo.com>2019-10-09 02:08:17 +0200
commitd74b4518052066446e2a1975a91ce53c745ca75a (patch)
treec08180f88a512d15e37fbec628931f02b3825f65 /odoo/tools
parentec162dd0cb5622ab6e56db1a63dab5f531a96f4a (diff)
parentf4105eb9c7010f8693e5d2fd20c0bd9d9cc2e483 (diff)
[MERGE] forward port branch 13.0 up to f4105eb9c70
Diffstat (limited to 'odoo/tools')
-rw-r--r--odoo/tools/config.py24
-rw-r--r--odoo/tools/image.py17
-rw-r--r--odoo/tools/mail.py37
-rw-r--r--odoo/tools/misc.py57
-rw-r--r--odoo/tools/sql.py2
5 files changed, 66 insertions, 71 deletions
diff --git a/odoo/tools/config.py b/odoo/tools/config.py
index f0c6cacda37..e691cdb4d08 100644
--- a/odoo/tools/config.py
+++ b/odoo/tools/config.py
@@ -473,7 +473,7 @@ class configmanager(object):
elif isinstance(self.options[arg], str) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
- self.options['root_path'] = os.path.abspath(os.path.expanduser(os.path.expandvars(os.path.join(os.path.dirname(__file__), '..'))))
+ self.options['root_path'] = self._normalize(os.path.join(os.path.dirname(__file__), '..'))
if not self.options['addons_path'] or self.options['addons_path']=='None':
default_addons = []
base_addons = os.path.join(self.options['root_path'], 'addons')
@@ -485,18 +485,16 @@ class configmanager(object):
self.options['addons_path'] = ','.join(default_addons)
else:
self.options['addons_path'] = ",".join(
- os.path.abspath(os.path.expanduser(os.path.expandvars(x.strip())))
- for x in self.options['addons_path'].split(','))
+ self._normalize(x)
+ for x in self.options['addons_path'].split(','))
self.options['upgrades_paths'] = (
- ",".join(os.path.abspath(os.path.expanduser(os.path.expandvars(x.strip())))
+ ",".join(self._normalize(x)
for x in self.options['upgrades_paths'].split(','))
if self.options['upgrades_paths']
else ""
)
- self.options['data_dir'] = os.path.abspath(os.path.expanduser(os.path.expandvars(self.options['data_dir'].strip())))
-
self.options['init'] = opt.init and dict.fromkeys(opt.init.split(','), 1) or {}
self.options['demo'] = (dict(self.options['init'])
if not self.options['without_demo'] else {})
@@ -519,6 +517,10 @@ class configmanager(object):
if opt.save:
self.save()
+ # normalize path options
+ for key in ['data_dir', 'logfile', 'pidfile', 'test_file', 'screencasts', 'screenshots', 'pg_path', 'translate_out', 'translate_in', 'geoip_database']:
+ self.options[key] = self._normalize(self.options[key])
+
conf.addons_paths = self.options['addons_path'].split(',')
conf.server_wide_modules = [
@@ -553,7 +555,7 @@ class configmanager(object):
upgrades_paths = []
for path in value.split(','):
path = path.strip()
- res = os.path.abspath(os.path.expanduser(os.path.expandvars(path)))
+ res = self._normalize(path)
if not os.path.isdir(res):
raise optparse.OptionValueError("option %s: no such directory: %r" % (opt, path))
upgrades_paths.append(res)
@@ -695,4 +697,12 @@ class configmanager(object):
self.options['admin_passwd'] = updated_hash
return True
+ def _normalize(self, path):
+ if not path:
+ return path
+ return os.path.abspath(
+ os.path.expanduser(
+ os.path.expandvars(
+ path.strip())))
+
config = configmanager()
diff --git a/odoo/tools/image.py b/odoo/tools/image.py
index 5819b8c7b10..f5b4b953da9 100644
--- a/odoo/tools/image.py
+++ b/odoo/tools/image.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
+import binascii
import io
from PIL import Image
@@ -9,6 +10,7 @@ from PIL import IcoImagePlugin
from random import randrange
+from odoo.exceptions import UserError
from odoo.tools.translate import _
@@ -49,8 +51,7 @@ class ImageProcess():
:rtype: ImageProcess
:raise: ValueError if `verify_resolution` is True and the image is too large
- :raise: binascii.Error: if the base64 is incorrect
- :raise: OSError if the image can't be identified by PIL
+ :raise: UserError if the base64 is incorrect or the image can't be identified by PIL
"""
self.base64_source = base64_source or False
self.operationsCount = 0
@@ -215,8 +216,8 @@ class ImageProcess():
new_w, new_h = (new_w * h) // new_h, h
# Correctly place the center of the crop.
- x_offset = (w - new_w) * center_x
- h_offset = (h - new_h) * center_y
+ x_offset = int((w - new_w) * center_x)
+ h_offset = int((h - new_h) * center_y)
if new_w != w or new_h != h:
self.image = self.image.crop((x_offset, h_offset, x_offset + new_w, h_offset + new_h))
@@ -339,10 +340,12 @@ def base64_to_image(base64_source):
:return: the PIL image
:rtype: PIL.Image
- :raise: binascii.Error: if the base64 is incorrect
- :raise: OSError if the image can't be identified by PIL
+ :raise: UserError if the base64 is incorrect or the image can't be identified by PIL
"""
- return Image.open(io.BytesIO(base64.b64decode(base64_source)))
+ try:
+ return Image.open(io.BytesIO(base64.b64decode(base64_source)))
+ except (OSError, binascii.Error):
+ raise UserError(_("This file could not be decoded as an image file. Please try with a different file."))
def image_to_base64(image, format, **params):
diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py
index 1ca5e0dc325..e1c999a041f 100644
--- a/odoo/tools/mail.py
+++ b/odoo/tools/mail.py
@@ -20,31 +20,6 @@ from odoo.tools import misc
_logger = logging.getLogger(__name__)
-# Optional Flanker dependency for email validation.
-_flanker_warning = False
-try:
- from flanker.addresslib import address
- if hasattr(address, 'six'):
- # Python 3 supported
- def checkmail(mail):
- return bool(address.validate_address(mail))
- else:
- def checkmail(mail):
- global _flanker_warning
- if not _flanker_warning:
- _logger.info("Flanker version 0.9 or greater required for Python 3 compatibility")
- _flanker_warning = True
- return True
-
-except ImportError:
- _logger.info('The flanker Python module is not installed, so email validation with flanker is unavailable')
- def checkmail(mail):
- global _flanker_warning
- if not _flanker_warning:
- _logger.info('The flanker Python module is not installed, so email validation with flanker is unavailable')
- _flanker_warning = True
- return True
-
#----------------------------------------------------------
# HTML Sanitizer
#----------------------------------------------------------
@@ -286,7 +261,7 @@ def html_keep_url(text):
link_tags = re.compile(r"""(?<!["'])((ftp|http|https):\/\/(\w+:{0,1}\w*@)?([^\s<"']+)(:[0-9]+)?(\/|\/([^\s<"']))?)(?![^\s<"']*["']|[^\s<"']*</a>)""")
for item in re.finditer(link_tags, text):
final += text[idx:item.start()]
- final += '<a href="%s" target="_blank">%s</a>' % (item.group(0), item.group(0))
+ final += '<a href="%s" target="_blank" rel="noreferrer noopener">%s</a>' % (item.group(0), item.group(0))
idx = item.end()
final += text[idx:]
return final
@@ -533,16 +508,6 @@ def email_normalize(text):
return False
return emails[0].lower()
-def email_validate(email):
- """
- Check is the email is valid using email normalization + optional Flanker module.
- If Flanker is installed, it will check if email is valid (checking DNS and other stuff).
- If Flanker is not installed, this method only normalizes the email
- :param text: string containing the email to validate
- :return: normalized email if mail is valid or False
- """
- return email_normalize(email) if checkmail(email) else False
-
def email_escape_char(email_address):
""" Escape problematic characters in the given email address string"""
return email_address.replace('\\', '\\\\').replace('%', '\\%').replace('_', '\\_')
diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py
index 5715c9b83c6..b65cd23cf6f 100644
--- a/odoo/tools/misc.py
+++ b/odoo/tools/misc.py
@@ -926,8 +926,10 @@ class ConstantMapping(Mapping):
return self._value
-def dumpstacks(sig=None, frame=None):
- """ Signal handler: dump a stack trace for each existing thread."""
+def dumpstacks(sig=None, frame=None, thread_idents=None):
+ """ Signal handler: dump a stack trace for each existing thread or given
+ thread(s) specified through the ``thread_idents`` sequence.
+ """
code = []
def extract_stack(stack):
@@ -944,14 +946,15 @@ def dumpstacks(sig=None, frame=None):
'url': getattr(th, 'url', 'n/a')}
for th in threading.enumerate()}
for threadId, stack in sys._current_frames().items():
- thread_info = threads_info.get(threadId, {})
- code.append("\n# Thread: %s (db:%s) (uid:%s) (url:%s)" %
- (thread_info.get('repr', threadId),
- thread_info.get('dbname', 'n/a'),
- thread_info.get('uid', 'n/a'),
- thread_info.get('url', 'n/a')))
- for line in extract_stack(stack):
- code.append(line)
+ if not thread_idents or threadId in thread_idents:
+ thread_info = threads_info.get(threadId, {})
+ code.append("\n# Thread: %s (db:%s) (uid:%s) (url:%s)" %
+ (thread_info.get('repr', threadId),
+ thread_info.get('dbname', 'n/a'),
+ thread_info.get('uid', 'n/a'),
+ thread_info.get('url', 'n/a')))
+ for line in extract_stack(stack):
+ code.append(line)
if odoo.evented:
# code from http://stackoverflow.com/questions/12510648/in-gevent-how-can-i-dump-stack-traces-of-all-running-greenlets
@@ -1159,6 +1162,21 @@ else:
def html_escape(text):
return werkzeug.utils.escape(text)
+
+def get_lang(env, lang_code=False):
+ """
+ Retrieve the first lang object installed, by checking the parameter lang_code,
+ the context and then the company. If no lang is installed from those variables,
+ fallback on the first lang installed in the system.
+ :param str lang_code: the locale (i.e. en_US)
+ :return res.lang: the first lang found that is installed on the system.
+ """
+ langs = [code for code, _ in env['res.lang'].get_installed()]
+ for code in [lang_code, env.context.get('lang'), env.user.company_id.partner_id.lang, langs[0]]:
+ if code in langs:
+ return env['res.lang']._lang_get(code)
+
+
def formatLang(env, value, digits=None, grouping=True, monetary=False, dp=False, currency_obj=False):
"""
Assuming 'Account' decimal.precision=3:
@@ -1179,9 +1197,7 @@ def formatLang(env, value, digits=None, grouping=True, monetary=False, dp=False,
if isinstance(value, str) and not value:
return ''
- langs = [code for code, _ in env['res.lang'].get_installed()]
- lang_code = env.context['lang'] if env.context.get('lang') in langs else (env.user.company_id.partner_id.lang or langs[0])
- lang_obj = env['res.lang']._lang_get(lang_code)
+ lang_obj = get_lang(env)
res = lang_obj.format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary)
@@ -1218,7 +1234,7 @@ def format_date(env, value, lang_code=False, date_format=False):
else:
value = odoo.fields.Datetime.from_string(value)
- lang = env['res.lang']._lang_get(lang_code or env.context.get('lang') or 'en_US')
+ lang = get_lang(env, lang_code)
locale = babel.Locale.parse(lang.code)
if not date_format:
date_format = posix_to_ldml(lang.date_format, locale=locale)
@@ -1237,7 +1253,7 @@ def parse_date(env, value, lang_code=False):
:return: date object from the localized string
:rtype: datetime.date
'''
- lang = env['res.lang']._lang_get(lang_code or env.context.get('lang') or 'en_US')
+ lang = get_lang(env, lang_code)
locale = babel.Locale.parse(lang.code)
try:
return babel.dates.parse_date(value, locale=locale)
@@ -1268,8 +1284,7 @@ def format_datetime(env, value, tz=False, dt_format='medium', lang_code=False):
except Exception:
localized_datetime = utc_datetime
- lang_code = lang_code or env.context.get('lang') or 'en_US'
- lang = env['res.lang']._lang_get(lang_code)
+ lang = get_lang(env, lang_code)
locale = babel.Locale.parse(lang.code or lang_code) # lang can be inactive, so `lang`is empty
if not dt_format:
@@ -1299,7 +1314,7 @@ def format_time(env, value, tz=False, time_format='medium', lang_code=False):
if not value:
return ''
- lang = env['res.lang']._lang_get(lang_code or env.context.get('lang') or 'en_US')
+ lang = get_lang(env, lang_code)
locale = babel.Locale.parse(lang.code)
if not time_format:
time_format = posix_to_ldml(lang.time_format, locale=locale)
@@ -1307,17 +1322,17 @@ def format_time(env, value, tz=False, time_format='medium', lang_code=False):
return babel.dates.format_time(value, format=time_format, locale=locale)
-def _format_time_ago(env, time_delta, lang_code=False):
+def _format_time_ago(env, time_delta, lang_code=False, add_direction=True):
if not lang_code:
langs = [code for code, _ in env['res.lang'].get_installed()]
lang_code = env.context['lang'] if env.context.get('lang') in langs else (env.user.company_id.partner_id.lang or langs[0])
locale = babel.Locale.parse(lang_code)
- return babel.dates.format_timedelta(-time_delta, add_direction=True, locale=locale)
+ return babel.dates.format_timedelta(-time_delta, add_direction=add_direction, locale=locale)
def format_amount(env, amount, currency, lang_code=False):
fmt = "%.{0}f".format(currency.decimal_places)
- lang = env['res.lang']._lang_get(lang_code or env.context.get('lang') or 'en_US')
+ lang = get_lang(env, lang_code)
formatted_amount = lang.format(fmt, currency.round(amount), grouping=True, monetary=True)\
.replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}')
diff --git a/odoo/tools/sql.py b/odoo/tools/sql.py
index 30cebe3bcd0..e1e4256dc7f 100644
--- a/odoo/tools/sql.py
+++ b/odoo/tools/sql.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+# pylint: disable=sql-injection
+
import logging
import psycopg2