diff --git a/sao/src/model.js b/sao/src/model.js index 39db3aaf12..d60d87c9c2 100644 --- a/sao/src/model.js +++ b/sao/src/model.js @@ -2202,23 +2202,45 @@ for (const val of value) { for (const fieldname in val) { if (!(fieldname in group.model.fields) && - (!~fieldname.indexOf('.'))) { + (!~fieldname.indexOf('.')) && + (!fieldname.startsWith('_'))) { field_names.add(fieldname); } } } - if (field_names.size) { + var attr_fields = Object.values(this.description.views || {}) + .map(v => v.fields) + .reduce((acc, elem) => { + for (const field in elem) { + acc[field] = elem[field]; + } + return acc; + }, {}); + var fields = {}; + for (const n of field_names) { + if (n in attr_fields) { + fields[n] = attr_fields[n]; + } + } + + var to_fetch = Array.from(field_names).filter(k => !(k in attr_fields)); + if (to_fetch.length) { var args = { 'method': 'model.' + this.description.relation + '.fields_get', - 'params': [Array.from(field_names), context] + 'params': [to_fetch, context] }; - var fields; try { - fields = Sao.rpc(args, record.model.session, false); + var rpc_fields = Sao.rpc( + args, record.model.session, false); + for (const [key, value] of Object.entries(rpc_fields)) { + fields[key] = value; + } } catch (e) { return; } + } + if (!jQuery.isEmptyObject(fields)) { group.add_fields(fields); } } diff --git a/tryton/tryton/gui/window/view_form/model/field.py b/tryton/tryton/gui/window/view_form/model/field.py index f7206d1c8f..767eb30b51 100644 --- a/tryton/tryton/gui/window/view_form/model/field.py +++ b/tryton/tryton/gui/window/view_form/model/field.py @@ -2,9 +2,11 @@ # this repository contains the full copyright notices and license terms. import datetime import decimal +import functools import locale import logging import math +import operator import os from decimal import Decimal from itertools import chain @@ -712,13 +714,25 @@ class O2MField(Field): if mode == 'list values': context = self.get_context(record) field_names = set(f for v in value for f in v - if f not in group.fields and '.' not in f) - if field_names: + if (f not in group.fields + and '.' not in f + and ':' not in f + and not f.startswith('_'))) + attr_fields = functools.reduce( + operator.or_, + (v['fields'] for v in self.attrs.get('views', {}).values()), + {}) + fields = {n: attr_fields[n] + for n in field_names + if n in attr_fields} + if to_fetch := (field_names - attr_fields.keys()): try: - fields = RPCExecute('model', self.attrs['relation'], - 'fields_get', list(field_names), context=context) + fields |= RPCExecute('model', self.attrs['relation'], + 'fields_get', list(to_fetch), context=context) except RPCException: return + + if fields: group.load_fields(fields) if mode == 'list ids': diff --git a/trytond/trytond/model/modelview.py b/trytond/trytond/model/modelview.py index 1950299203..0b778c2e80 100644 --- a/trytond/trytond/model/modelview.py +++ b/trytond/trytond/model/modelview.py @@ -507,6 +507,8 @@ class ModelView(Model): tree, encoding='utf-8', pretty_print=False).decode('utf-8') # Do not call fields_def without fields as it returns all fields if fields_def: + # Prevent clients calling fields_view_get to get 'id' definition + fields_def.setdefault('id', {'name': 'id'}) fields2 = cls.fields_get(list(fields_def.keys()), level=level) else: fields2 = {} diff --git a/trytond/trytond/tests/test_modelview.py b/trytond/trytond/tests/test_modelview.py index 32d4d6951e..164f34e175 100644 --- a/trytond/trytond/tests/test_modelview.py +++ b/trytond/trytond/tests/test_modelview.py @@ -611,7 +611,7 @@ class ModelView(unittest.TestCase): fields = DependsDepends.fields_view_get(view_type='form')['fields'] - self.assertEqual(fields.keys(), {'foo', 'bar', 'baz'}) + self.assertEqual(fields.keys(), {'id', 'foo', 'bar', 'baz'}) @with_transaction(context={'_check_access': True}) def test_button_depends_access(self):