[IMP] tests: make server-side Form use get_views()

This aligns the behavior of Form with the web client form view.

Part-of: odoo/odoo#116779
This commit is contained in:
Raphael Collet
2023-03-31 17:02:43 +02:00
parent c93a212213
commit 63ee425b46
+39 -41
View File
@@ -120,13 +120,14 @@ class Form:
view_id = record.env.ref(view).id
else:
view_id = view or False
view = record.get_view(view_id, 'form')
view['tree'] = etree.fromstring(view['arch'])
view['fields'] = self._get_view_fields(view['tree'], record)
views = record.get_views([(view_id, 'form')])
object.__setattr__(self, '_models_info', views['models'])
# self._models_info = {model_name: {field_name: field_info}}
tree = etree.fromstring(views['views']['form']['arch'])
view = self._process_view(tree, record)
object.__setattr__(self, '_view', view)
self._process_view(view, record)
# self._view = {
# 'arch': view_arch_str,
# 'tree': view_arch_etree,
# 'fields': {field_name: field_info},
# 'modifiers': {field_name: {modifier: domain}},
@@ -144,30 +145,22 @@ class Form:
else:
self._init_from_defaults()
def _get_view_fields(self, node, model):
""" Return the field info of the fields in the view ``node``. """
level = node.xpath('count(ancestor::field)')
# retrieve the names of the <field> elements at the level of 'node';
# this trick is necessary for subviews, because 'node' is a part of its
# parent view's etree
field_names = {
elem.get('name')
for elem in node.xpath(f'.//field[count(ancestor::field) = {level}]')
}
return model.fields_get(sorted(field_names))
def _process_view(self, view, model, level=2):
def _process_view(self, tree, model, level=2):
""" Post-processes to augment the view_get with:
* an id field (may not be present if not in the view but needed)
* pre-processed modifiers (map of modifier name to json-loaded domain)
* pre-processed onchanges list
"""
view['fields'].setdefault('id', {'type': 'id'})
# pre-resolve modifiers & bind to arch toplevel
modifiers = view['modifiers'] = {
'id': {'required': [FALSE_LEAF], 'readonly': [TRUE_LEAF]},
fields = {'id': {'type': 'id'}}
modifiers = {'id': {'required': [FALSE_LEAF], 'readonly': [TRUE_LEAF]}}
contexts = {}
view = {
'tree': tree,
'fields': fields,
'modifiers': modifiers,
'contexts': contexts,
}
contexts = view['contexts'] = {}
# pre-resolve modifiers & bind to arch toplevel
eval_context = {
"uid": self._env.user.id,
"tz": self._env.user.tz,
@@ -180,10 +173,14 @@ class Form:
"context": {},
}
# retrieve <field> nodes at the current level
flevel = view['tree'].xpath('count(ancestor::field)')
for node in view['tree'].xpath(f'.//field[count(ancestor::field) = {flevel}]'):
flevel = tree.xpath('count(ancestor::field)')
for node in tree.xpath(f'.//field[count(ancestor::field) = {flevel}]'):
field_name = node.get('name')
# add field_info into fields
field_info = self._models_info[model._name].get(field_name) or {'type': None}
fields[field_name] = field_info
# determine modifiers
field_modifiers = {}
for modifier, domain in json.loads(node.get('modifiers', '{}')).items():
@@ -228,7 +225,6 @@ class Form:
if ctx:
contexts[field_name] = ctx
field_info = view['fields'].get(field_name) or {'type': None}
# FIXME: better widgets support
# NOTE: selection breaks because of m2o widget=selection
if node.get('widget') in ['many2many']:
@@ -236,9 +232,12 @@ class Form:
# determine subview to use for edition
if level and field_info['type'] == 'one2many':
field_info['invisible'] = field_modifiers.get('invisible') == [TRUE_LEAF]
field_info['edition_view'] = self._get_one2many_edition_view(field_info, node, level)
view['onchange'] = model._onchange_spec({'arch': etree.tostring(view['tree'])})
view['onchange'] = model._onchange_spec({'arch': etree.tostring(tree)})
return view
def _get_one2many_edition_view(self, field_info, node, level):
""" Return a suitable view for editing records into a one2many field. """
@@ -251,28 +250,27 @@ class Form:
for view_type in ['tree', 'form']:
if view_type in views:
continue
if field_info['invisible']:
# add an empty view
views[view_type] = etree.Element(view_type)
continue
refs = self._env['ir.ui.view']._get_view_refs(node)
subview = submodel.with_context(**refs).get_view(view_type=view_type)
subnode = etree.fromstring(subview['arch'])
subviews = submodel.with_context(**refs).get_views([(None, view_type)])
subnode = etree.fromstring(subviews['views'][view_type]['arch'])
views[view_type] = subnode
node.append(subnode)
for model_name, fields in subviews['models'].items():
self._models_info.setdefault(model_name, {}).update(fields)
# if the default view is a kanban or a non-editable list, the
# "edition controller" is the form view
# pick the first editable subview
view_type = next(
vtype for vtype in node.get('mode', 'tree').split(',') if vtype != 'form'
)
if not (view_type == 'tree' and views['tree'].get('editable')):
view_type = 'form'
subnode = views[view_type]
subview = {
'tree': subnode,
'fields': self._get_view_fields(subnode, submodel),
}
# don't recursively process o2ms in o2ms
self._process_view(subview, submodel, level=level-1)
return subview
return self._process_view(views[view_type], submodel, level=level-1)
def __str__(self):
return f"<{type(self).__name__} {self._record}>"
@@ -723,10 +721,10 @@ class O2MForm(Form):
object.__setattr__(self, '_record', model)
object.__setattr__(self, '_env', model.env)
# copy so we don't risk breaking it too much (?)
view = dict(proxy._field_info['edition_view'])
object.__setattr__(self, '_models_info', proxy._form._models_info)
tree = proxy._field_info['edition_view']['tree']
view = self._process_view(tree, model)
object.__setattr__(self, '_view', view)
self._process_view(view, model)
vals = dict.fromkeys(view['fields'], False)
object.__setattr__(self, '_values', vals)