[IMP] base: only pass to the webclient the fields it requires

The main goal is to reduce the number of KB sent by the `get_views`
method to the web client by getting rid of things not used by the web
client.

This revision aims to only pass the `fields_get` info of fields
actually required by the web client.

Before this revision, for each view, the list of all fields
of all models implied in the view are passed.
For the main view, it's required to pass all the fields,
for the search view: The advanced search needs to know all fields
of the model to be able to let the user to do advanced filters and
groupbys on the model fields.

But, for the models of the subviews (one2many/many2many embbeded views),
this is not required. Only the fields included in the view are required.
Also, if the search view is not requested by the web client,
then it's also not required to pass all the fields of the main model.

After this revision, for each view, pass the `fields_get`
info of only the fields actually implied in the view.
For the search view, info of all fields are passed,
not only the ones implied in the view.

For instance, on the `get_views` of `account.move`,
this allows a gain of an extra 18,05KB,
reducing from 165.82 to 147.77KB.

In addition, before this revision, the whole content of `fields_get`
was stored in the cache of `_get_view_cache`, meaning per view.
If 3 views were requested on `get_views`
let's say kanban, list, form,
the same whole `fields_get` info, of all fields,
was stored 3 times in the cache.

This revision removes the content of fields_get of the cache
for `get_views`, to avoid storing multiple times the exact same
information in the cache, therefore gaining tremendous memory,
while not loosing that processing time.
The decrease of speed is only 1~3ms per call to `get_views`.
This is mainly because all the information gathered by `fields_get`
is already in the cache. For instance, it doesn't require any SQL
request (at the moment). e.g. the translations (string, help, selection)
are already cached.

closes odoo/odoo#99834

Related: odoo/enterprise#31167
Signed-off-by: Denis Ledoux (dle) <dle@odoo.com>
This commit is contained in:
Denis Ledoux
2022-09-09 11:12:38 +02:00
parent a42f554c23
commit 4636620004
3 changed files with 24 additions and 22 deletions
+1
View File
@@ -11,5 +11,6 @@ class View(models.Model):
if node.xpath("ancestor::div[hasclass('oe_chatter')]"):
# Pass the postprocessing of the mail thread fields
# The web client makes it completely custom, and this is therefore pointless.
name_manager.has_field(node, node.get('name'), {})
return
return super()._postprocess_tag_field(node, name_manager, node_info)
+4 -12
View File
@@ -1006,14 +1006,14 @@ actual arch.
arch = etree.tostring(node, encoding="unicode").replace('\t', '')
models = set()
models = {}
name_managers = [name_manager]
for name_manager in name_managers:
models.add(name_manager.model._name)
models.setdefault(name_manager.model._name, set()).update(name_manager.available_fields)
name_managers.extend(name_manager.children)
return arch, models
def _postprocess_access_rights(self, tree, models):
def _postprocess_access_rights(self, tree):
"""
Apply group restrictions: elements with a 'groups' attribute should
be removed from the view to people who are not members.
@@ -1022,14 +1022,6 @@ actual arch.
views can add additional specific rights like creating columns for
many2one-based grouping views.
"""
models = {
model: {
fname: field
for fname, field in fields.items()
if not field.get('groups') or self.user_has_groups(field['groups'])
}
for model, fields in models.items()
}
for node in tree.xpath('//*[@groups]'):
if not self.user_has_groups(node.attrib.pop('groups')):
@@ -1073,7 +1065,7 @@ actual arch.
not self._context.get(action, True) and is_base_model):
node.set(action, 'false')
return tree, models
return tree
def _postprocess_view(self, node, model_name, editable=True, parent_name_manager=None, **options):
""" Process the given architecture, modifying it in-place to add and
+19 -10
View File
@@ -1654,11 +1654,22 @@ class BaseModel(metaclass=MetaModel):
for [v_id, v_type] in views
}
result['models'] = {
model: fields
for info in result['views'].values()
for model, fields in info.pop('models').items()
}
models = {}
for view in result['views'].values():
for model, model_fields in view.pop('models').items():
models.setdefault(model, {'id', self.CONCURRENCY_CHECK_FIELD}).update(model_fields)
result['models'] = {}
if 'search' in result['views']:
# If the search view is requested, all fields of the main model must be passed.
result['models'][self._name] = self.fields_get(attributes=self._get_view_field_attributes())
models.pop(self._name)
for model, model_fields in models.items():
result['models'][model] = self.env[model].fields_get(
allfields=model_fields, attributes=self._get_view_field_attributes()
)
# Add related action information if asked
if options.get('toolbar'):
@@ -1791,10 +1802,8 @@ class BaseModel(metaclass=MetaModel):
# TODO: only `web_studio` seems to require this. But this one on the other hand should be eliminated:
# you just called `get_views` for that model, so obviously the web client already knows the model.
'model': self._name,
# sudo is important to get all fields, including fields restricted to groups, in the cache.
'models': {model: frozendict(self.env[model].sudo().fields_get(
attributes=self._get_view_field_attributes(),
)) for model in models},
# Set a frozendict and tuple for the field list to make sure the value in cache cannot be updated.
'models': frozendict({model: tuple(fields) for model, fields in models.items()}),
}
return frozendict(result)
@@ -1824,7 +1833,7 @@ class BaseModel(metaclass=MetaModel):
result = dict(self._get_view_cache(view_id, view_type, **options))
node = etree.fromstring(result['arch'])
node, result['models'] = self.env['ir.ui.view']._postprocess_access_rights(node, result['models'])
node = self.env['ir.ui.view']._postprocess_access_rights(node)
result['arch'] = etree.tostring(node, encoding="unicode").replace('\t', '')
return result