pre-commit, black, isort

This commit is contained in:
Stéphane Bidoul (ACSONE) 2019-10-12 12:57:26 +02:00 committed by Maxime Franco
parent d93bdf1b46
commit 40132984fd
7 changed files with 188 additions and 216 deletions

View File

@ -5,18 +5,12 @@
{
"name": "server configuration environment files",
"version": "13.0.2.0.0",
"depends": [
"base",
"base_sparse_field",
],
"depends": ["base", "base_sparse_field"],
"author": "Camptocamp,Odoo Community Association (OCA)",
"summary": "move some configurations out of the database",
"website": "http://github.com/OCA/server-env",
"license": "GPL-3 or any later version",
"category": "Tools",
"data": [
'security/res_groups.xml',
'serv_config.xml',
],
'installable': True,
"data": ["security/res_groups.xml", "serv_config.xml"],
"installable": True,
}

View File

@ -2,12 +2,12 @@
# License GPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from functools import partialmethod
from lxml import etree
from odoo import api, fields, models
from ..server_env import serv_config
_logger = logging.getLogger(__name__)
@ -96,19 +96,20 @@ class ServerEnvMixin(models.AbstractModel):
``keychain.backend``.
"""
_name = 'server.env.mixin'
_description = 'Mixin to add server environment in existing models'
_name = "server.env.mixin"
_description = "Mixin to add server environment in existing models"
server_env_defaults = fields.Serialized()
_server_env_getter_mapping = {
'integer': 'getint',
'float': 'getfloat',
'monetary': 'getfloat',
'boolean': 'getboolean',
'char': 'get',
'selection': 'get',
'text': 'get',
"integer": "getint",
"float": "getfloat",
"monetary": "getfloat",
"boolean": "getboolean",
"char": "get",
"selection": "get",
"text": "get",
}
@property
@ -180,16 +181,13 @@ class ServerEnvMixin(models.AbstractModel):
# _server_env_has_key_defined so we are sure that the value is
# either in the global or the record config
getter = getattr(serv_config, config_getter)
if (section_name in serv_config
and field_name in serv_config[section_name]):
if section_name in serv_config and field_name in serv_config[section_name]:
value = getter(section_name, field_name)
else:
value = getter(global_section_name, field_name)
except Exception:
_logger.exception(
"error trying to read field %s in section %s",
field_name,
section_name,
"error trying to read field %s in section %s", field_name, section_name
)
return False
return value
@ -203,34 +201,31 @@ class ServerEnvMixin(models.AbstractModel):
and field_name in serv_config[global_section_name]
)
has_config = (
section_name in serv_config
and field_name in serv_config[section_name]
section_name in serv_config and field_name in serv_config[section_name]
)
return has_global_config or has_config
def _compute_server_env_from_config(self, field_name, options):
getter_name = options.get('getter') if options else None
getter_name = options.get("getter") if options else None
if not getter_name:
field_type = self._fields[field_name].type
getter_name = self._server_env_getter_mapping.get(field_type)
if not getter_name:
# if you get this message and the field is working as expected,
# you may want to add the type in _server_env_getter_mapping
_logger.warning('server.env.mixin is used on a field of type %s, '
'which may not be supported properly')
getter_name = 'get'
value = self._server_env_read_from_config(
field_name, getter_name
)
_logger.warning(
"server.env.mixin is used on a field of type %s, "
"which may not be supported properly"
)
getter_name = "get"
value = self._server_env_read_from_config(field_name, getter_name)
self[field_name] = value
def _compute_server_env_from_default(self, field_name, options):
if options and options.get('compute_default'):
getattr(self, options['compute_default'])()
if options and options.get("compute_default"):
getattr(self, options["compute_default"])()
else:
default_field = self._server_env_default_fieldname(
field_name
)
default_field = self._server_env_default_fieldname(field_name)
if default_field:
self[field_name] = self[default_field]
else:
@ -248,9 +243,7 @@ class ServerEnvMixin(models.AbstractModel):
record._compute_server_env_from_config(field_name, options)
else:
record._compute_server_env_from_default(
field_name, options
)
record._compute_server_env_from_default(field_name, options)
def _inverse_server_env(self, field_name):
options = self._server_env_fields[field_name]
@ -262,8 +255,8 @@ class ServerEnvMixin(models.AbstractModel):
# we update the default value in database
if record[is_editable_field]:
if options and options.get('inverse_default'):
getattr(record, options['inverse_default'])()
if options and options.get("inverse_default"):
getattr(record, options["inverse_default"])()
elif default_field:
record[default_field] = record[field_name]
@ -278,12 +271,8 @@ class ServerEnvMixin(models.AbstractModel):
# in ``_inverse_server_env`` it would reset the value of the field
for record in self:
for field_name in self._server_env_fields:
is_editable_field = self._server_env_is_editable_fieldname(
field_name
)
is_editable = not record._server_env_has_key_defined(
field_name
)
is_editable_field = self._server_env_is_editable_fieldname(field_name)
is_editable = not record._server_env_has_key_defined(field_name)
record[is_editable_field] = is_editable
def _server_env_view_set_readonly(self, view_arch):
@ -293,37 +282,32 @@ class ServerEnvMixin(models.AbstractModel):
for elem in view_arch.findall(field_xpath % field):
# set env-computed fields to readonly if the configuration
# files have a key set for this field
elem.set('attrs',
str({'readonly': [(is_editable_field, '=', False)]}))
elem.set("attrs", str({"readonly": [(is_editable_field, "=", False)]}))
if not view_arch.findall(field_xpath % is_editable_field):
# add the _is_editable fields in the view for the 'attrs'
# domain
view_arch.append(
etree.Element(
'field',
name=is_editable_field,
invisible="1"
)
etree.Element("field", name=is_editable_field, invisible="1")
)
return view_arch
def _fields_view_get(self, view_id=None, view_type='form', toolbar=False,
submenu=False):
def _fields_view_get(
self, view_id=None, view_type="form", toolbar=False, submenu=False
):
view_data = super()._fields_view_get(
view_id=view_id, view_type=view_type,
toolbar=toolbar, submenu=submenu
view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu
)
view_arch = etree.fromstring(view_data['arch'].encode('utf-8'))
view_arch = etree.fromstring(view_data["arch"].encode("utf-8"))
view_arch = self._server_env_view_set_readonly(view_arch)
view_data['arch'] = etree.tostring(view_arch, encoding='unicode')
view_data["arch"] = etree.tostring(view_arch, encoding="unicode")
return view_data
def _server_env_default_fieldname(self, base_field_name):
"""Return the name of the field with default value"""
options = self._server_env_fields[base_field_name]
if options and options.get('no_default_field'):
return ''
return '%s_env_default' % (base_field_name,)
if options and options.get("no_default_field"):
return ""
return "{}_env_default".format(base_field_name)
def _server_env_is_editable_fieldname(self, base_field_name):
"""Return the name of the field for "is editable"
@ -331,16 +315,14 @@ class ServerEnvMixin(models.AbstractModel):
This is the field used to tell if the env-computed field can
be edited.
"""
return '%s_env_is_editable' % (base_field_name,)
return "{}_env_is_editable".format(base_field_name)
def _server_env_transform_field_to_read_from_env(self, field):
"""Transform the original field in a computed field"""
field.compute = '_compute_server_env'
field.compute = "_compute_server_env"
inverse_method_name = '_inverse_server_env_%s' % field.name
inverse_method = partialmethod(
type(self)._inverse_server_env, field.name
)
inverse_method_name = "_inverse_server_env_%s" % field.name
inverse_method = partialmethod(type(self)._inverse_server_env, field.name)
setattr(type(self), inverse_method_name, inverse_method)
field.inverse = inverse_method_name
field.store = False
@ -360,7 +342,7 @@ class ServerEnvMixin(models.AbstractModel):
# (inherits), we want to override it with a new one
if fieldname not in self._fields or self._fields[fieldname].inherited:
field = fields.Boolean(
compute='_compute_server_env_is_editable',
compute="_compute_server_env_is_editable",
automatic=True,
# this is required to be able to edit fields
# on new records
@ -385,14 +367,11 @@ class ServerEnvMixin(models.AbstractModel):
if fieldname not in self._fields or self._fields[fieldname].inherited:
base_field_cls = base_field.__class__
field_args = base_field.args.copy()
field_args.pop('_sequence', None)
field_args.update({
'sparse': 'server_env_defaults',
'automatic': True,
})
field_args.pop("_sequence", None)
field_args.update({"sparse": "server_env_defaults", "automatic": True})
if hasattr(base_field, 'selection'):
field_args['selection'] = base_field.selection
if hasattr(base_field, "selection"):
field_args["selection"] = base_field.selection
field = base_field_cls(**field_args)
self._add_field(fieldname, field)

View File

@ -18,13 +18,14 @@
#
##############################################################################
import configparser
import logging
import os
import configparser
from lxml import etree
from itertools import chain
from odoo import api, models, fields
from lxml import etree
from odoo import api, fields, models
from odoo.tools.config import config as system_base_config
from .system_info import get_server_environment
@ -33,19 +34,29 @@ _logger = logging.getLogger(__name__)
try:
from odoo.addons import server_environment_files
_dir = os.path.dirname(server_environment_files.__file__)
except ImportError:
_logger.info('not using server_environment_files for configuration,'
' no directory found')
_logger.info(
"not using server_environment_files for configuration," " no directory found"
)
_dir = None
ENV_VAR_NAMES = ('SERVER_ENV_CONFIG', 'SERVER_ENV_CONFIG_SECRET')
ENV_VAR_NAMES = ("SERVER_ENV_CONFIG", "SERVER_ENV_CONFIG_SECRET")
# Same dict as RawConfigParser._boolean_states
_boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True,
'0': False, 'no': False, 'false': False, 'off': False}
_boolean_states = {
"1": True,
"yes": True,
"true": True,
"on": True,
"0": False,
"no": False,
"false": False,
"off": False,
}
if not system_base_config.get('running_env', False):
if not system_base_config.get("running_env", False):
raise Exception(
"The parameter 'running_env' has not be set neither in base config "
"file option -c or in openerprc.\n"
@ -56,7 +67,7 @@ if not system_base_config.get('running_env', False):
ck_path = None
if _dir:
ck_path = os.path.join(_dir, system_base_config['running_env'])
ck_path = os.path.join(_dir, system_base_config["running_env"])
if not os.path.exists(ck_path):
raise Exception(
@ -77,25 +88,29 @@ def setboolean(obj, attr, _bool=None):
# Borrowed from MarkupSafe
def _escape(s):
"""Convert the characters &<>'" in string s to HTML-safe sequences."""
return (str(s).replace('&', '&amp;')
.replace('>', '&gt;')
.replace('<', '&lt;')
.replace("'", '&#39;')
.replace('"', '&#34;'))
return (
str(s)
.replace("&", "&amp;")
.replace(">", "&gt;")
.replace("<", "&lt;")
.replace("'", "&#39;")
.replace('"', "&#34;")
)
def _listconf(env_path):
"""List configuration files in a folder."""
files = [os.path.join(env_path, name)
for name in sorted(os.listdir(env_path))
if name.endswith('.conf')]
files = [
os.path.join(env_path, name)
for name in sorted(os.listdir(env_path))
if name.endswith(".conf")
]
return files
def _load_config_from_server_env_files(config_p):
default = os.path.join(_dir, 'default')
running_env = os.path.join(_dir,
system_base_config['running_env'])
default = os.path.join(_dir, "default")
running_env = os.path.join(_dir, system_base_config["running_env"])
if os.path.isdir(default):
conf_files = _listconf(default) + _listconf(running_env)
else:
@ -104,12 +119,12 @@ def _load_config_from_server_env_files(config_p):
try:
config_p.read(conf_files)
except Exception as e:
raise Exception('Cannot read config files "%s": %s' % (conf_files, e))
raise Exception('Cannot read config files "{}": {}'.format(conf_files, e))
def _load_config_from_rcfile(config_p):
config_p.read(system_base_config.rcfile)
config_p.remove_section('options')
config_p.remove_section("options")
def _load_config_from_env(config_p):
@ -120,8 +135,7 @@ def _load_config_from_env(config_p):
config_p.read_string(env_config)
except configparser.Error as err:
raise Exception(
'%s content could not be parsed: %s'
% (varname, err,)
"{} content could not be parsed: {}".format(varname, err)
)
@ -147,13 +161,15 @@ class _Defaults(dict):
def __setitem__(self, key, value):
def func(*a):
return str(value)
return dict.__setitem__(self, key, func)
class ServerConfiguration(models.TransientModel):
"""Display server configuration."""
_name = 'server.config'
_description = 'Display server configuration'
_name = "server.config"
_description = "Display server configuration"
_conf_defaults = _Defaults()
@classmethod
@ -164,20 +180,20 @@ class ServerConfiguration(models.TransientModel):
"""
ModelClass = super(ServerConfiguration, cls)._build_model(pool, cr)
ModelClass._add_columns()
ModelClass.running_env = system_base_config['running_env']
ModelClass.running_env = system_base_config["running_env"]
# Only show passwords in development
ModelClass.show_passwords = ModelClass.running_env in ('dev',)
ModelClass.show_passwords = ModelClass.running_env in ("dev",)
ModelClass._arch = None
ModelClass._build_osv()
return ModelClass
@classmethod
def _format_key(cls, section, key):
return '%s_I_%s' % (section, key)
return "{}_I_{}".format(section, key)
@classmethod
def _format_key_display_name(cls, key_name):
return key_name.replace('_I_', ' | ')
return key_name.replace("_I_", " | ")
@classmethod
def _add_columns(cls):
@ -185,16 +201,17 @@ class ServerConfiguration(models.TransientModel):
cols = chain(
list(cls._get_base_cols().items()),
list(cls._get_env_cols().items()),
list(cls._get_system_cols().items())
list(cls._get_system_cols().items()),
)
for col, value in cols:
col_name = col.replace('.', '_')
setattr(ServerConfiguration,
col_name,
fields.Char(
string=cls._format_key_display_name(col_name),
readonly=True)
)
col_name = col.replace(".", "_")
setattr(
ServerConfiguration,
col_name,
fields.Char(
string=cls._format_key_display_name(col_name), readonly=True
),
)
cls._conf_defaults[col_name] = value
@classmethod
@ -202,7 +219,7 @@ class ServerConfiguration(models.TransientModel):
""" Compute base fields"""
res = {}
for col, item in list(system_base_config.options.items()):
key = cls._format_key('odoo', col)
key = cls._format_key("odoo", col)
res[key] = item
return res
@ -222,7 +239,7 @@ class ServerConfiguration(models.TransientModel):
""" Compute system fields"""
res = {}
for col, item in get_server_environment():
key = cls._format_key('system', col)
key = cls._format_key("system", col)
res[key] = item
return res
@ -232,17 +249,19 @@ class ServerConfiguration(models.TransientModel):
names = []
for key in sorted(items):
names.append(key.replace('.', '_'))
return ('<group col="2" colspan="4">' +
''.join(['<field name="%s" readonly="1"/>' %
_escape(name) for name in names]) +
'</group>')
names.append(key.replace(".", "_"))
return (
'<group col="2" colspan="4">'
+ "".join(
['<field name="%s" readonly="1"/>' % _escape(name) for name in names]
)
+ "</group>"
)
@classmethod
def _build_osv(cls):
"""Build the view for the current configuration."""
arch = ('<form string="Configuration Form">'
'<notebook colspan="4">')
arch = '<form string="Configuration Form">' '<notebook colspan="4">'
# Odoo server configuration
rcfile = system_base_config.rcfile
@ -265,23 +284,23 @@ class ServerConfiguration(models.TransientModel):
arch += cls._group(cls._get_system_cols())
arch += '<separator colspan="4"/></page>'
arch += '</notebook></form>'
arch += "</notebook></form>"
cls._arch = etree.fromstring(arch)
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False,
submenu=False):
def fields_view_get(
self, view_id=None, view_type="form", toolbar=False, submenu=False
):
"""Overwrite the default method to render the custom view."""
res = super(ServerConfiguration, self).fields_view_get(view_id,
view_type,
toolbar)
View = self.env['ir.ui.view']
if view_type == 'form':
res = super(ServerConfiguration, self).fields_view_get(
view_id, view_type, toolbar
)
View = self.env["ir.ui.view"]
if view_type == "form":
arch_node = self._arch
xarch, xfields = View.postprocess_and_fields(
self._name, arch_node, view_id)
res['arch'] = xarch
res['fields'] = xfields
xarch, xfields = View.postprocess_and_fields(self._name, arch_node, view_id)
res["arch"] = xarch
res["fields"] = xfields
return res
@api.model
@ -291,18 +310,19 @@ class ServerConfiguration(models.TransientModel):
should be secret.
:return: list of secret keywords
"""
secret_keys = ['passw', 'key', 'secret', 'token']
secret_keys = ["passw", "key", "secret", "token"]
return any(secret_key in key for secret_key in secret_keys)
@api.model
def default_get(self, fields_list):
res = {}
if not self.env.user.has_group(
'server_environment.has_server_configuration_access'):
"server_environment.has_server_configuration_access"
):
return res
for key in self._conf_defaults:
if not self.show_passwords and self._is_secret(key=key):
res[key] = '**********'
res[key] = "**********"
else:
res[key] = self._conf_defaults[key]()
return res

View File

@ -28,38 +28,39 @@ from odoo.tools.config import config
def _get_output(cmd):
bindir = config['root_path']
p = subprocess.Popen(cmd, shell=True, cwd=bindir, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
bindir = config["root_path"]
p = subprocess.Popen(
cmd, shell=True, cwd=bindir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
return p.communicate()[0].rstrip()
def get_server_environment():
# inspired by server/bin/service/web_services.py
try:
rev_id = 'git:%s' % _get_output('git rev-parse HEAD')
rev_id = "git:%s" % _get_output("git rev-parse HEAD")
except Exception:
try:
rev_id = 'bzr: %s' % _get_output('bzr revision-info')
rev_id = "bzr: %s" % _get_output("bzr revision-info")
except Exception:
rev_id = 'Can not retrieve revison from git or bzr'
rev_id = "Can not retrieve revison from git or bzr"
os_lang = '.'.join([x for x in locale.getdefaultlocale() if x])
os_lang = ".".join([x for x in locale.getdefaultlocale() if x])
if not os_lang:
os_lang = 'NOT SET'
if os.name == 'posix' and platform.system() == 'Linux':
lsbinfo = _get_output('lsb_release -a')
os_lang = "NOT SET"
if os.name == "posix" and platform.system() == "Linux":
lsbinfo = _get_output("lsb_release -a")
else:
lsbinfo = 'not lsb compliant'
lsbinfo = "not lsb compliant"
return (
('platform', platform.platform()),
('os.name', os.name),
('lsb_release', lsbinfo),
('release', platform.release()),
('version', platform.version()),
('architecture', platform.architecture()[0]),
('locale', os_lang),
('python', platform.python_version()),
('odoo', release.version),
('revision', rev_id),
("platform", platform.platform()),
("os.name", os.name),
("lsb_release", lsbinfo),
("release", platform.release()),
("version", platform.version()),
("architecture", platform.architecture()[0]),
("locale", os_lang),
("python", platform.python_version()),
("odoo", release.version),
("revision", rev_id),
)

View File

@ -6,29 +6,27 @@ from contextlib import contextmanager
from unittest.mock import patch
from odoo.tests import common
from odoo.addons.server_environment import server_env
from odoo.tools.config import config
import odoo.addons.server_environment.models.server_env_mixin as \
server_env_mixin
import odoo.addons.server_environment.models.server_env_mixin as server_env_mixin
from odoo.addons.server_environment import server_env
class ServerEnvironmentCase(common.SavepointCase):
def setUp(self):
super().setUp()
self._original_running_env = config.get('running_env')
config['running_env'] = 'testing'
self._original_running_env = config.get("running_env")
config["running_env"] = "testing"
def tearDown(self):
super().tearDown()
config['running_env'] = self._original_running_env
config["running_env"] = self._original_running_env
@contextmanager
def set_config_dir(self, path):
original_dir = server_env._dir
if path and not os.path.isabs(path):
path = os.path.join(os.path.dirname(__file__,), path)
path = os.path.join(os.path.dirname(__file__), path)
server_env._dir = path
try:
yield
@ -39,18 +37,17 @@ class ServerEnvironmentCase(common.SavepointCase):
def set_env_variables(self, public=None, secret=None):
newkeys = {}
if public:
newkeys['SERVER_ENV_CONFIG'] = public
newkeys["SERVER_ENV_CONFIG"] = public
if secret:
newkeys['SERVER_ENV_CONFIG_SECRET'] = secret
with patch.dict('os.environ', newkeys):
newkeys["SERVER_ENV_CONFIG_SECRET"] = secret
with patch.dict("os.environ", newkeys):
yield
@contextmanager
def load_config(self, public=None, secret=None):
original_serv_config = server_env_mixin.serv_config
try:
with self.set_config_dir(None), \
self.set_env_variables(public, secret):
with self.set_config_dir(None), self.set_env_variables(public, secret):
parser = server_env._load_config()
server_env_mixin.serv_config = parser
yield

View File

@ -3,43 +3,25 @@
from odoo.addons.server_environment import server_env
from .common import ServerEnvironmentCase
class TestEnvironmentVariables(ServerEnvironmentCase):
def test_env_variables(self):
public = (
"[section]\n"
"foo=bar\n"
"bar=baz\n"
)
secret = (
"[section]\n"
"bar=foo\n"
"alice=bob\n"
)
with self.set_config_dir(None), \
self.set_env_variables(public, secret):
public = "[section]\n" "foo=bar\n" "bar=baz\n"
secret = "[section]\n" "bar=foo\n" "alice=bob\n"
with self.set_config_dir(None), self.set_env_variables(public, secret):
parser = server_env._load_config()
self.assertEqual(
list(parser.keys()),
['DEFAULT', 'section']
)
self.assertEqual(list(parser.keys()), ["DEFAULT", "section"])
self.assertDictEqual(
dict(parser['section'].items()),
{'alice': 'bob',
'bar': 'foo',
'foo': 'bar'}
dict(parser["section"].items()),
{"alice": "bob", "bar": "foo", "foo": "bar"},
)
def test_env_variables_override(self):
public = (
"[external_service.ftp]\n"
"user=foo\n"
)
with self.set_config_dir('testfiles'), \
self.set_env_variables(public):
public = "[external_service.ftp]\n" "user=foo\n"
with self.set_config_dir("testfiles"), self.set_env_variables(public):
parser = server_env._load_config()
val = parser.get('external_service.ftp', 'user')
self.assertEqual(val, 'foo')
val = parser.get("external_service.ftp", "user")
self.assertEqual(val, "foo")

View File

@ -1,34 +1,33 @@
# Copyright 2018 Camptocamp (https://www.camptocamp.com).
# License GPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.addons.server_environment import server_env
from . import common
class TestEnv(common.ServerEnvironmentCase):
def test_view(self):
model = self.env['server.config']
model = self.env["server.config"]
view = model.fields_view_get()
self.assertTrue(view)
def test_default(self):
model = self.env['server.config']
model = self.env["server.config"]
rec = model.create({})
defaults = rec.default_get([])
self.assertTrue(defaults)
self.assertIsInstance(defaults, dict)
pass_checked = False
for default in defaults:
if 'passw' in default:
self.assertNotEqual(defaults[default],
'**********')
if "passw" in default:
self.assertNotEqual(defaults[default], "**********")
pass_checked = True
self.assertTrue(pass_checked)
def test_value_retrival(self):
with self.set_config_dir('testfiles'):
with self.set_config_dir("testfiles"):
parser = server_env._load_config()
val = parser.get('external_service.ftp', 'user')
self.assertEqual(val, 'testing')
val = parser.get('external_service.ftp', 'host')
self.assertEqual(val, 'sftp.example.com')
val = parser.get("external_service.ftp", "user")
self.assertEqual(val, "testing")
val = parser.get("external_service.ftp", "host")
self.assertEqual(val, "sftp.example.com")