[MIG] base_view_inheritance_extension: Migration to 15.0
* Removed `list_add` and `list_remove`, they've been deprecated and implemented in Odoo core since several versions ago. * Removed `move`, as it has also already been implemented in core several versions ago. * Replaced `python_dict` by `update`, that performs an operation similar to :meth:`dict.update`, but on the ast.Dict.pull/2494/head
parent
44871e11f2
commit
1b70255898
|
@ -3,7 +3,7 @@
|
||||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||||
{
|
{
|
||||||
"name": "Extended view inheritance",
|
"name": "Extended view inheritance",
|
||||||
"version": "14.0.1.1.0",
|
"version": "15.0.1.0.0",
|
||||||
"author": "Therp BV,Odoo Community Association (OCA)",
|
"author": "Therp BV,Odoo Community Association (OCA)",
|
||||||
"license": "LGPL-3",
|
"license": "LGPL-3",
|
||||||
"category": "Hidden/Dependency",
|
"category": "Hidden/Dependency",
|
||||||
|
|
|
@ -8,16 +8,12 @@
|
||||||
<attribute name="string">Partner form</attribute>
|
<attribute name="string">Partner form</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
<field name="parent_id" position="attributes">
|
<field name="parent_id" position="attributes">
|
||||||
<attribute
|
<attribute name="context" operation="update">
|
||||||
name="context"
|
{
|
||||||
operation="python_dict"
|
"default_name": "The company name",
|
||||||
key="default_name"
|
"default_company_id": context.get("company_id", context.get("company"))
|
||||||
>'The company name'</attribute>
|
}
|
||||||
<attribute
|
</attribute>
|
||||||
name="context"
|
|
||||||
operation="python_dict"
|
|
||||||
key="default_company_id"
|
|
||||||
>context.get('company_id', context.get('company'))</attribute>
|
|
||||||
</field>
|
</field>
|
||||||
<!-- without operations, the standard handler should be called /-->
|
<!-- without operations, the standard handler should be called /-->
|
||||||
<field name="parent_id" position="attributes">
|
<field name="parent_id" position="attributes">
|
||||||
|
|
|
@ -11,6 +11,45 @@ from lxml import etree
|
||||||
from odoo import api, models
|
from odoo import api, models
|
||||||
|
|
||||||
|
|
||||||
|
def ast_dict_update(source, update):
|
||||||
|
"""Perform a dict `update` on an ast.Dict
|
||||||
|
|
||||||
|
Behaves similar to :meth:`dict.update`, but on ast.Dict instead.
|
||||||
|
Only compares string-like ast.Dict keys (ast.Str or ast.Constant).
|
||||||
|
|
||||||
|
:returns: The updated ast.Dict
|
||||||
|
:rtype: ast.Dict
|
||||||
|
"""
|
||||||
|
if not isinstance(source, ast.Dict):
|
||||||
|
raise TypeError("`source` must be an AST dict")
|
||||||
|
if not isinstance(update, ast.Dict):
|
||||||
|
raise TypeError("`update` must be an AST dict")
|
||||||
|
|
||||||
|
def ast_key_eq(k1, k2):
|
||||||
|
# python < 3.8 uses ast.Str; python >= 3.8 uses ast.Constant
|
||||||
|
if type(k1) != type(k2):
|
||||||
|
return False
|
||||||
|
elif isinstance(k1, ast.Str):
|
||||||
|
return k1.s == k2.s
|
||||||
|
elif isinstance(k1, ast.Constant):
|
||||||
|
return k1.value == k2.value
|
||||||
|
|
||||||
|
toadd_uidx = []
|
||||||
|
for uidx, ukey in enumerate(update.keys):
|
||||||
|
found = False
|
||||||
|
for sidx, skey in enumerate(source.keys):
|
||||||
|
if ast_key_eq(ukey, skey):
|
||||||
|
source.values[sidx] = update.values[uidx]
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
toadd_uidx.append(uidx)
|
||||||
|
for uidx in toadd_uidx:
|
||||||
|
source.keys.append(update.keys[uidx])
|
||||||
|
source.values.append(update.values[uidx])
|
||||||
|
return source
|
||||||
|
|
||||||
|
|
||||||
class IrUiView(models.Model):
|
class IrUiView(models.Model):
|
||||||
_inherit = "ir.ui.view"
|
_inherit = "ir.ui.view"
|
||||||
|
|
||||||
|
@ -41,14 +80,14 @@ class IrUiView(models.Model):
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _get_inheritance_handler(self, node):
|
def _get_inheritance_handler(self, node):
|
||||||
handler = super(IrUiView, self).apply_inheritance_specs
|
handler = super().apply_inheritance_specs
|
||||||
if hasattr(self, "inheritance_handler_%s" % node.tag):
|
if hasattr(self, "inheritance_handler_%s" % node.tag):
|
||||||
handler = getattr(self, "inheritance_handler_%s" % node.tag)
|
handler = getattr(self, "inheritance_handler_%s" % node.tag)
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _get_inheritance_handler_attributes(self, node):
|
def _get_inheritance_handler_attributes(self, node):
|
||||||
handler = super(IrUiView, self).apply_inheritance_specs
|
handler = super().apply_inheritance_specs
|
||||||
if hasattr(self, "inheritance_handler_attributes_%s" % node.get("operation")):
|
if hasattr(self, "inheritance_handler_attributes_%s" % node.get("operation")):
|
||||||
handler = getattr(
|
handler = getattr(
|
||||||
self, "inheritance_handler_attributes_%s" % node.get("operation")
|
self, "inheritance_handler_attributes_%s" % node.get("operation")
|
||||||
|
@ -56,75 +95,35 @@ class IrUiView(models.Model):
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def inheritance_handler_attributes_python_dict(self, source, specs):
|
def inheritance_handler_attributes_update(self, source, specs):
|
||||||
"""Implement
|
"""Implement dict `update` operation on the attribute node.
|
||||||
<$node position="attributes">
|
|
||||||
<attribute name="$attribute" operation="python_dict" key="$key">
|
.. code-block:: xml
|
||||||
$keyvalue
|
|
||||||
|
<field position="attributes">
|
||||||
|
<attribute name="context" operation="update">
|
||||||
|
{
|
||||||
|
"key": "value",
|
||||||
|
}
|
||||||
</attribute>
|
</attribute>
|
||||||
</$node>"""
|
</field>
|
||||||
|
"""
|
||||||
node = self.locate_node(source, specs)
|
node = self.locate_node(source, specs)
|
||||||
for attribute_node in specs:
|
for spec in specs:
|
||||||
attr_name = attribute_node.get("name")
|
attr_name = spec.get("name")
|
||||||
attr_key = attribute_node.get("key")
|
# Parse ast from both node and spec
|
||||||
str_dict = node.get(attr_name) or "{}"
|
source_ast = ast.parse(node.get(attr_name) or "{}", mode="eval").body
|
||||||
ast_dict = ast.parse(str_dict, mode="eval").body
|
update_ast = ast.parse(spec.text.strip(), mode="eval").body
|
||||||
assert isinstance(ast_dict, ast.Dict), f"'{attr_name}' is not a dict"
|
if not isinstance(source_ast, ast.Dict):
|
||||||
assert attr_key, "No key specified for 'python_dict' operation"
|
raise TypeError(f"Attribute `{attr_name}` is not a dict")
|
||||||
# Find the ast dict key
|
if not isinstance(update_ast, ast.Dict):
|
||||||
# python < 3.8 uses ast.Str; python >= 3.8 uses ast.Constant
|
raise TypeError(f"Operation for attribute `{attr_name}` is not a dict")
|
||||||
key_idx = next(
|
# Update node ast dict
|
||||||
(
|
source_ast = ast_dict_update(source_ast, update_ast)
|
||||||
i
|
|
||||||
for i, k in enumerate(ast_dict.keys)
|
|
||||||
if (isinstance(k, ast.Str) and k.s == attr_key)
|
|
||||||
or (isinstance(k, ast.Constant) and k.value == attr_key)
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
# Update or create the key
|
|
||||||
value = ast.parse(attribute_node.text.strip(), mode="eval").body
|
|
||||||
if key_idx:
|
|
||||||
ast_dict.values[key_idx] = value
|
|
||||||
else:
|
|
||||||
ast_dict.keys.append(ast.Str(attr_key))
|
|
||||||
ast_dict.values.append(value)
|
|
||||||
# Dump the ast back to source
|
# Dump the ast back to source
|
||||||
# TODO: once odoo requires python >= 3.9; use `ast.unparse` instead
|
# TODO: once odoo requires python >= 3.9; use `ast.unparse` instead
|
||||||
node.attrib[attribute_node.get("name")] = astor.to_source(
|
node.attrib[attr_name] = astor.to_source(
|
||||||
ast_dict, pretty_source=lambda s: "".join(s).strip()
|
source_ast,
|
||||||
|
pretty_source=lambda s: "".join(s).strip(),
|
||||||
)
|
)
|
||||||
return source
|
return source
|
||||||
|
|
||||||
@api.model
|
|
||||||
def inheritance_handler_attributes_list_add(self, source, specs):
|
|
||||||
"""Implement
|
|
||||||
<$node position="attributes">
|
|
||||||
<attribute name="$attribute" operation="list_add">
|
|
||||||
$new_value
|
|
||||||
</attribute>
|
|
||||||
</$node>"""
|
|
||||||
node = self.locate_node(source, specs)
|
|
||||||
for attribute_node in specs:
|
|
||||||
attribute_name = attribute_node.get("name")
|
|
||||||
old_value = node.get(attribute_name) or ""
|
|
||||||
new_value = old_value + "," + attribute_node.text
|
|
||||||
node.attrib[attribute_name] = new_value
|
|
||||||
return source
|
|
||||||
|
|
||||||
@api.model
|
|
||||||
def inheritance_handler_attributes_list_remove(self, source, specs):
|
|
||||||
"""Implement
|
|
||||||
<$node position="attributes">
|
|
||||||
<attribute name="$attribute" operation="list_remove">
|
|
||||||
$value_to_remove
|
|
||||||
</attribute>
|
|
||||||
</$node>"""
|
|
||||||
node = self.locate_node(source, specs)
|
|
||||||
for attribute_node in specs:
|
|
||||||
attribute_name = attribute_node.get("name")
|
|
||||||
old_values = (node.get(attribute_name) or "").split(",")
|
|
||||||
remove_values = attribute_node.text.split(",")
|
|
||||||
new_values = [x for x in old_values if x not in remove_values]
|
|
||||||
node.attrib[attribute_name] = ",".join([_f for _f in new_values if _f])
|
|
||||||
return source
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
* On `15.0`, remove `list_add` and `list_remove` fetures.
|
|
||||||
* Support an ``eval`` attribute for our new node types.
|
* Support an ``eval`` attribute for our new node types.
|
||||||
|
|
|
@ -3,21 +3,14 @@
|
||||||
|
|
||||||
.. code-block:: xml
|
.. code-block:: xml
|
||||||
|
|
||||||
<attribute name="$attribute" operation="python_dict" key="$key">
|
<field position="attributes">
|
||||||
$new_value
|
<attribute name="context" operation="update">
|
||||||
|
{
|
||||||
|
"key": "value",
|
||||||
|
}
|
||||||
</attribute>
|
</attribute>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
|
||||||
Note that views are subject to evaluation of xmlids anyways, so if you need
|
Note that views are subject to evaluation of xmlids anyways, so if you need
|
||||||
to refer to some xmlid, say ``%(xmlid)s``.
|
to refer to some xmlid, say ``%(xmlid)s``.
|
||||||
|
|
||||||
**Add to values in a list (states for example)**
|
|
||||||
|
|
||||||
Deprecated. This feature is now native, use `<attribute name="attrname" separator="," add="something" />`.
|
|
||||||
|
|
||||||
**Remove values from a list (states for example)**
|
|
||||||
|
|
||||||
Deprecated. This feature is now native, use `<attribute name="attrname" separator="," remove="something" />`.
|
|
||||||
|
|
||||||
**Move an element in the view**
|
|
||||||
|
|
||||||
This feature is now native, cf the `official Odoo documentation <https://www.odoo.com/documentation/14.0/developer/reference/addons/views.html#inheritance-specs>`_.
|
|
||||||
|
|
|
@ -5,22 +5,22 @@
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from odoo.tests.common import SavepointCase
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
|
||||||
class TestBaseViewInheritanceExtension(SavepointCase):
|
class TestBaseViewInheritanceExtension(TransactionCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
cls.ViewModel = cls.env["ir.ui.view"]
|
cls.maxDiff = None
|
||||||
|
|
||||||
def test_base_view_inheritance_extension(self):
|
def test_base_view_inheritance_extension(self):
|
||||||
view_id = self.env.ref("base.view_partner_simple_form").id
|
view_id = self.env.ref("base.view_partner_simple_form").id
|
||||||
fields_view_get = self.env["res.partner"].fields_view_get(view_id=view_id)
|
fields_view_get = self.env["res.partner"].fields_view_get(view_id=view_id)
|
||||||
view = etree.fromstring(fields_view_get["arch"])
|
view = etree.fromstring(fields_view_get["arch"])
|
||||||
# verify normal attributes work
|
# Verify normal attributes work
|
||||||
self.assertEqual(view.xpath("//form")[0].get("string"), "Partner form")
|
self.assertEqual(view.xpath("//form")[0].get("string"), "Partner form")
|
||||||
# verify our extra context key worked
|
# Verify our extra context key worked
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
"default_name" in view.xpath('//field[@name="parent_id"]')[0].get("context")
|
"default_name" in view.xpath('//field[@name="parent_id"]')[0].get("context")
|
||||||
)
|
)
|
||||||
|
@ -29,75 +29,7 @@ class TestBaseViewInheritanceExtension(SavepointCase):
|
||||||
in view.xpath('//field[@name="parent_id"]')[0].get("context")
|
in view.xpath('//field[@name="parent_id"]')[0].get("context")
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_list_add(self):
|
def test_update_context_default(self):
|
||||||
view_model = self.env["ir.ui.view"]
|
|
||||||
source = etree.fromstring(
|
|
||||||
"""\
|
|
||||||
<form>
|
|
||||||
<button name="test" states="draft,open"/>
|
|
||||||
</form>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
# extend with single value
|
|
||||||
specs = etree.fromstring(
|
|
||||||
"""\
|
|
||||||
<button name="test" position="attributes">
|
|
||||||
<attribute
|
|
||||||
name="states"
|
|
||||||
operation="list_add"
|
|
||||||
>valid</attribute>
|
|
||||||
</button>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
modified_source = view_model.inheritance_handler_attributes_list_add(
|
|
||||||
source, specs
|
|
||||||
)
|
|
||||||
button_node = modified_source.xpath('//button[@name="test"]')[0]
|
|
||||||
self.assertEqual(button_node.attrib["states"], "draft,open,valid")
|
|
||||||
# extend with list of values
|
|
||||||
specs = etree.fromstring(
|
|
||||||
"""\
|
|
||||||
<button name="test" position="attributes">
|
|
||||||
<attribute
|
|
||||||
name="states"
|
|
||||||
operation="list_add"
|
|
||||||
>payable,paid</attribute>
|
|
||||||
</button>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
modified_source = view_model.inheritance_handler_attributes_list_add(
|
|
||||||
source, specs
|
|
||||||
)
|
|
||||||
button_node = modified_source.xpath('//button[@name="test"]')[0]
|
|
||||||
self.assertEqual(button_node.attrib["states"], "draft,open,valid,payable,paid")
|
|
||||||
|
|
||||||
def test_list_remove(self):
|
|
||||||
view_model = self.env["ir.ui.view"]
|
|
||||||
source = etree.fromstring(
|
|
||||||
"""\
|
|
||||||
<form>
|
|
||||||
<button name="test" states="draft,open,valid,payable,paid"/>
|
|
||||||
</form>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
# remove list of values
|
|
||||||
specs = etree.fromstring(
|
|
||||||
"""\
|
|
||||||
<button name="test" position="attributes">
|
|
||||||
<attribute
|
|
||||||
name="states"
|
|
||||||
operation="list_remove"
|
|
||||||
>open,payable</attribute>
|
|
||||||
</button>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
modified_source = view_model.inheritance_handler_attributes_list_remove(
|
|
||||||
source, specs
|
|
||||||
)
|
|
||||||
button_node = modified_source.xpath('//button[@name="test"]')[0]
|
|
||||||
self.assertEqual(button_node.attrib["states"], "draft,valid,paid")
|
|
||||||
|
|
||||||
def test_python_dict_inheritance_context_default(self):
|
|
||||||
source = etree.fromstring(
|
source = etree.fromstring(
|
||||||
"""
|
"""
|
||||||
<form>
|
<form>
|
||||||
|
@ -108,19 +40,19 @@ class TestBaseViewInheritanceExtension(SavepointCase):
|
||||||
specs = etree.fromstring(
|
specs = etree.fromstring(
|
||||||
"""
|
"""
|
||||||
<field name="account_move_id" position="attributes">
|
<field name="account_move_id" position="attributes">
|
||||||
<attribute name="context" operation="python_dict" key="default_company_id">
|
<attribute name="context" operation="update">
|
||||||
company_id
|
{"default_company_id": company_id}
|
||||||
</attribute>
|
</attribute>
|
||||||
</field>
|
</field>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
res = self.ViewModel.inheritance_handler_attributes_python_dict(source, specs)
|
res = self.env["ir.ui.view"].apply_inheritance_specs(source, specs)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
res.xpath('//field[@name="account_move_id"]')[0].attrib["context"],
|
res.xpath('//field[@name="account_move_id"]')[0].attrib["context"],
|
||||||
"{'default_journal_id': journal_id, 'default_company_id': company_id}",
|
"{'default_journal_id': journal_id, 'default_company_id': company_id}",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_python_dict_inheritance_context_complex(self):
|
def test_update_context_complex(self):
|
||||||
source = etree.fromstring(
|
source = etree.fromstring(
|
||||||
"""
|
"""
|
||||||
<form>
|
<form>
|
||||||
|
@ -142,27 +74,39 @@ class TestBaseViewInheritanceExtension(SavepointCase):
|
||||||
specs = etree.fromstring(
|
specs = etree.fromstring(
|
||||||
"""
|
"""
|
||||||
<field name="invoice_line_ids" position="attributes">
|
<field name="invoice_line_ids" position="attributes">
|
||||||
<attribute name="context" operation="python_dict" key="default_product_id">
|
<attribute name="context" operation="update">
|
||||||
product_id
|
{
|
||||||
</attribute>
|
"default_product_id": product_id,
|
||||||
<attribute name="context" operation="python_dict" key="default_cost_center_id">
|
"default_cost_center_id": (
|
||||||
context.get('handle_mrp_cost') and cost_center_id or False
|
context.get("handle_mrp_cost") and cost_center_id or False
|
||||||
|
),
|
||||||
|
}
|
||||||
</attribute>
|
</attribute>
|
||||||
</field>
|
</field>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
res = self.ViewModel.inheritance_handler_attributes_python_dict(source, specs)
|
res = self.env["ir.ui.view"].apply_inheritance_specs(source, specs)
|
||||||
|
expected_items = [
|
||||||
|
"'default_type': context.get('default_type')",
|
||||||
|
"'journal_id': journal_id",
|
||||||
|
"'default_partner_id': commercial_partner_id",
|
||||||
|
(
|
||||||
|
"'default_currency_id': "
|
||||||
|
"currency_id != company_currency_id and currency_id or False"
|
||||||
|
),
|
||||||
|
"'default_name': 'The company name'",
|
||||||
|
"'default_product_id': product_id",
|
||||||
|
(
|
||||||
|
"'default_cost_center_id': "
|
||||||
|
"context.get('handle_mrp_cost') and cost_center_id or False"
|
||||||
|
),
|
||||||
|
]
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
res.xpath('//field[@name="invoice_line_ids"]')[0].attrib["context"],
|
res.xpath('//field[@name="invoice_line_ids"]')[0].attrib["context"],
|
||||||
"{'default_type': context.get('default_type'), 'journal_id': journal_id, "
|
"{%s}" % ", ".join(expected_items),
|
||||||
"'default_partner_id': commercial_partner_id, 'default_currency_id': "
|
|
||||||
"currency_id != company_currency_id and currency_id or False, "
|
|
||||||
"'default_name': 'The company name', 'default_product_id': product_id, "
|
|
||||||
"'default_cost_center_id': context.get('handle_mrp_cost') and "
|
|
||||||
"cost_center_id or False}",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_python_dict_inheritance_attrs_add(self):
|
def test_update_attrs_new_key(self):
|
||||||
"""Test that we can add new keys to an existing dict"""
|
"""Test that we can add new keys to an existing dict"""
|
||||||
source = etree.fromstring(
|
source = etree.fromstring(
|
||||||
"""
|
"""
|
||||||
|
@ -177,20 +121,22 @@ class TestBaseViewInheritanceExtension(SavepointCase):
|
||||||
specs = etree.fromstring(
|
specs = etree.fromstring(
|
||||||
"""
|
"""
|
||||||
<field name="ref" position="attributes">
|
<field name="ref" position="attributes">
|
||||||
<attribute name="attrs" operation="python_dict" key="required">
|
<attribute name="attrs" operation="update">
|
||||||
[('state', '!=', 'draft')]
|
{
|
||||||
|
"required": [("state", "!=", "draft")],
|
||||||
|
}
|
||||||
</attribute>
|
</attribute>
|
||||||
</field>
|
</field>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
res = self.ViewModel.inheritance_handler_attributes_python_dict(source, specs)
|
res = self.env["ir.ui.view"].apply_inheritance_specs(source, specs)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
res.xpath('//field[@name="ref"]')[0].attrib["attrs"],
|
res.xpath('//field[@name="ref"]')[0].attrib["attrs"],
|
||||||
"{'invisible': [('state', '=', 'draft')], "
|
"{'invisible': [('state', '=', 'draft')], "
|
||||||
"'required': [('state', '!=', 'draft')]}",
|
"'required': [('state', '!=', 'draft')]}",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_python_dict_inheritance_attrs_update(self):
|
def test_update_attrs_replace(self):
|
||||||
"""Test that we can replace an existing dict key"""
|
"""Test that we can replace an existing dict key"""
|
||||||
source = etree.fromstring(
|
source = etree.fromstring(
|
||||||
"""
|
"""
|
||||||
|
@ -208,20 +154,22 @@ class TestBaseViewInheritanceExtension(SavepointCase):
|
||||||
specs = etree.fromstring(
|
specs = etree.fromstring(
|
||||||
"""
|
"""
|
||||||
<field name="ref" position="attributes">
|
<field name="ref" position="attributes">
|
||||||
<attribute name="attrs" operation="python_dict" key="required">
|
<attribute name="attrs" operation="update">
|
||||||
[('state', '!=', 'draft')]
|
{
|
||||||
|
"required": [('state', '!=', 'draft')],
|
||||||
|
}
|
||||||
</attribute>
|
</attribute>
|
||||||
</field>
|
</field>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
res = self.ViewModel.inheritance_handler_attributes_python_dict(source, specs)
|
res = self.env["ir.ui.view"].apply_inheritance_specs(source, specs)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
res.xpath('//field[@name="ref"]')[0].attrib["attrs"],
|
res.xpath('//field[@name="ref"]')[0].attrib["attrs"],
|
||||||
"{'invisible': [('state', '=', 'draft')], "
|
"{'invisible': [('state', '=', 'draft')], "
|
||||||
"'required': [('state', '!=', 'draft')]}",
|
"'required': [('state', '!=', 'draft')]}",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_python_dict_inheritance_attrs_new(self):
|
def test_update_empty_source_dict(self):
|
||||||
"""Test that we can add new keys by creating the dict if it's missing"""
|
"""Test that we can add new keys by creating the dict if it's missing"""
|
||||||
source = etree.fromstring(
|
source = etree.fromstring(
|
||||||
"""
|
"""
|
||||||
|
@ -233,20 +181,22 @@ class TestBaseViewInheritanceExtension(SavepointCase):
|
||||||
specs = etree.fromstring(
|
specs = etree.fromstring(
|
||||||
"""
|
"""
|
||||||
<field name="ref" position="attributes">
|
<field name="ref" position="attributes">
|
||||||
<attribute name="attrs" operation="python_dict" key="required">
|
<attribute name="attrs" operation="update">
|
||||||
[('state', '!=', 'draft')]
|
{
|
||||||
|
"required": [('state', '!=', 'draft')],
|
||||||
|
}
|
||||||
</attribute>
|
</attribute>
|
||||||
</field>
|
</field>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
res = self.ViewModel.inheritance_handler_attributes_python_dict(source, specs)
|
res = self.env["ir.ui.view"].apply_inheritance_specs(source, specs)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
res.xpath('//field[@name="ref"]')[0].attrib["attrs"],
|
res.xpath('//field[@name="ref"]')[0].attrib["attrs"],
|
||||||
"{'required': [('state', '!=', 'draft')]}",
|
"{'required': [('state', '!=', 'draft')]}",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_python_dict_inheritance_attrs_missing_key(self):
|
def test_update_operation_not_a_dict(self):
|
||||||
"""We should get an error if we try to update a dict without specifing a key"""
|
"""We should get an error if we try to update a dict with a non-dict spec"""
|
||||||
source = etree.fromstring(
|
source = etree.fromstring(
|
||||||
"""
|
"""
|
||||||
<form>
|
<form>
|
||||||
|
@ -257,18 +207,18 @@ class TestBaseViewInheritanceExtension(SavepointCase):
|
||||||
specs = etree.fromstring(
|
specs = etree.fromstring(
|
||||||
"""
|
"""
|
||||||
<field name="ref" position="attributes">
|
<field name="ref" position="attributes">
|
||||||
<attribute name="attrs" operation="python_dict">
|
<attribute name="attrs" operation="update">
|
||||||
[('state', '!=', 'draft')]
|
["not", "a", "dict"]
|
||||||
</attribute>
|
</attribute>
|
||||||
</field>
|
</field>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaisesRegex(
|
||||||
AssertionError, "No key specified for 'python_dict' operation"
|
TypeError, "Operation for attribute `attrs` is not a dict"
|
||||||
):
|
):
|
||||||
self.ViewModel.inheritance_handler_attributes_python_dict(source, specs)
|
self.env["ir.ui.view"].apply_inheritance_specs(source, specs)
|
||||||
|
|
||||||
def test_python_dict_inheritance_error_if_not_a_dict(self):
|
def test_update_source_not_a_dict(self):
|
||||||
"""We should get an error if we try to update a non-dict attribute"""
|
"""We should get an error if we try to update a non-dict attribute"""
|
||||||
source = etree.fromstring(
|
source = etree.fromstring(
|
||||||
"""
|
"""
|
||||||
|
@ -280,11 +230,13 @@ class TestBaseViewInheritanceExtension(SavepointCase):
|
||||||
specs = etree.fromstring(
|
specs = etree.fromstring(
|
||||||
"""
|
"""
|
||||||
<field name="child_ids" position="attributes">
|
<field name="child_ids" position="attributes">
|
||||||
<attribute name="domain" operation="python_dict" key="required">
|
<attribute name="domain" operation="update">
|
||||||
[('state', '!=', 'draft')]
|
{
|
||||||
|
"required": [('state', '!=', 'draft')],
|
||||||
|
}
|
||||||
</attribute>
|
</attribute>
|
||||||
</field>
|
</field>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
with self.assertRaisesRegex(AssertionError, "'domain' is not a dict"):
|
with self.assertRaisesRegex(TypeError, "Attribute `domain` is not a dict"):
|
||||||
self.ViewModel.inheritance_handler_attributes_python_dict(source, specs)
|
self.env["ir.ui.view"].apply_inheritance_specs(source, specs)
|
||||||
|
|
Loading…
Reference in New Issue