From b9feebc25cc8caad97e5c3cb2953643bfd14984f Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Wed, 23 Mar 2022 13:52:18 +0000 Subject: [PATCH] [REF] models, fields: refactor fields_get - Filter in attributes rather than filter out. The goal is to not gather attributes which are costly in term of performance if they are not requested in the first place. e.g., with all modules installed: - Before rev: ```py In [1]: %time for _i in range(1000): self.env["res.partner"].fields_get(attributes=['readonly', 'required', 'states', 'invisible']);self.invalidate_cache() CPU times: user 1.99 s, sys: 9.83 ms, total: 2 s Wall time: 2.03 s ``` - After rev: ```py In [2]: %time for _i in range(1000): self.env["res.partner"].fields_get(attributes=['readonly', 'required', 'states', 'invisible']);self.invalidate_cache() CPU times: user 345 ms, sys: 0 ns, total: 345 ms Wall time: 345 ms ``` - Use the `_description_` mechanism for the attributes `name` and `type`, so its no longer needed to treat them as exception in `field_get` and `get_description` respectively, and make the code shorter and cleaner. - Move out from `fields_get` the block ```py has_access = functools.partial(self.check_access_rights, raise_exception=False) readonly = not (has_access('write') or has_access('create')) ... if readonly: description['readonly'] = True description['states'] = {} ``` because: - It meant you had a different behavior using `fields_get` or `get_description` for the keys `readonly` and `states`, meaning a field could be marked as `readonly` by `fields_get` but not by `get_description`, which is confusing. - It is actually used in only one place, the post-process of back-end views, which mark the fields readonly for the web client if you do not have the create or write access to their model. closes odoo/odoo#87273 Signed-off-by: Denis Ledoux (dle) --- odoo/addons/base/models/ir_ui_view.py | 9 ++++++++- odoo/fields.py | 8 ++++++-- odoo/models.py | 13 +------------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index 385fbeb6f23..c42646ea186 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -3,6 +3,7 @@ import ast import collections import datetime +import functools import inspect import json import logging @@ -2195,7 +2196,13 @@ class NameManager: @lazy_property def field_info(self): - return self.model.fields_get() + field_info = self.model.fields_get() + has_access = functools.partial(self.model.check_access_rights, raise_exception=False) + if not (has_access('write') or has_access('create')): + for info in field_info.vals(): + info['readonly'] = True + info['states'] = {} + return field_info def has_field(self, name, info=frozendict()): self.available_fields[name].update(info) diff --git a/odoo/fields.py b/odoo/fields.py index 3c73428c535..bdf2ea0082b 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -793,10 +793,12 @@ class Field(MetaField('DummyField', (object,), {})): # Field description # - def get_description(self, env): + def get_description(self, env, attributes=None): """ Return a dictionary that describes the field ``self``. """ - desc = {'type': self.type} + desc = {} for attr, prop in self.description_attrs: + if attributes is not None and attr not in attributes: + continue value = getattr(self, prop) if callable(value): value = value(env) @@ -806,6 +808,8 @@ class Field(MetaField('DummyField', (object,), {})): return desc # properties used by get_description() + _description_name = property(attrgetter('name')) + _description_type = property(attrgetter('type')) _description_store = property(attrgetter('store')) _description_manual = property(attrgetter('manual')) _description_related = property(attrgetter('related')) diff --git a/odoo/models.py b/odoo/models.py index ab16f1c40f7..60720e79c73 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -3195,9 +3195,6 @@ class BaseModel(metaclass=MetaModel): :param allfields: list of fields to document, all if empty or not provided :param attributes: list of description attributes to return for each field, all if empty or not provided """ - has_access = functools.partial(self.check_access_rights, raise_exception=False) - readonly = not (has_access('write') or has_access('create')) - res = {} for fname, field in self._fields.items(): if allfields and fname not in allfields: @@ -3205,15 +3202,7 @@ class BaseModel(metaclass=MetaModel): if field.groups and not self.env.su and not self.user_has_groups(field.groups): continue - description = field.get_description(self.env) - description['name'] = fname - if readonly: - description['readonly'] = True - description['states'] = {} - if attributes: - description = {key: val - for key, val in description.items() - if key in attributes} + description = field.get_description(self.env, attributes=attributes) res[fname] = description return res