From f7e207a1a1e404436940c4c077fbbc6a6a9b8aad Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 19 Jul 2018 16:56:22 +0200 Subject: [PATCH] Allow to edit default values for env-computed fields When they don't have any key in the environment configuration files. In the UI, when a field is set in a configuration file, the field is readonly, if not the field is editable. Which means you can selectively choose which fields depend on the environment and which can use a "default" value stored in database. --- server_environment/models/server_env_mixin.py | 113 +++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/server_environment/models/server_env_mixin.py b/server_environment/models/server_env_mixin.py index 24ce90c..e8d1d0d 100644 --- a/server_environment/models/server_env_mixin.py +++ b/server_environment/models/server_env_mixin.py @@ -3,6 +3,10 @@ import logging +from functools import partialmethod + +from lxml import etree + from odoo import api, fields, models from ..serv_config import serv_config @@ -26,7 +30,7 @@ class ServerEnvMixin(models.AbstractModel): environment configuration for the field ``directory_path``. Under the hood, this mixin automatically replaces the original field - by a computed field that reads from the configuration files. + 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 @@ -38,6 +42,10 @@ class ServerEnvMixin(models.AbstractModel): and the configuration files do not contain a key, the env-computed field uses the default value stored in database. If a key 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. """ _name = 'server.env.mixin' @@ -97,6 +105,11 @@ class ServerEnvMixin(models.AbstractModel): @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 ``_env_default`` field from database. + """ for record in self: for field_name, getter_name in self._server_env_fields.items(): section_name = self._server_env_section_name() @@ -115,17 +128,114 @@ class ServerEnvMixin(models.AbstractModel): record[field_name] = value + def _inverse_server_env(self, 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]: + record[default_field] = record[field_name] + + @api.multi + def _compute_server_env_is_editable(self): + """Compute _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 + ) + section_name = self._server_env_section_name() + is_editable = not (section_name in serv_config + and field_name in serv_config[section_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 + 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""" 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( + ServerEnvMixin._inverse_server_env, field.name + ) + setattr(ServerEnvMixin, inverse_method_name, inverse_method) + field.inverse = inverse_method_name field.store = False field.copy = False field.sparse = None + 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 fieldname not in self._fields: + field = fields.Boolean( + compute='_compute_server_env_is_editable', + automatic=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 stored in the serialized field ``server_env_defaults``. + """ fieldname = self._server_env_default_fieldname(base_field.name) if fieldname not in self._fields: base_field_cls = base_field.__class__ @@ -144,4 +254,5 @@ class ServerEnvMixin(models.AbstractModel): for fieldname in self._server_env_fields: field = self._fields[fieldname] self._server_env_transform_field_to_read_from_env(field) + self._server_env_add_is_editable_field(field) self._server_env_add_default_field(field)