From a0dd81bfab7078ee0eb5889f449eb3e3bf7391a5 Mon Sep 17 00:00:00 2001 From: Richard Mathot Date: Thu, 16 Nov 2017 17:39:28 +0100 Subject: [PATCH] [IMP] base: add native support for ir.config_parameter in res.config Res config currently have some native behavior to support features like default_XX (setting global default value), group_XX (adding implied groups) and module_XX (installing or removing modules). This commit add support of config_parameter attribute on other fields that automatically links a config parameter to the field. It allows to hide the set and get of config parameter and their value conversion as everything is now handled by the base settings class. Thanks to @rco-odoo for his help and review when working on this feature. --- odoo/addons/base/models/res_config.py | 74 ++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/odoo/addons/base/models/res_config.py b/odoo/addons/base/models/res_config.py index 81e62bdf2d8..f0b2622c355 100644 --- a/odoo/addons/base/models/res_config.py +++ b/odoo/addons/base/models/res_config.py @@ -4,10 +4,9 @@ import json import logging import re -from operator import attrgetter, add from lxml import etree -from odoo import api, models, registry, SUPERUSER_ID, _ +from odoo import api, models, _ from odoo.exceptions import AccessError, RedirectWarning, UserError from odoo.tools import ustr @@ -341,6 +340,7 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) default_foo = fields.type(..., default_model='my.model'), group_bar = fields.Boolean(..., group='base.group_user', implied_group='my.group'), module_baz = fields.Boolean(...), + config_qux = fields.Char(..., config_parameter='my.parameter') other_field = fields.type(...), The method ``execute`` provides some support based on a naming convention: @@ -366,14 +366,18 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) ``execute`` triggers the immediate installation of the module named 'XXX' if the field has the integer value ``1``. + * For a field with no specific prefix BUT an attribute 'config_parameter', + ``execute``` will save its value in an ir.config.parameter (global setting for the + database). + * For the other fields, the method ``execute`` invokes all methods with a name that starts with 'set_'; such methods can be defined to implement the effect of those fields. The method ``default_get`` retrieves values that reflect the current status of the - fields like 'default_XXX', 'group_XXX' and 'module_XXX'. It also invokes all methods - with a name that starts with 'get_default_'; such methods can be defined to provide - current values for other fields. + fields like 'default_XXX', 'group_XXX', 'module_XXX' and config_XXX. + It also invokes all methods with a name that starts with 'get_default_'; + such methods can be defined to provide current values for other fields. """ _name = 'res.config.settings' @@ -437,6 +441,7 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) { 'default': [('default_foo', 'model', 'foo'), ...], 'group': [('group_bar', [browse_group], browse_implied_group), ...], 'module': [('module_baz', browse_module), ...], + 'config': [('config_qux', 'my.parameter'), ...], 'other': ['other_field', ...], } """ @@ -444,7 +449,7 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) Groups = self.env['res.groups'] ref = self.env.ref - defaults, groups, modules, others = [], [], [], [] + defaults, groups, modules, configs, others = [], [], [], [], [] for name, field in self._fields.items(): if name.startswith('default_') and hasattr(field, 'default_model'): defaults.append((name, field.default_model, name[8:])) @@ -456,10 +461,18 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) elif name.startswith('module_') and field.type in ('boolean', 'selection'): module = IrModule.sudo().search([('name', '=', name[7:])], limit=1) modules.append((name, module)) + elif hasattr(field, 'config_parameter'): + if field.type in ('boolean', 'char', 'selection', 'many2one', 'integer', 'float'): + configs.append((name, field.config_parameter)) + else: + msg = "Error with field %s !" \ + "Automatic ir.config_parameter getting/setting is not implemented for %s's." \ + % (name, field.type) + raise Exception(msg) else: others.append(name) - return {'default': defaults, 'group': groups, 'module': modules, 'other': others} + return {'default': defaults, 'group': groups, 'module': modules, 'config': configs, 'other': others} def get_values(self): """ @@ -470,6 +483,7 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) @api.model def default_get(self, fields): IrDefault = self.env['ir.default'] + IrConfigParameter = self.env['ir.config_parameter'].sudo() classified = self._get_classified_fields() res = super(ResConfigSettings, self).default_get(fields) @@ -492,6 +506,36 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) if self._fields[name].type == 'selection': res[name] = int(res[name]) + # config: get & convert stored ir.config_parameter (or default) + WARNING_MESSAGE = "Error when converting value %r of field %s for ir.config.parameter %r" + for name, icp in classified['config']: + field = self._fields[name] + value = IrConfigParameter.get_param(icp, field.default(self) if field.default else False) + if value is not False: + if field.type == 'many2one': + try: + # Special case when value is the id of a deleted record, we do not want to + # block the settings screen + value = self.env[field.comodel_name].browse(int(value)).exists().id + except ValueError: + _logger.warning(WARNING_MESSAGE, value, field, icp) + value = False + elif field.type == 'integer': + try: + value = int(value) + except ValueError: + _logger.warning(WARNING_MESSAGE, value, field, icp) + value = 0 + elif field.type == 'float': + try: + value = float(value) + except ValueError: + _logger.warning(WARNING_MESSAGE, value, field, icp) + value = 0.0 + elif field.type == 'boolean': + value = value.lower() == 'true' + res[name] = value + # other fields: call the method 'get_values' # The other methods that start with `get_default_` are deprecated for method in dir(self): @@ -536,6 +580,22 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) groups.write({'implied_ids': [(3, implied_group.id)]}) implied_group.write({'users': [(3, user.id) for user in groups.mapped('users')]}) + # config fields: store ir.config_parameters + IrConfigParameter = self.env['ir.config_parameter'].sudo() + for name, icp in classified['config']: + field = self._fields[name] + value = self[name] + if field.type == 'char': + # storing developer keys as ir.config_parameter may lead to nasty + # bugs when users leave spaces around them + value = (value or "").strip() or False + elif field.type in ('integer', 'float'): + value = repr(value) if value else False + elif field.type == 'many2one': + # value is a (possibly empty) recordset + value = value.id + IrConfigParameter.set_param(icp, value) + # other fields: execute method 'set_values' # Methods that start with `set_` are now deprecated for method in dir(self):