diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py index 1d1b0476337..b8bdb9787b3 100644 --- a/addons/auth_signup/controllers/main.py +++ b/addons/auth_signup/controllers/main.py @@ -93,7 +93,7 @@ class AuthSignupHome(Home): get_param = request.env['ir.config_parameter'].sudo().get_param return { - 'signup_enabled': get_param('auth_signup.allow_uninvited') == 'True', + 'signup_enabled': get_param('auth_signup.invitation_scope', 'b2b') == 'b2c', 'reset_password_enabled': get_param('auth_signup.reset_password') == 'True', } diff --git a/addons/auth_signup/models/res_config_settings.py b/addons/auth_signup/models/res_config_settings.py index 7350121137b..9cfceecaecd 100644 --- a/addons/auth_signup/models/res_config_settings.py +++ b/addons/auth_signup/models/res_config_settings.py @@ -9,36 +9,13 @@ from odoo import api, fields, models class ResConfigSettings(models.TransientModel): _inherit = 'res.config.settings' - auth_signup_reset_password = fields.Boolean(string='Enable password reset from Login page') + auth_signup_reset_password = fields.Boolean(string='Enable password reset from Login page', config_parameter='auth_signup.reset_password') auth_signup_uninvited = fields.Selection([ ('b2b', 'On invitation (B2B)'), ('b2c', 'Free sign up (B2C)'), - ], string='Customer Account') - auth_signup_template_user_id = fields.Many2one('res.users', string='Template user for new users created through signup') - - @api.model - def get_values(self): - res = super(ResConfigSettings, self).get_values() - get_param = self.env['ir.config_parameter'].sudo().get_param - auth_signup_template_user_id = literal_eval(get_param('auth_signup.template_user_id', default='False')) - if auth_signup_template_user_id and not self.env['res.users'].sudo().browse(auth_signup_template_user_id).exists(): - auth_signup_template_user_id = False - # the value of the parameter is a nonempty string - res.update( - auth_signup_reset_password=get_param('auth_signup.reset_password', 'False').lower() == 'true', - auth_signup_uninvited='b2c' if get_param('auth_signup.allow_uninvited', 'False').lower() == 'true' else 'b2b', - auth_signup_template_user_id=auth_signup_template_user_id, - ) - return res - - @api.multi - def set_values(self): - super(ResConfigSettings, self).set_values() - set_param = self.env['ir.config_parameter'].sudo().set_param - # we store the repr of the values, since the value of the parameter is a required string - set_param('auth_signup.reset_password', repr(self.auth_signup_reset_password)) - set_param('auth_signup.allow_uninvited', repr(self.auth_signup_uninvited == 'b2c')) - set_param('auth_signup.template_user_id', repr(self.auth_signup_template_user_id.id)) + ], string='Customer Account', default='b2b', config_parameter='auth_signup.invitation_scope') + auth_signup_template_user_id = fields.Many2one('res.users', string='Template user for new users created through signup', + config_parameter='auth_signup.template_user_id') @api.multi def open_template_user(self): diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py index ecf63246580..9fbcd27872f 100644 --- a/addons/auth_signup/models/res_partner.py +++ b/addons/auth_signup/models/res_partner.py @@ -102,7 +102,7 @@ class ResPartner(models.Model): """ res = defaultdict(dict) - allow_signup = self.env['ir.config_parameter'].sudo().get_param('auth_signup.allow_uninvited', 'False').lower() == 'true' + allow_signup = self.env['ir.config_parameter'].sudo().get_param('auth_signup.invitation_scope', 'b2b') == 'b2c' for partner in self: if allow_signup and not partner.user_ids: partner.signup_prepare() diff --git a/addons/auth_signup/models/res_users.py b/addons/auth_signup/models/res_users.py index ceab0701772..c91cf78a3fc 100644 --- a/addons/auth_signup/models/res_users.py +++ b/addons/auth_signup/models/res_users.py @@ -84,7 +84,7 @@ class ResUsers(models.Model): # check that uninvited users may sign up if 'partner_id' not in values: - if not literal_eval(get_param('auth_signup.allow_uninvited', 'False')): + if get_param('auth_signup.invitation_scope', 'b2b') != 'b2c': raise SignupError(_('Signup is not allowed for uninvited users')) assert values.get('login'), "Signup: no login given for new user" diff --git a/addons/base_setup/models/res_config_settings.py b/addons/base_setup/models/res_config_settings.py index e925bac6a05..063ebf6a829 100644 --- a/addons/base_setup/models/res_config_settings.py +++ b/addons/base_setup/models/res_config_settings.py @@ -11,8 +11,14 @@ class ResConfigSettings(models.TransientModel): group_multi_company = fields.Boolean("Manage multiple companies", implied_group='base.group_multi_company') company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.user.company_id) - default_user_rights = fields.Boolean("Default Access Rights") - default_external_email_server = fields.Boolean("External Email Servers") + user_default_rights = fields.Boolean( + "Default Access Rights", + config_parameter='base_setup.default_user_rights', + oldname='default_user_rights') + external_email_server_default = fields.Boolean( + "External Email Servers", + config_parameter='base_setup.default_external_email_server', + oldname='default_external_email_server') module_base_import = fields.Boolean("Allow users to import data from CSV/XLS/XLSX/ODS files") module_google_calendar = fields.Boolean( string='Allow the users to synchronize their calendar with Google Calendar') @@ -28,7 +34,6 @@ class ResConfigSettings(models.TransientModel): help="Share your partners to all companies defined in your instance.\n" " * Checked : Partners are visible for every companies, even if a company is defined on the partner.\n" " * Unchecked : Each company can see only its partner (partners where company is defined). Partners not related to a company are visible for all companies.") - default_custom_report_footer = fields.Boolean("Default Custom Report Footer") report_footer = fields.Text(related="company_id.report_footer", string='Custom Report Footer', help="Footer text displayed at the bottom of all reports.") group_multi_currency = fields.Boolean(string='Multi-Currencies', implied_group='base.group_multi_currency', @@ -39,14 +44,7 @@ class ResConfigSettings(models.TransientModel): @api.model def get_values(self): res = super(ResConfigSettings, self).get_values() - params = self.env['ir.config_parameter'].sudo() - default_external_email_server = params.get_param('base_setup.default_external_email_server', default=False) - default_user_rights = params.get_param('base_setup.default_user_rights', default=False) - default_custom_report_footer = params.get_param('base_setup.default_custom_report_footer', default=False) res.update( - default_external_email_server=default_external_email_server, - default_user_rights=default_user_rights, - default_custom_report_footer=default_custom_report_footer, company_share_partner=not self.env.ref('base.res_partner_rule').active, ) return res @@ -54,9 +52,6 @@ class ResConfigSettings(models.TransientModel): @api.multi def set_values(self): super(ResConfigSettings, self).set_values() - self.env['ir.config_parameter'].sudo().set_param("base_setup.default_external_email_server", self.default_external_email_server) - self.env['ir.config_parameter'].sudo().set_param("base_setup.default_user_rights", self.default_user_rights) - self.env['ir.config_parameter'].sudo().set_param("base_setup.default_custom_report_footer", self.default_custom_report_footer) self.env.ref('base.res_partner_rule').write({'active': not self.company_share_partner}) @api.multi diff --git a/addons/base_setup/views/res_config_settings_views.xml b/addons/base_setup/views/res_config_settings_views.xml index 12b60ef5914..a58f3e0d808 100644 --- a/addons/base_setup/views/res_config_settings_views.xml +++ b/addons/base_setup/views/res_config_settings_views.xml @@ -50,17 +50,17 @@
Sign Up Now to Follow Your Order diff --git a/addons/website_slides/models/res_config_settings.py b/addons/website_slides/models/res_config_settings.py index 65c8604bc53..7021191249e 100644 --- a/addons/website_slides/models/res_config_settings.py +++ b/addons/website_slides/models/res_config_settings.py @@ -7,17 +7,4 @@ from odoo import api, fields, models class ResConfigSettings(models.TransientModel): _inherit = "res.config.settings" - website_slide_google_app_key = fields.Char(string='Google Doc Key') - - @api.model - def get_values(self): - res = super(ResConfigSettings, self).get_values() - res.update( - website_slide_google_app_key=self.env['ir.config_parameter'].sudo().get_param('website_slides.google_app_key'), - ) - return res - - @api.multi - def set_values(self): - super(ResConfigSettings, self).set_values() - self.env['ir.config_parameter'].sudo().set_param('website_slides.google_app_key', self.website_slide_google_app_key) + website_slide_google_app_key = fields.Char(string='Google Doc Key', config_parameter='website_slides.google_app_key') diff --git a/odoo/addons/base/models/ir_config_parameter.py b/odoo/addons/base/models/ir_config_parameter.py index 781b0a62c81..c53946a9d22 100644 --- a/odoo/addons/base/models/ir_config_parameter.py +++ b/odoo/addons/base/models/ir_config_parameter.py @@ -27,6 +27,7 @@ class IrConfigParameter(models.Model): """Per-database storage of configuration key-value pairs.""" _name = 'ir.config_parameter' _rec_name = 'key' + _order = 'key' key = fields.Char(required=True, index=True) value = fields.Text(required=True) diff --git a/odoo/addons/base/models/res_config.py b/odoo/addons/base/models/res_config.py index 81e62bdf2d8..60f42b16c1c 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,22 +449,33 @@ 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'): + if name.startswith('default_'): + if not hasattr(field, 'default_model'): + raise Exception("Field %s without attribute 'default_model'" % field) defaults.append((name, field.default_model, name[8:])) - elif name.startswith('group_') and field.type in ('boolean', 'selection') and \ - hasattr(field, 'implied_group'): + elif name.startswith('group_'): + if field.type not in ('boolean', 'selection'): + raise Exception("Field %s must have type 'boolean' or 'selection'" % field) + if not hasattr(field, 'implied_group'): + raise Exception("Field %s without attribute 'implied_group'" % field) field_group_xmlids = getattr(field, 'group', 'base.group_user').split(',') field_groups = Groups.concat(*(ref(it) for it in field_group_xmlids)) groups.append((name, field_groups, ref(field.implied_group))) - elif name.startswith('module_') and field.type in ('boolean', 'selection'): + elif name.startswith('module_'): + if field.type not in ('boolean', 'selection'): + raise Exception("Field %s must have type 'boolean' or 'selection'" % field) module = IrModule.sudo().search([('name', '=', name[7:])], limit=1) modules.append((name, module)) + elif hasattr(field, 'config_parameter'): + if field.type not in ('boolean', 'integer', 'float', 'char', 'selection', 'many2one'): + raise Exception("Field %s must have type 'boolean', 'integer', 'float', 'char', 'selection' or 'many2one'" % field) + configs.append((name, field.config_parameter)) 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 +486,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 +509,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 +583,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):