Merge pull request #13 from guewen/11.0-server-env-mixin
server env: add fallback on database default values
This commit is contained in:
commit
71d75f2e79
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
{
|
||||
'name': 'Mail configuration with server_environment',
|
||||
'version': '11.0.1.0.0',
|
||||
'version': '11.0.1.1.0',
|
||||
'category': 'Tools',
|
||||
'summary': 'Configure mail servers with server_environment_files',
|
||||
'author': "Camptocamp, Odoo Community Association (OCA)",
|
||||
|
|
@ -13,7 +13,5 @@
|
|||
'fetchmail',
|
||||
'server_environment',
|
||||
],
|
||||
'data': [
|
||||
'views/fetchmail_server_views.xml',
|
||||
],
|
||||
'data': [],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,58 +3,38 @@
|
|||
|
||||
import operator
|
||||
from odoo import api, fields, models
|
||||
from odoo.addons.server_environment import serv_config
|
||||
|
||||
|
||||
class FetchmailServer(models.Model):
|
||||
"""Incoming POP/IMAP mail server account"""
|
||||
_inherit = 'fetchmail.server'
|
||||
_name = 'fetchmail.server'
|
||||
_inherit = ["fetchmail.server", "server.env.mixin"]
|
||||
|
||||
server = fields.Char(compute='_compute_server_env',
|
||||
states={})
|
||||
port = fields.Integer(compute='_compute_server_env',
|
||||
states={})
|
||||
type = fields.Selection(compute='_compute_server_env',
|
||||
search='_search_type',
|
||||
states={})
|
||||
user = fields.Char(compute='_compute_server_env',
|
||||
states={})
|
||||
password = fields.Char(compute='_compute_server_env',
|
||||
states={})
|
||||
is_ssl = fields.Boolean(compute='_compute_server_env')
|
||||
attach = fields.Boolean(compute='_compute_server_env')
|
||||
original = fields.Boolean(compute='_compute_server_env')
|
||||
|
||||
@api.depends()
|
||||
def _compute_server_env(self):
|
||||
for fetchmail in self:
|
||||
global_section_name = 'incoming_mail'
|
||||
|
||||
key_types = {'port': int,
|
||||
'is_ssl': lambda a: bool(int(a or 0)),
|
||||
'attach': lambda a: bool(int(a or 0)),
|
||||
'original': lambda a: bool(int(a or 0)),
|
||||
@property
|
||||
def _server_env_fields(self):
|
||||
base_fields = super()._server_env_fields
|
||||
mail_fields = {
|
||||
"server": {},
|
||||
"port": {},
|
||||
"type": {},
|
||||
"user": {},
|
||||
"password": {},
|
||||
"is_ssl": {},
|
||||
"attach": {},
|
||||
"original": {},
|
||||
}
|
||||
mail_fields.update(base_fields)
|
||||
return mail_fields
|
||||
|
||||
# default vals
|
||||
config_vals = {'port': 993,
|
||||
'is_ssl': 0,
|
||||
'attach': 0,
|
||||
'original': 0,
|
||||
}
|
||||
if serv_config.has_section(global_section_name):
|
||||
config_vals.update(serv_config.items(global_section_name))
|
||||
type = fields.Selection(search='_search_type')
|
||||
|
||||
custom_section_name = '.'.join((global_section_name,
|
||||
fetchmail.name))
|
||||
if serv_config.has_section(custom_section_name):
|
||||
config_vals.update(serv_config.items(custom_section_name))
|
||||
@api.model
|
||||
def _server_env_global_section_name(self):
|
||||
"""Name of the global section in the configuration files
|
||||
|
||||
for key, to_type in key_types.items():
|
||||
if config_vals.get(key):
|
||||
config_vals[key] = to_type(config_vals[key])
|
||||
|
||||
fetchmail.update(config_vals)
|
||||
Can be customized in your model
|
||||
"""
|
||||
return 'incoming_mail'
|
||||
|
||||
@api.model
|
||||
def _search_type(self, oper, value):
|
||||
|
|
|
|||
|
|
@ -1,44 +1,30 @@
|
|||
# Copyright 2012-2018 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.addons.server_environment import serv_config
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class IrMailServer(models.Model):
|
||||
_inherit = "ir.mail_server"
|
||||
_name = "ir.mail_server"
|
||||
_inherit = ["ir.mail_server", "server.env.mixin"]
|
||||
|
||||
smtp_host = fields.Char(compute='_compute_server_env',
|
||||
required=False,
|
||||
readonly=True)
|
||||
smtp_port = fields.Integer(compute='_compute_server_env',
|
||||
required=False,
|
||||
readonly=True)
|
||||
smtp_user = fields.Char(compute='_compute_server_env',
|
||||
required=False,
|
||||
readonly=True)
|
||||
smtp_pass = fields.Char(compute='_compute_server_env',
|
||||
required=False,
|
||||
readonly=True)
|
||||
smtp_encryption = fields.Selection(compute='_compute_server_env',
|
||||
required=False,
|
||||
readonly=True)
|
||||
@property
|
||||
def _server_env_fields(self):
|
||||
base_fields = super()._server_env_fields
|
||||
mail_fields = {
|
||||
"smtp_host": {},
|
||||
"smtp_port": {},
|
||||
"smtp_user": {},
|
||||
"smtp_pass": {},
|
||||
"smtp_encryption": {},
|
||||
}
|
||||
mail_fields.update(base_fields)
|
||||
return mail_fields
|
||||
|
||||
@api.depends()
|
||||
def _compute_server_env(self):
|
||||
for server in self:
|
||||
global_section_name = 'outgoing_mail'
|
||||
@api.model
|
||||
def _server_env_global_section_name(self):
|
||||
"""Name of the global section in the configuration files
|
||||
|
||||
# default vals
|
||||
config_vals = {'smtp_port': 587}
|
||||
if serv_config.has_section(global_section_name):
|
||||
config_vals.update((serv_config.items(global_section_name)))
|
||||
|
||||
custom_section_name = '.'.join((global_section_name, server.name))
|
||||
if serv_config.has_section(custom_section_name):
|
||||
config_vals.update(serv_config.items(custom_section_name))
|
||||
|
||||
if config_vals.get('smtp_port'):
|
||||
config_vals['smtp_port'] = int(config_vals['smtp_port'])
|
||||
|
||||
server.update(config_vals)
|
||||
Can be customized in your model
|
||||
"""
|
||||
return 'outgoing_mail'
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="inherit_fetchmail" model="ir.ui.view">
|
||||
<field name="model">fetchmail.server</field>
|
||||
<field name="inherit_id" ref="fetchmail.view_email_server_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="server" position="attributes">
|
||||
<attribute name="attrs" eval="False"/>
|
||||
</field>
|
||||
<field name="port" position="attributes">
|
||||
<attribute name="attrs" eval="False"/>
|
||||
</field>
|
||||
<field name="user" position="attributes">
|
||||
<attribute name="attrs" eval="False"/>
|
||||
</field>
|
||||
<field name="password" position="attributes">
|
||||
<attribute name="attrs" eval="False"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -3,17 +3,20 @@
|
|||
:alt: License: GPL-3
|
||||
|
||||
==================
|
||||
server environment
|
||||
Server Environment
|
||||
==================
|
||||
|
||||
This module provides a way to define an environment in the main Odoo
|
||||
configuration file and to read some configurations from files
|
||||
depending on the configured environment: you define the environment in
|
||||
the main configuration file, and the values for the various possible
|
||||
environments are stored in the `server_environment_files` companion
|
||||
environments are stored in the ``server_environment_files`` companion
|
||||
module.
|
||||
|
||||
All the settings will be read only and visible under the Configuration
|
||||
The ``server_environment_files`` module is optional, the values can be set using
|
||||
an environment variable with a fallback on default values in the database.
|
||||
|
||||
The configuration read from the files are visible under the Configuration
|
||||
menu. If you are not in the 'dev' environment you will not be able to
|
||||
see the values contained in keys named '*passw*'.
|
||||
|
||||
|
|
@ -21,14 +24,14 @@ Installation
|
|||
============
|
||||
|
||||
By itself, this module does little. See for instance the
|
||||
`mail_environment` addon which depends on this one to allow configuring
|
||||
``mail_environment`` addon which depends on this one to allow configuring
|
||||
the incoming and outgoing mail servers depending on the environment.
|
||||
|
||||
To install this module, you need to provide a companion module called
|
||||
`server_environment_files`. You can copy and customize the provided
|
||||
`server_environment_files_sample` module for this purpose.
|
||||
You can provide additional options in environment variables
|
||||
``SERVER_ENV_CONFIG`` and ``SERVER_ENV_CONFIG_SECRET``.
|
||||
You can store your configuration values in a companion module called
|
||||
``server_environment_files``. You can copy and customize the provided
|
||||
``server_environment_files_sample`` module for this purpose. Alternatively, you
|
||||
can provide them in environment variables ``SERVER_ENV_CONFIG`` and
|
||||
``SERVER_ENV_CONFIG_SECRET``.
|
||||
|
||||
|
||||
Configuration
|
||||
|
|
@ -65,7 +68,7 @@ Environment variable
|
|||
You can define configuration in the environment variable ``SERVER_ENV_CONFIG``
|
||||
and/or ``SERVER_ENV_CONFIG_SECRET``. The 2 variables are handled the exact same
|
||||
way, this is only a convenience for the deployment where you can isolate the
|
||||
secrets in a different, encrypted, file. This is a multi-line environment variable
|
||||
secrets in a different, encrypted, file. They are multi-line environment variables
|
||||
in the same configparser format than the files.
|
||||
If you used options in ``server_environment_files``, the options set in the
|
||||
environment variable overrides them.
|
||||
|
|
@ -75,7 +78,6 @@ the content of the variable must be set accordingly to the running environment.
|
|||
|
||||
Example of setup:
|
||||
|
||||
|
||||
A public file, containing that will contain public variables::
|
||||
|
||||
# These variables are not odoo standard variables,
|
||||
|
|
@ -106,17 +108,44 @@ A second file which is encrypted and contains secrets::
|
|||
sftp_password=xxxxxxxxx
|
||||
"
|
||||
|
||||
Default values
|
||||
--------------
|
||||
|
||||
When using the ``server.env.mixin`` mixin, for each env-computed field, a
|
||||
companion field ``<field>_env_default`` is created. This field is not
|
||||
environment-dependent. It's a fallback value used when no key is set in
|
||||
configuration files / environment variable.
|
||||
|
||||
When the default field is used, the field is made editable on Odoo.
|
||||
|
||||
Note: empty environment keys always take precedence over default fields
|
||||
|
||||
|
||||
Keychain integration
|
||||
--------------------
|
||||
|
||||
Read the documentation of the class `models/server_env_mixin.py
|
||||
<models/server_env_mixin.py>`_.
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
To use this module, in your code, you can follow this example::
|
||||
You can include a mixin in your model and configure the env-computed fields
|
||||
by an override of ``_server_env_fields``.
|
||||
|
||||
from openerp.addons.server_environment import serv_config
|
||||
for key, value in serv_config.items('external_service.ftp'):
|
||||
print (key, value)
|
||||
::
|
||||
|
||||
serv_config.get('external_service.ftp', 'tls')
|
||||
class StorageBackend(models.Model):
|
||||
_name = "storage.backend"
|
||||
_inherit = ["storage.backend", "server.env.mixin"]
|
||||
|
||||
@property
|
||||
def _server_env_fields(self):
|
||||
return {"directory_path": {}}
|
||||
|
||||
Read the documentation of the class and methods in `models/server_env_mixin.py
|
||||
<models/server_env_mixin.py>`__.
|
||||
|
||||
|
||||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
|
||||
|
|
|
|||
|
|
@ -17,4 +17,11 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from . import models
|
||||
# TODO when migrating to 12, fix the import of serv_config by renaming the
|
||||
# file?
|
||||
# Add an alias to access to the 'serv_config' module as it is shadowed
|
||||
# in the following line by an import of a variable with the same name.
|
||||
# We can't change the import of serv_config for backward compatibility.
|
||||
from . import serv_config as server_env
|
||||
from .serv_config import serv_config, setboolean
|
||||
|
|
|
|||
|
|
@ -20,8 +20,11 @@
|
|||
|
||||
{
|
||||
"name": "server configuration environment files",
|
||||
"version": "11.0.1.0.1",
|
||||
"depends": ["base"],
|
||||
"version": "11.0.2.0.0",
|
||||
"depends": [
|
||||
"base",
|
||||
"base_sparse_field",
|
||||
],
|
||||
"author": "Camptocamp,Odoo Community Association (OCA)",
|
||||
"summary": "move some configurations out of the database",
|
||||
"website": "http://odoo-community.org/",
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
from . import server_env_mixin
|
||||
|
|
@ -0,0 +1,408 @@
|
|||
# Copyright 2018 Camptocamp (https://www.camptocamp.com).
|
||||
# License AGPL-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 ..serv_config import serv_config
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ServerEnvMixin(models.AbstractModel):
|
||||
"""Mixin to add server environment in existing models
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
::
|
||||
|
||||
class StorageBackend(models.Model):
|
||||
_name = "storage.backend"
|
||||
_inherit = ["storage.backend", "server.env.mixin"]
|
||||
|
||||
@property
|
||||
def _server_env_fields(self):
|
||||
return {"directory_path": {}}
|
||||
|
||||
With the snippet above, the "storage.backend" model now uses a server
|
||||
environment configuration for the field ``directory_path``.
|
||||
|
||||
Under the hood, this mixin automatically replaces the original field
|
||||
by an env-computed field that reads from the configuration files.
|
||||
|
||||
By default, it looks for the configuration in a section named
|
||||
``[model_name.Record Name]`` where ``model_name`` is the ``_name`` of the
|
||||
model with ``.`` replaced by ``_``. Then in a global section which is only
|
||||
the name of the model. They can be customized by overriding the method
|
||||
:meth:`~_server_env_section_name` and
|
||||
:meth:`~_server_env_global_section_name`.
|
||||
|
||||
For each field transformed to an env-computed field, a companion field
|
||||
``<field>_env_default`` is automatically created. When its value is set
|
||||
and the configuration files do not contain a key for that field, the
|
||||
env-computed field uses the default value stored in database. If there is a
|
||||
key for this field but it is empty, the env-computed field has an empty
|
||||
value.
|
||||
|
||||
Env-computed fields are conditionally editable, based on the absence
|
||||
of their key in environment configuration files. When edited, their
|
||||
value is stored in the database.
|
||||
|
||||
Integration with keychain
|
||||
-------------------------
|
||||
The keychain addon is used account information, encrypting the password
|
||||
with a key per environment.
|
||||
|
||||
The default behavior of server_environment is to store the default fields
|
||||
in a serialized field, so the password would lend there unencrypted.
|
||||
|
||||
You can benefit from keychain by using custom compute/inverse methods to
|
||||
get/set the password field:
|
||||
|
||||
::
|
||||
|
||||
class StorageBackend(models.Model):
|
||||
_name = 'storage.backend'
|
||||
_inherit = ['keychain.backend', 'collection.base']
|
||||
|
||||
@property
|
||||
def _server_env_fields(self):
|
||||
base_fields = super()._server_env_fields
|
||||
sftp_fields = {
|
||||
"sftp_server": {},
|
||||
"sftp_port": {},
|
||||
"sftp_login": {},
|
||||
"sftp_password": {
|
||||
"no_default_field": True,
|
||||
"compute_default": "_compute_password",
|
||||
"inverse_default": "_inverse_password",
|
||||
},
|
||||
}
|
||||
sftp_fields.update(base_fields)
|
||||
return sftp_fields
|
||||
|
||||
* ``no_default_field`` means that no new (sparse) field need to be
|
||||
created, it already is provided by keychain
|
||||
* ``compute_default`` is the name of the compute method to get the default
|
||||
value when no key is set in the configuration files.
|
||||
``_compute_password`` is implemented by ``keychain.backend``.
|
||||
* ``inverse_default`` is the name of the compute method to set the default
|
||||
value when it is editable. ``_inverse_password`` is implemented by
|
||||
``keychain.backend``.
|
||||
|
||||
"""
|
||||
_name = 'server.env.mixin'
|
||||
|
||||
server_env_defaults = fields.Serialized()
|
||||
|
||||
_server_env_getter_mapping = {
|
||||
'integer': 'getint',
|
||||
'float': 'getfloat',
|
||||
'monetary': 'getfloat',
|
||||
'boolean': 'getboolean',
|
||||
'char': 'get',
|
||||
'selection': 'get',
|
||||
'text': 'get',
|
||||
}
|
||||
|
||||
@property
|
||||
def _server_env_fields(self):
|
||||
"""Dict of fields to replace by fields computed from env
|
||||
|
||||
To override in models. The dictionary is:
|
||||
{'name_of_the_field': options}
|
||||
|
||||
Where ``options`` is a dictionary::
|
||||
|
||||
options = {
|
||||
"getter": "getint",
|
||||
"no_default_field": True,
|
||||
"compute_default": "_compute_password",
|
||||
"inverse_default": "_inverse_password",
|
||||
}
|
||||
|
||||
* ``getter``: The configparser getter can be one of: get, getboolean,
|
||||
getint, getfloat. The getter is automatically inferred from the
|
||||
type of the field, so it shouldn't generally be needed to set it.
|
||||
* ``no_default_field``: disable creation of a field for storing
|
||||
the default value, must be used with ``compute_default`` and
|
||||
``inverse_default``
|
||||
* ``compute_default``: name of a compute method to get the default
|
||||
value when no key is present in configuration files
|
||||
* ``inverse_default``: name of an inverse method to set the default
|
||||
value when the value is editable
|
||||
|
||||
Example::
|
||||
|
||||
@property
|
||||
def _server_env_fields(self):
|
||||
base_fields = super()._server_env_fields
|
||||
sftp_fields = {
|
||||
"sftp_server": {},
|
||||
"sftp_port": {},
|
||||
"sftp_login": {},
|
||||
"sftp_password": {},
|
||||
}
|
||||
sftp_fields.update(base_fields)
|
||||
return sftp_fields
|
||||
"""
|
||||
return {}
|
||||
|
||||
@api.model
|
||||
def _server_env_global_section_name(self):
|
||||
"""Name of the global section in the configuration files
|
||||
|
||||
Can be customized in your model
|
||||
"""
|
||||
return self._name.replace(".", "_")
|
||||
|
||||
@api.multi
|
||||
def _server_env_section_name(self):
|
||||
"""Name of the section in the configuration files
|
||||
|
||||
Can be customized in your model
|
||||
"""
|
||||
self.ensure_one()
|
||||
base = self._server_env_global_section_name()
|
||||
return ".".join((base, self.name))
|
||||
|
||||
@api.multi
|
||||
def _server_env_read_from_config(self, field_name, config_getter):
|
||||
self.ensure_one()
|
||||
global_section_name = self._server_env_global_section_name()
|
||||
section_name = self._server_env_section_name()
|
||||
try:
|
||||
# at this point we should have checked that we have a key with
|
||||
# _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]):
|
||||
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,
|
||||
)
|
||||
return False
|
||||
return value
|
||||
|
||||
@api.multi
|
||||
def _server_env_has_key_defined(self, field_name):
|
||||
self.ensure_one()
|
||||
global_section_name = self._server_env_global_section_name()
|
||||
section_name = self._server_env_section_name()
|
||||
has_global_config = (
|
||||
global_section_name in serv_config
|
||||
and field_name in serv_config[global_section_name]
|
||||
)
|
||||
has_config = (
|
||||
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
|
||||
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
|
||||
)
|
||||
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'])()
|
||||
else:
|
||||
default_field = self._server_env_default_fieldname(
|
||||
field_name
|
||||
)
|
||||
if default_field:
|
||||
self[field_name] = self[default_field]
|
||||
|
||||
@api.multi
|
||||
def _compute_server_env(self):
|
||||
"""Read values from environment configuration files
|
||||
|
||||
If an env-computed field has no key in configuration files,
|
||||
read from the ``<field>_env_default`` field from database.
|
||||
"""
|
||||
for record in self:
|
||||
for field_name, options in self._server_env_fields.items():
|
||||
if record._server_env_has_key_defined(field_name):
|
||||
record._compute_server_env_from_config(field_name, options)
|
||||
|
||||
else:
|
||||
record._compute_server_env_from_default(
|
||||
field_name, options
|
||||
)
|
||||
|
||||
def _inverse_server_env(self, field_name):
|
||||
options = self._server_env_fields[field_name]
|
||||
default_field = self._server_env_default_fieldname(field_name)
|
||||
is_editable_field = self._server_env_is_editable_fieldname(field_name)
|
||||
|
||||
for record in self:
|
||||
# when we write in an env-computed field, if it is editable
|
||||
# we update the default value in database
|
||||
|
||||
if record[is_editable_field]:
|
||||
if options and options.get('inverse_default'):
|
||||
getattr(record, options['inverse_default'])()
|
||||
elif default_field:
|
||||
record[default_field] = record[field_name]
|
||||
|
||||
@api.multi
|
||||
def _compute_server_env_is_editable(self):
|
||||
"""Compute <field>_is_editable values
|
||||
|
||||
We can edit an env-computed filed only if there is no key
|
||||
in any environment configuration file. If there is an empty
|
||||
key, it's an empty value so we can't edit the env-computed field.
|
||||
"""
|
||||
# we can't group it with _compute_server_env otherwise when called
|
||||
# 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
|
||||
)
|
||||
record[is_editable_field] = is_editable
|
||||
|
||||
def _server_env_view_set_readonly(self, view_arch):
|
||||
field_xpath = './/field[@name="%s"]'
|
||||
for field in self._server_env_fields:
|
||||
is_editable_field = self._server_env_is_editable_fieldname(field)
|
||||
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)]}))
|
||||
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"
|
||||
)
|
||||
)
|
||||
return view_arch
|
||||
|
||||
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_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')
|
||||
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,)
|
||||
|
||||
def _server_env_is_editable_fieldname(self, base_field_name):
|
||||
"""Return the name of the field for "is editable"
|
||||
|
||||
This is the field used to tell if the env-computed field can
|
||||
be edited.
|
||||
"""
|
||||
return '%s_env_is_editable' % (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'
|
||||
|
||||
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
|
||||
field.required = False
|
||||
field.copy = False
|
||||
field.sparse = None
|
||||
field.prefetch = False
|
||||
|
||||
def _server_env_add_is_editable_field(self, base_field):
|
||||
"""Add a field indicating if we can edit the env-computed fields
|
||||
|
||||
It is used in the inverse function of the env-computed field
|
||||
and in the views to add 'readonly' on the fields.
|
||||
"""
|
||||
fieldname = self._server_env_is_editable_fieldname(base_field.name)
|
||||
# if the field is inherited, it's a related to its delegated model
|
||||
# (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',
|
||||
automatic=True,
|
||||
# this is required to be able to edit fields
|
||||
# on new records
|
||||
default=True,
|
||||
)
|
||||
self._add_field(fieldname, field)
|
||||
|
||||
def _server_env_add_default_field(self, base_field):
|
||||
"""Add a field storing the default value
|
||||
|
||||
The default value is used when there is no key for an env-computed
|
||||
field in the configuration files.
|
||||
|
||||
The field is a sparse field stored in the serialized (json) field
|
||||
``server_env_defaults``.
|
||||
"""
|
||||
fieldname = self._server_env_default_fieldname(base_field.name)
|
||||
if not fieldname:
|
||||
return
|
||||
# if the field is inherited, it's a related to its delegated model
|
||||
# (inherits), we want to override it with a new one
|
||||
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,
|
||||
})
|
||||
|
||||
if hasattr(base_field, 'selection'):
|
||||
field_args['selection'] = base_field.selection
|
||||
field = base_field_cls(**field_args)
|
||||
self._add_field(fieldname, field)
|
||||
|
||||
@api.model
|
||||
def _setup_base(self):
|
||||
super()._setup_base()
|
||||
for fieldname in self._server_env_fields:
|
||||
field = self._fields[fieldname]
|
||||
self._server_env_add_default_field(field)
|
||||
self._server_env_transform_field_to_read_from_env(field)
|
||||
self._server_env_add_is_editable_field(field)
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import logging
|
||||
import os
|
||||
import configparser
|
||||
from lxml import etree
|
||||
|
|
@ -28,8 +29,15 @@ from odoo.tools.config import config as system_base_config
|
|||
|
||||
from .system_info import get_server_environment
|
||||
|
||||
from odoo.addons import server_environment_files
|
||||
_dir = os.path.dirname(server_environment_files.__file__)
|
||||
_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')
|
||||
_dir = None
|
||||
|
||||
ENV_VAR_NAMES = ('SERVER_ENV_CONFIG', 'SERVER_ENV_CONFIG_SECRET')
|
||||
|
||||
|
|
@ -46,9 +54,11 @@ if not system_base_config.get('running_env', False):
|
|||
"[options]\nrunning_env = dev"
|
||||
)
|
||||
|
||||
ck_path = os.path.join(_dir, system_base_config['running_env'])
|
||||
ck_path = None
|
||||
if _dir:
|
||||
ck_path = os.path.join(_dir, system_base_config['running_env'])
|
||||
|
||||
if not os.path.exists(ck_path):
|
||||
if not os.path.exists(ck_path):
|
||||
raise Exception(
|
||||
"Provided server environment does not exist, "
|
||||
"please add a folder %s" % ck_path
|
||||
|
|
@ -82,8 +92,7 @@ def _listconf(env_path):
|
|||
return files
|
||||
|
||||
|
||||
def _load_config():
|
||||
"""Load the configuration and return a ConfigParser instance."""
|
||||
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'])
|
||||
|
|
@ -92,17 +101,18 @@ def _load_config():
|
|||
else:
|
||||
conf_files = _listconf(running_env)
|
||||
|
||||
config_p = configparser.SafeConfigParser()
|
||||
# options are case-sensitive
|
||||
config_p.optionxform = str
|
||||
try:
|
||||
config_p.read(conf_files)
|
||||
except Exception as e:
|
||||
raise Exception('Cannot read config files "%s": %s' % (conf_files, e))
|
||||
|
||||
|
||||
def _load_config_from_rcfile(config_p):
|
||||
config_p.read(system_base_config.rcfile)
|
||||
config_p.remove_section('options')
|
||||
|
||||
|
||||
def _load_config_from_env(config_p):
|
||||
for varname in ENV_VAR_NAMES:
|
||||
env_config = os.getenv(varname)
|
||||
if env_config:
|
||||
|
|
@ -114,6 +124,17 @@ def _load_config():
|
|||
% (varname, err,)
|
||||
)
|
||||
|
||||
|
||||
def _load_config():
|
||||
"""Load the configuration and return a ConfigParser instance."""
|
||||
config_p = configparser.SafeConfigParser()
|
||||
# options are case-sensitive
|
||||
config_p.optionxform = str
|
||||
|
||||
if _dir:
|
||||
_load_config_from_server_env_files(config_p)
|
||||
_load_config_from_rcfile(config_p)
|
||||
_load_config_from_env(config_p)
|
||||
return config_p
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -18,3 +18,4 @@
|
|||
#
|
||||
##############################################################################
|
||||
from . import test_server_environment
|
||||
from . import test_environment_variable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
# Copyright 2018 Camptocamp (https://www.camptocamp.com).
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import os
|
||||
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
|
||||
|
||||
|
||||
class ServerEnvironmentCase(common.SavepointCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self._original_running_env = config.get('running_env')
|
||||
config['running_env'] = 'testing'
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
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)
|
||||
server_env._dir = path
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
server_env._dir = original_dir
|
||||
|
||||
@contextmanager
|
||||
def set_env_variables(self, public=None, secret=None):
|
||||
newkeys = {}
|
||||
if public:
|
||||
newkeys['SERVER_ENV_CONFIG'] = public
|
||||
if secret:
|
||||
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):
|
||||
parser = server_env._load_config()
|
||||
server_env_mixin.serv_config = parser
|
||||
yield
|
||||
|
||||
finally:
|
||||
server_env_mixin.serv_config = original_serv_config
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# Copyright 2018 Camptocamp (https://www.camptocamp.com).
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
|
||||
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):
|
||||
parser = server_env._load_config()
|
||||
self.assertEqual(
|
||||
list(parser.keys()),
|
||||
['DEFAULT', 'section']
|
||||
)
|
||||
self.assertDictEqual(
|
||||
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):
|
||||
parser = server_env._load_config()
|
||||
val = parser.get('external_service.ftp', 'user')
|
||||
self.assertEqual(val, 'foo')
|
||||
|
|
@ -17,11 +17,11 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from odoo.tests import common
|
||||
from odoo.addons.server_environment import serv_config
|
||||
from odoo.addons.server_environment import server_env
|
||||
from . import common
|
||||
|
||||
|
||||
class TestEnv(common.TransactionCase):
|
||||
class TestEnv(common.ServerEnvironmentCase):
|
||||
|
||||
def test_view(self):
|
||||
model = self.env['server.config']
|
||||
|
|
@ -43,5 +43,9 @@ class TestEnv(common.TransactionCase):
|
|||
self.assertTrue(pass_checked)
|
||||
|
||||
def test_value_retrival(self):
|
||||
val = serv_config.get('external_service.ftp', 'user')
|
||||
self.assertEqual(val, 'toto')
|
||||
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')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
[external_service.ftp]
|
||||
host = sftp.example.com
|
||||
user = foo
|
||||
password = bar
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
[external_service.ftp]
|
||||
user = testing
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||
:alt: License: AGPL-3
|
||||
|
||||
=======================
|
||||
Test Server Environment
|
||||
=======================
|
||||
|
||||
This addon is not meant to be used. It extends the Odoo Models
|
||||
in order to run automated tests on the Server Environment module.
|
||||
|
||||
Same basic tests are integrated within the ``server_environment`` addon.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
This module only contains Python tests.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Images
|
||||
------
|
||||
|
||||
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
||||
.. image:: https://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: https://odoo-community.org
|
||||
|
||||
This module is maintained by the OCA.
|
||||
|
||||
OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.
|
||||
|
||||
To contribute to this module, please visit https://odoo-community.org.
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import models
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Copyright 2018 Camptocamp (https://www.camptocamp.com).
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
"name": "Automated tests for server environment - technical",
|
||||
"summary": "Used to run automated tests, do not install",
|
||||
"version": "11.0.1.0.0",
|
||||
"depends": [
|
||||
"server_environment",
|
||||
],
|
||||
"author": "Camptocamp,Odoo Community Association (OCA)",
|
||||
"website": "http://odoo-community.org/",
|
||||
"license": "AGPL-3",
|
||||
"category": "Tools",
|
||||
"data": [
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import server_env_test
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
# Copyright 2018 Camptocamp (https://www.camptocamp.com).
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
""" Models used for testing server_environment
|
||||
|
||||
Create models that will be used in tests.
|
||||
|
||||
"""
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ServerEnvTest(models.Model):
|
||||
_name = 'server.env.test'
|
||||
_description = 'Server Environment Test Model'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
# if the original field is required, it must not
|
||||
# be required anymore as we set it with config
|
||||
host = fields.Char(required=True)
|
||||
port = fields.Integer()
|
||||
user = fields.Char()
|
||||
password = fields.Char()
|
||||
ssl = fields.Boolean()
|
||||
|
||||
# we'll use these ones to stress the custom
|
||||
# compute/inverse for the default value
|
||||
alias = fields.Char()
|
||||
alias_default = fields.Char()
|
||||
|
||||
|
||||
# Intentionally re-declares a class to stress the inclusion of the mixin
|
||||
class ServerEnvTestWithMixin(models.Model):
|
||||
_name = 'server.env.test'
|
||||
_inherit = ['server.env.test', 'server.env.mixin']
|
||||
|
||||
@property
|
||||
def _server_env_fields(self):
|
||||
base_fields = super()._server_env_fields
|
||||
sftp_fields = {
|
||||
"host": {},
|
||||
"port": {},
|
||||
"user": {},
|
||||
"password": {},
|
||||
"ssl": {},
|
||||
"alias": {
|
||||
"no_default_field": True,
|
||||
"compute_default": "_compute_alias_default",
|
||||
"inverse_default": "_inverse_alias_default",
|
||||
}
|
||||
}
|
||||
sftp_fields.update(base_fields)
|
||||
return sftp_fields
|
||||
|
||||
def _compute_alias_default(self):
|
||||
for record in self:
|
||||
record.alias = record.alias_default
|
||||
|
||||
def _inverse_alias_default(self):
|
||||
for record in self:
|
||||
record.alias_default = record.alias
|
||||
|
||||
|
||||
class ServerEnvTest2(models.Model):
|
||||
_name = 'server.env.test2'
|
||||
_description = 'Server Environment Test Model 2'
|
||||
# applied directly on the model
|
||||
_inherit = 'server.env.mixin'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
host = fields.Char()
|
||||
|
||||
@property
|
||||
def _server_env_fields(self):
|
||||
base_fields = super()._server_env_fields
|
||||
sftp_fields = {
|
||||
"host": {},
|
||||
}
|
||||
sftp_fields.update(base_fields)
|
||||
return sftp_fields
|
||||
|
||||
|
||||
class ServerEnvTestInherits1(models.Model):
|
||||
_name = 'server.env.test.inherits1'
|
||||
_description = 'Server Environment Test Model Inherits'
|
||||
|
||||
base_id = fields.Many2one(
|
||||
comodel_name='server.env.test',
|
||||
delegate=True,
|
||||
required=True,
|
||||
)
|
||||
# host is not redefined, handled by the delegated model
|
||||
|
||||
|
||||
class ServerEnvTestInherits2(models.Model):
|
||||
_name = 'server.env.test.inherits2'
|
||||
_description = 'Server Environment Test Model Inherits'
|
||||
# if you want to benefit from mixin in an inherits,
|
||||
# even if the parent includes it, you have to
|
||||
# add the inheritance here as well
|
||||
_inherit = 'server.env.mixin'
|
||||
|
||||
base_id = fields.Many2one(
|
||||
comodel_name='server.env.test',
|
||||
delegate=True,
|
||||
required=True,
|
||||
)
|
||||
host = fields.Char()
|
||||
|
||||
@property
|
||||
def _server_env_fields(self):
|
||||
base_fields = super()._server_env_fields
|
||||
sftp_fields = {
|
||||
"host": {},
|
||||
}
|
||||
sftp_fields.update(base_fields)
|
||||
return sftp_fields
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_server_env_test,access_server_env_test,model_server_env_test,,1,0,0,0
|
||||
access_server_env_test2,access_server_env_test2,model_server_env_test2,,1,0,0,0
|
||||
access_server_env_test_inherits1,access_server_env_test_inherits1,model_server_env_test_inherits1,,1,0,0,0
|
||||
access_server_env_test_inherits2,access_server_env_test_inherits2,model_server_env_test_inherits2,,1,0,0,0
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
from . import test_server_env_mixin
|
||||
from . import test_server_env_mixin_inherit
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
# Copyright 2018 Camptocamp (https://www.camptocamp.com).
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo.addons.server_environment.tests.common import ServerEnvironmentCase
|
||||
|
||||
|
||||
class TestServerEnvMixin(ServerEnvironmentCase):
|
||||
|
||||
def test_env_computed_fields_read(self):
|
||||
"""Read values from the config in env-computed fields"""
|
||||
public = (
|
||||
# global for all server.env.test records
|
||||
"[server_env_test]\n"
|
||||
"ssl=1\n"
|
||||
# for our server.env.test test record now
|
||||
"[server_env_test.foo]\n"
|
||||
"host=test.example.com\n"
|
||||
"port=21\n"
|
||||
"user=foo\n"
|
||||
)
|
||||
secret = (
|
||||
"[server_env_test.foo]\n"
|
||||
"password=bar\n"
|
||||
)
|
||||
# we can create the record even if we didn't provide
|
||||
# the field host which was required
|
||||
foo = self.env['server.env.test'].create({
|
||||
'name': 'foo',
|
||||
})
|
||||
with self.load_config(public, secret):
|
||||
self.assertEqual(foo.name, 'foo')
|
||||
self.assertEqual(foo.host, 'test.example.com')
|
||||
self.assertEqual(foo.port, 21)
|
||||
self.assertEqual(foo.user, 'foo')
|
||||
self.assertEqual(foo.password, 'bar')
|
||||
self.assertTrue(foo.ssl)
|
||||
|
||||
def test_env_computed_fields_read_multi(self):
|
||||
"""Read values in env-computed fields on several records"""
|
||||
public = (
|
||||
"[server_env_test]\n"
|
||||
"host=test.example.com\n"
|
||||
"port=21\n"
|
||||
"user=foo\n"
|
||||
)
|
||||
# we can create the record even if we didn't provide
|
||||
# the field host which was required
|
||||
foo = self.env['server.env.test'].create({
|
||||
'name': 'foo',
|
||||
})
|
||||
foo2 = self.env['server.env.test'].create({
|
||||
'name': 'foo2',
|
||||
})
|
||||
foos = foo + foo2
|
||||
with self.load_config(public):
|
||||
foos._compute_server_env()
|
||||
|
||||
def test_env_computed_fields_write(self):
|
||||
"""Env-computed fields without key in config can be written"""
|
||||
public = (
|
||||
# for our server.env.test test record now
|
||||
"[server_env_test.foo]\n"
|
||||
"host=test.example.com\n"
|
||||
"port=21\n"
|
||||
)
|
||||
secret = (
|
||||
"[server_env_test.foo]\n"
|
||||
"password=bar\n"
|
||||
)
|
||||
# we can create the record even if we didn't provide
|
||||
# the field host which was required
|
||||
foo = self.env['server.env.test'].create({
|
||||
'name': 'foo',
|
||||
})
|
||||
with self.load_config(public, secret):
|
||||
self.assertEqual(foo.host, 'test.example.com')
|
||||
self.assertFalse(foo.host_env_is_editable)
|
||||
self.assertEqual(foo.port, 21)
|
||||
self.assertFalse(foo.port_env_is_editable)
|
||||
self.assertEqual(foo.password, 'bar')
|
||||
self.assertFalse(foo.password_env_is_editable)
|
||||
|
||||
self.assertFalse(foo.user)
|
||||
self.assertTrue(foo.user_env_is_editable)
|
||||
self.assertFalse(foo.ssl)
|
||||
self.assertTrue(foo.ssl_env_is_editable)
|
||||
|
||||
# field set in config, no effect
|
||||
foo.host = 'new.example.com'
|
||||
self.assertFalse(foo.host_env_default)
|
||||
|
||||
# fields not set in config, written
|
||||
foo.user = 'dummy'
|
||||
self.assertEqual(foo.user_env_default, 'dummy')
|
||||
foo.ssl = True
|
||||
self.assertTrue(foo.ssl_env_default)
|
||||
|
||||
def test_env_computed_default(self):
|
||||
"""Env-computed fields read from default fields"""
|
||||
# we can create the record even if we didn't provide
|
||||
# the field host which was required
|
||||
foo = self.env['server.env.test'].create({
|
||||
'name': 'foo',
|
||||
})
|
||||
# empty files
|
||||
with self.load_config():
|
||||
self.assertFalse(foo.host)
|
||||
self.assertFalse(foo.port)
|
||||
self.assertFalse(foo.password)
|
||||
self.assertFalse(foo.user)
|
||||
self.assertFalse(foo.ssl)
|
||||
|
||||
self.assertTrue(foo.host_env_is_editable)
|
||||
self.assertTrue(foo.port_env_is_editable)
|
||||
self.assertTrue(foo.password_env_is_editable)
|
||||
self.assertTrue(foo.user_env_is_editable)
|
||||
self.assertTrue(foo.ssl_env_is_editable)
|
||||
|
||||
foo.write({
|
||||
'host_env_default': 'test.example.com',
|
||||
'port_env_default': 21,
|
||||
'password_env_default': 'bar',
|
||||
'user_env_default': 'foo',
|
||||
'ssl_env_default': True,
|
||||
})
|
||||
|
||||
# refresh env-computed fields, it should read from
|
||||
# the default fields
|
||||
foo.invalidate_cache()
|
||||
self.assertEqual(foo.host, 'test.example.com')
|
||||
self.assertEqual(foo.port, 21)
|
||||
self.assertEqual(foo.user, 'foo')
|
||||
self.assertEqual(foo.password, 'bar')
|
||||
self.assertTrue(foo.ssl)
|
||||
|
||||
def test_env_custom_compute_method(self):
|
||||
"""Can customize compute/inverse methods"""
|
||||
foo = self.env['server.env.test'].create({
|
||||
'name': 'foo',
|
||||
})
|
||||
self.assertNotIn('alias_env_default', foo._fields)
|
||||
with self.load_config():
|
||||
self.assertTrue(foo.alias_env_is_editable)
|
||||
|
||||
foo.alias = 'test'
|
||||
self.assertEqual(foo.alias_default, 'test')
|
||||
|
||||
foo = self.env['server.env.test'].create({
|
||||
'name': 'foo_with_default',
|
||||
})
|
||||
with self.load_config():
|
||||
self.assertTrue(foo.alias_env_is_editable)
|
||||
|
||||
foo.alias_default = 'new_value'
|
||||
self.assertEqual(foo.alias, 'new_value')
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
# Copyright 2018 Camptocamp (https://www.camptocamp.com).
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo.addons.server_environment.tests.common import ServerEnvironmentCase
|
||||
|
||||
|
||||
class TestServerEnvMixinSameFieldName(ServerEnvironmentCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.public = (
|
||||
# global for all server.env.test records
|
||||
"[server_env_test]\n"
|
||||
"host=global_value\n"
|
||||
# for our server.env.test test record now
|
||||
"[server_env_test.foo]\n"
|
||||
"host=foo_value\n"
|
||||
# for our server.env.test2 test record now
|
||||
"[server_env_test2.foo]\n"
|
||||
"host=foo2_value\n"
|
||||
)
|
||||
cls.foo = cls.env['server.env.test'].create({'name': 'foo'})
|
||||
cls.foo2 = cls.env['server.env.test2'].create({
|
||||
'name': 'foo',
|
||||
})
|
||||
|
||||
def test_env_computed_fields_read(self):
|
||||
"""Read values from the config in env-computed fields"""
|
||||
with self.load_config(self.public):
|
||||
self.assertEqual(self.foo.name, 'foo')
|
||||
self.assertEqual(self.foo2.name, 'foo')
|
||||
self.assertEqual(self.foo.host, 'foo_value')
|
||||
self.assertEqual(self.foo2.host, 'foo2_value')
|
||||
|
||||
def test_env_computed_fields_not_editable(self):
|
||||
"""Env-computed fields without key in config can be written"""
|
||||
# we can create the record even if we didn't provide
|
||||
# the field host which was required
|
||||
with self.load_config(self.public):
|
||||
self.assertEqual(self.foo.host, 'foo_value')
|
||||
self.assertFalse(self.foo.host_env_is_editable)
|
||||
self.assertEqual(self.foo2.host, 'foo2_value')
|
||||
self.assertFalse(self.foo2.host_env_is_editable)
|
||||
|
||||
def test_env_computed_fields_editable(self):
|
||||
"""Env-computed fields without key in config can be written"""
|
||||
# we can create the record even if we didn't provide
|
||||
# the field host which was required
|
||||
with self.load_config():
|
||||
self.assertFalse(self.foo.host)
|
||||
self.assertTrue(self.foo.host_env_is_editable)
|
||||
self.assertFalse(self.foo2.host)
|
||||
self.assertTrue(self.foo2.host_env_is_editable)
|
||||
|
||||
self.foo.host_env_default = 'foo_value'
|
||||
self.foo.invalidate_cache()
|
||||
self.assertEqual(self.foo.host, 'foo_value')
|
||||
|
||||
self.foo2.host_env_default = 'foo2_value'
|
||||
self.foo2.invalidate_cache()
|
||||
self.assertEqual(self.foo2.host, 'foo2_value')
|
||||
|
||||
self.foo.host = 'foo_new_value'
|
||||
self.foo.invalidate_cache()
|
||||
self.assertEqual(self.foo.host, 'foo_new_value')
|
||||
|
||||
self.foo2.host = 'foo2_new_value'
|
||||
self.foo2.invalidate_cache()
|
||||
self.assertEqual(self.foo2.host, 'foo2_new_value')
|
||||
|
||||
|
||||
class TestServerEnvMixinInherits(ServerEnvironmentCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.public = (
|
||||
# global for all server.env.test records
|
||||
"[server_env_test]\n"
|
||||
"host=global_value\n"
|
||||
# for our server.env.test test record now
|
||||
"[server_env_test.foo]\n"
|
||||
"host=foo_value\n"
|
||||
# for our server.env.test.inherits1 test record now
|
||||
"[server_env_test_inherits1.foo]\n"
|
||||
"host=foo_inherits_value\n"
|
||||
# for our server.env.test.inherits2 test record now
|
||||
"[server_env_test_inherits2.foo]\n"
|
||||
"host=foo_inherits_value\n"
|
||||
)
|
||||
cls.foo = cls.env['server.env.test'].create({'name': 'foo'})
|
||||
cls.foo_inh1 = cls.env['server.env.test.inherits1'].create({
|
||||
'name': 'foo'
|
||||
})
|
||||
cls.foo_inh2 = cls.env['server.env.test.inherits2'].create({
|
||||
'name': 'foo'
|
||||
})
|
||||
|
||||
def test_env_computed_fields_read(self):
|
||||
"""Read values from the config in env-computed fields"""
|
||||
with self.load_config(self.public):
|
||||
self.assertEqual(self.foo.name, 'foo')
|
||||
self.assertEqual(self.foo_inh1.name, 'foo')
|
||||
self.assertEqual(self.foo_inh2.name, 'foo')
|
||||
self.assertEqual(self.foo.host, 'foo_value')
|
||||
# inh1 does not redefine the host field so has the
|
||||
# same value than the parent record (delegate)
|
||||
self.assertEqual(self.foo_inh1.host, 'foo_value')
|
||||
# inh2 redefines self.the host field so has its own value
|
||||
self.assertEqual(self.foo_inh2.host, 'foo_inherits_value')
|
||||
|
||||
def test_env_computed_fields_not_editable(self):
|
||||
"""Env-computed fields without key in config can be written"""
|
||||
with self.load_config(self.public):
|
||||
self.assertEqual(self.foo.host, 'foo_value')
|
||||
self.assertFalse(self.foo.host_env_is_editable)
|
||||
self.assertEqual(self.foo_inh1.host, 'foo_value')
|
||||
self.assertFalse(self.foo_inh1.host_env_is_editable)
|
||||
self.assertEqual(self.foo_inh2.host, 'foo_inherits_value')
|
||||
self.assertFalse(self.foo_inh2.host_env_is_editable)
|
||||
|
||||
def test_env_computed_fields_editable(self):
|
||||
"""Env-computed fields without key in config can be written"""
|
||||
with self.load_config():
|
||||
self.assertFalse(self.foo.host)
|
||||
self.assertTrue(self.foo.host_env_is_editable)
|
||||
self.assertFalse(self.foo_inh1.host)
|
||||
self.assertTrue(self.foo_inh1.host_env_is_editable)
|
||||
self.assertFalse(self.foo_inh2.host)
|
||||
self.assertTrue(self.foo_inh2.host_env_is_editable)
|
||||
|
||||
self.foo.host_env_default = 'foo_value'
|
||||
self.foo.invalidate_cache()
|
||||
self.assertEqual(self.foo.host, 'foo_value')
|
||||
|
||||
self.foo.host = 'foo_new_value'
|
||||
self.foo.invalidate_cache()
|
||||
self.assertEqual(self.foo.host, 'foo_new_value')
|
||||
|
||||
self.foo_inh1.host_env_default = 'foo2_value'
|
||||
self.foo_inh1.invalidate_cache()
|
||||
self.assertEqual(self.foo_inh1.host, 'foo2_value')
|
||||
self.assertEqual(self.foo_inh1.base_id.host, 'foo2_value')
|
||||
|
||||
self.foo_inh1.host = 'foo2_new_value'
|
||||
self.foo_inh1.invalidate_cache()
|
||||
self.assertEqual(self.foo_inh1.host, 'foo2_new_value')
|
||||
self.assertEqual(self.foo_inh1.base_id.host, 'foo2_new_value')
|
||||
|
||||
self.foo_inh2.host_env_default = 'foo_inherits_value'
|
||||
self.foo_inh2.base_id.host_env_default = 'bar_value'
|
||||
self.foo_inh2.invalidate_cache()
|
||||
self.foo_inh2.base_id.invalidate_cache()
|
||||
self.assertEqual(self.foo_inh2.host, 'foo_inherits_value')
|
||||
self.assertEqual(self.foo_inh2.base_id.host, 'bar_value')
|
||||
|
||||
self.foo_inh2.host = 'foo_inherits_new_value'
|
||||
self.foo_inh2.base_id.host = 'bar_new_value'
|
||||
self.foo_inh2.invalidate_cache()
|
||||
self.foo_inh2.base_id.invalidate_cache()
|
||||
self.assertEqual(self.foo_inh2.host, 'foo_inherits_new_value')
|
||||
self.assertEqual(self.foo_inh2.base_id.host, 'bar_new_value')
|
||||
Loading…
Reference in New Issue