[FIX] base: validate domain of field used in view
When a field has a domain like `"[('foo', '=', bar)]"` and is in a view, check
that `bar` is in the view as well. Note that the check should only occurs in
editable views (form or editable list) and when the field itself is editable.
OPW 1820269
This commit is contained in:
@@ -172,6 +172,7 @@ xpath_utils['hasclass'] = _hasclass
|
||||
|
||||
TRANSLATED_ATTRS_RE = re.compile(r"@(%s)\b" % "|".join(TRANSLATED_ATTRS))
|
||||
WRONGCLASS = re.compile(r"(@class\s*=|=\s*@class|contains\(@class)")
|
||||
READONLY = re.compile(r"\breadonly\b")
|
||||
|
||||
|
||||
class View(models.Model):
|
||||
@@ -786,12 +787,16 @@ actual arch.
|
||||
attrs = {}
|
||||
field = Model._fields.get(node.get('name'))
|
||||
if field:
|
||||
editable = self.env.context.get('view_is_editable', True) and self._field_is_editable(field, node)
|
||||
children = False
|
||||
views = {}
|
||||
for f in node:
|
||||
if f.tag in ('form', 'tree', 'graph', 'kanban', 'calendar'):
|
||||
node.remove(f)
|
||||
xarch, xfields = self.with_context(base_model_name=model).postprocess_and_fields(field.comodel_name, f, view_id)
|
||||
xarch, xfields = self.with_context(
|
||||
base_model_name=model,
|
||||
view_is_editable=editable,
|
||||
).postprocess_and_fields(field.comodel_name, f, view_id)
|
||||
views[str(f.tag)] = {
|
||||
'arch': xarch,
|
||||
'fields': xfields,
|
||||
@@ -866,6 +871,17 @@ actual arch.
|
||||
|
||||
return arch
|
||||
|
||||
def _view_is_editable(self, node):
|
||||
""" Return whether the node is an editable view. """
|
||||
return node.tag == 'form' or node.tag == 'tree' and node.get('editable')
|
||||
|
||||
def _field_is_editable(self, field, node):
|
||||
""" Return whether a field is editable (not always readonly). """
|
||||
return (
|
||||
(not field.readonly or READONLY.search(str(field.states or ""))) and
|
||||
(node.get('readonly') != "1" or READONLY.search(node.get('attrs') or ""))
|
||||
)
|
||||
|
||||
def get_attrs_symbols(self):
|
||||
""" Return a set of predefined symbols for evaluating attrs. """
|
||||
return {
|
||||
@@ -885,10 +901,11 @@ actual arch.
|
||||
'current_date',
|
||||
}
|
||||
|
||||
def get_attrs_field_names(self, arch):
|
||||
def get_attrs_field_names(self, arch, model, editable):
|
||||
""" Retrieve the field names appearing in context, domain and attrs, and
|
||||
return a list of triples ``(field_name, attr_name, attr_value)``.
|
||||
"""
|
||||
VIEW_TYPES = {item[0] for item in type(self).type.selection}
|
||||
symbols = self.get_attrs_symbols() | {None}
|
||||
result = []
|
||||
|
||||
@@ -918,8 +935,17 @@ actual arch.
|
||||
if isinstance(arg, (tuple, list)):
|
||||
process_expr(str(arg[0]), get, key, expr)
|
||||
|
||||
def process(node, get=get_name):
|
||||
def process(node, model, editable, get=get_name):
|
||||
""" traverse `node` and collect triples """
|
||||
if node.tag in VIEW_TYPES:
|
||||
# determine whether this view is editable
|
||||
editable = editable and self._view_is_editable(node)
|
||||
elif node.tag == 'field':
|
||||
# determine whether the field is editable
|
||||
field = model._fields.get(node.get('name'))
|
||||
if field:
|
||||
editable = editable and self._field_is_editable(field, node)
|
||||
|
||||
for key, val in node.items():
|
||||
if not val:
|
||||
continue
|
||||
@@ -927,13 +953,21 @@ actual arch.
|
||||
process_expr(val, get, key, val)
|
||||
elif key == 'attrs':
|
||||
process_attrs(val, get, key, val)
|
||||
if node.tag == 'field':
|
||||
# retrieve subfields of 'parent'
|
||||
get = partial(get_subname, get)
|
||||
for child in node:
|
||||
process(child, get)
|
||||
|
||||
process(arch)
|
||||
if node.tag == 'field' and field and field.relational:
|
||||
if editable and not node.get('domain'):
|
||||
domain = field._description_domain(self.env)
|
||||
# process the field's domain as if it was in the view
|
||||
if isinstance(domain, pycompat.string_types):
|
||||
process_expr(domain, get, 'domain', domain)
|
||||
# retrieve subfields of 'parent'
|
||||
model = self.env[field.comodel_name]
|
||||
get = partial(get_subname, get)
|
||||
|
||||
for child in node:
|
||||
process(child, model, editable, get)
|
||||
|
||||
process(arch, model, editable)
|
||||
return result
|
||||
|
||||
@api.model
|
||||
@@ -974,7 +1008,8 @@ actual arch.
|
||||
|
||||
attrs_fields = []
|
||||
if self.env.context.get('check_field_names'):
|
||||
attrs_fields = self.get_attrs_field_names(node)
|
||||
editable = self.env.context.get('view_is_editable', True)
|
||||
attrs_fields = self.get_attrs_field_names(node, Model, editable)
|
||||
|
||||
fields_def = self.postprocess(model, node, view_id, False, fields)
|
||||
if node.tag in ('kanban', 'tree', 'form', 'gantt'):
|
||||
|
||||
@@ -1106,6 +1106,183 @@ class TestViews(ViewCase):
|
||||
'arch': arch % ('', '<field name="model"/>'),
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view')
|
||||
def test_domain_on_field_in_view(self):
|
||||
field = self.env['ir.ui.view']._fields['inherit_id']
|
||||
self.patch(field, 'domain', "[('model', '=', model)]")
|
||||
|
||||
arch = """
|
||||
<form string="View">
|
||||
<field name="name"/>%s
|
||||
<field name="inherit_id"/>
|
||||
</form>
|
||||
"""
|
||||
self.View.create({
|
||||
'name': 'valid domain',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': arch % '<field name="model"/>',
|
||||
})
|
||||
with self.assertRaises(ValidationError):
|
||||
self.View.create({
|
||||
'name': 'valid domain',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': arch % '',
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view')
|
||||
def test_domain_on_field_in_subview(self):
|
||||
field = self.env['ir.ui.view']._fields['inherit_id']
|
||||
self.patch(field, 'domain', "[('model', '=', model)]")
|
||||
|
||||
arch = """
|
||||
<form string="View">
|
||||
<field name="name"/>%s
|
||||
<field name="inherit_children_ids">
|
||||
<form string="Children">
|
||||
<field name="name"/>%s
|
||||
<field name="inherit_id"/>
|
||||
</form>
|
||||
</field>
|
||||
</form>
|
||||
"""
|
||||
self.View.create({
|
||||
'name': 'valid domain',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': arch % ('', '<field name="model"/>'),
|
||||
})
|
||||
with self.assertRaises(ValidationError):
|
||||
self.View.create({
|
||||
'name': 'valid domain',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': arch % ('', ''),
|
||||
})
|
||||
with self.assertRaises(ValidationError):
|
||||
self.View.create({
|
||||
'name': 'valid domain',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': arch % ('<field name="model"/>', ''),
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view')
|
||||
def test_domain_on_field_in_subview_with_parent(self):
|
||||
field = self.env['ir.ui.view']._fields['inherit_id']
|
||||
self.patch(field, 'domain', "[('model', '=', parent.model)]")
|
||||
|
||||
arch = """
|
||||
<form string="View">
|
||||
<field name="name"/>%s
|
||||
<field name="inherit_children_ids">
|
||||
<form string="Children">
|
||||
<field name="name"/>%s
|
||||
<field name="inherit_id"/>
|
||||
</form>
|
||||
</field>
|
||||
</form>
|
||||
"""
|
||||
self.View.create({
|
||||
'name': 'valid domain',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': arch % ('<field name="model"/>', ''),
|
||||
})
|
||||
with self.assertRaises(ValidationError):
|
||||
self.View.create({
|
||||
'name': 'valid domain',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': arch % ('', ''),
|
||||
})
|
||||
with self.assertRaises(ValidationError):
|
||||
self.View.create({
|
||||
'name': 'valid domain',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': arch % ('', '<field name="model"/>'),
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view')
|
||||
def test_domain_on_field_in_noneditable_subview(self):
|
||||
field = self.env['ir.ui.view']._fields['inherit_id']
|
||||
self.patch(field, 'domain', "[('model', '=', model)]")
|
||||
|
||||
arch = """
|
||||
<form string="View">
|
||||
<field name="name"/>
|
||||
<field name="inherit_children_ids">
|
||||
<tree string="Children"%s>
|
||||
<field name="name"/>
|
||||
<field name="inherit_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>
|
||||
"""
|
||||
self.View.create({
|
||||
'name': 'valid domain',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': arch % '',
|
||||
})
|
||||
with self.assertRaises(ValidationError):
|
||||
self.View.create({
|
||||
'name': 'valid domain',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': arch % ' editable="bottom"',
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view')
|
||||
def test_domain_on_readonly_field_in_view(self):
|
||||
field = self.env['ir.ui.view']._fields['inherit_id']
|
||||
self.patch(field, 'domain', "[('model', '=', model)]")
|
||||
|
||||
arch = """
|
||||
<form string="View">
|
||||
<field name="name"/>
|
||||
<field name="inherit_id" readonly="1"/>
|
||||
</form>
|
||||
"""
|
||||
self.View.create({
|
||||
'name': 'valid domain',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': arch,
|
||||
})
|
||||
|
||||
self.patch(field, 'readonly', True)
|
||||
arch = """
|
||||
<form string="View">
|
||||
<field name="name"/>
|
||||
<field name="inherit_id"/>
|
||||
</form>
|
||||
"""
|
||||
self.View.create({
|
||||
'name': 'valid domain',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': arch,
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view')
|
||||
def test_domain_on_readonly_field_in_subview(self):
|
||||
field = self.env['ir.ui.view']._fields['inherit_id']
|
||||
self.patch(field, 'domain', "[('model', '=', model)]")
|
||||
|
||||
arch = """
|
||||
<form string="View">
|
||||
<field name="name"/>
|
||||
<field name="inherit_children_ids"%s>
|
||||
<form string="Children">
|
||||
<field name="name"/>
|
||||
<field name="inherit_id"/>
|
||||
</form>
|
||||
</field>
|
||||
</form>
|
||||
"""
|
||||
self.View.create({
|
||||
'name': 'valid domain',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': arch % ' readonly="1"',
|
||||
})
|
||||
with self.assertRaises(ValidationError):
|
||||
self.View.create({
|
||||
'name': 'valid domain',
|
||||
'model': 'ir.ui.view',
|
||||
'arch': arch % '',
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.base.ir.ir_ui_view')
|
||||
def test_attrs_field(self):
|
||||
arch = """
|
||||
|
||||
Reference in New Issue
Block a user