Odoo18-Base/extra-addons/test_web_studio/tests/test_exports.py

817 lines
32 KiB
Python

import ast
import base64
from itertools import starmap
from lxml import etree as ET
from odoo import Command
from odoo.addons.web_studio.controllers.export import ir_model_data_getter, generate_module, _clean_dependencies
from odoo.addons.website.tools import MockRequest
from odoo.osv import expression
from odoo.tests.common import TransactionCase, tagged
# ---------------------------------- HELPERS ----------------------------------
XMLPARSER = ET.XMLParser(remove_blank_text=True, strip_cdata=False, resolve_entities=False)
IR_MODEL_INFO_FIELD = """<field name="info"><![CDATA[ Main super-class for regular database-persisted Odoo models.
Odoo models are created by inheriting from this class::
class user(Model):
...
The system will later instantiate the class once per database (on
which the class' module is installed).
]]></field>"""
def nodes_equal(n1, n2):
if n1.tag != n2.tag:
return False
if n1.text != n2.text:
return False
if n1.tail != n2.tail:
return False
if n1.attrib != n2.attrib:
return False
if len(n1) != len(n2):
return False
if n1.tag == "field":
# compare a tostring version, to check if CDATA sections are preserved
n1_str = ET.tostring(n1)
n2_str = ET.tostring(n2)
if n1_str != n2_str:
return False
if n1.tag == "record":
# n1 and n2 children order doesn't matter, sort them by tagname, attrib['name']
n1 = sorted(n1, key=lambda n: (n.tag, n.attrib.get("name")))
n2 = sorted(n2, key=lambda n: (n.tag, n.attrib.get("name")))
return all(starmap(nodes_equal, zip(n1, n2)))
class StudioExportCase(TransactionCase):
def setUp(self):
super().setUp()
self.model_data_getter = ir_model_data_getter(self.env['studio.export.wizard.data'])
self._customizations = []
self._additional_models = self.env["studio.export.model"]
self._additional_models.search([]).unlink()
self.TestModel = self.env["test.studio_export.model1"].with_user(2)
self.TestModel2 = self.env["test.studio_export.model2"].with_user(2)
self.TestModel3 = self.env["test.studio_export.model3"].with_user(2)
def create_customization(self, _model, **kwargs):
Model = self.env[_model].with_context(studio=True)
custo = Model.create(kwargs)
self._customizations.append(custo)
return custo
def create_export_model(self, _model, **kwargs):
IrModel = self.env["ir.model"]
vals = {"model_id": IrModel._get_id(_model)}
vals.update(kwargs)
export_model = self.env["studio.export.model"].create(vals)
self._additional_models |= export_model
self.addCleanup(export_model.unlink)
return export_model
def get_xmlid(self, record):
if self._current_wizard:
self.model_data_getter = ir_model_data_getter(self._current_wizard.default_export_data | self._current_wizard.additional_export_data)
return self.model_data_getter(record)._xmlid_for_export()
def studio_export(self):
# Get all customization data
custo_domains = [
[("model", "=", custo._name), ("res_id", "=", custo.id)]
for custo in self._customizations
]
domain = expression.OR(custo_domains)
domain = expression.AND([domain, [("studio", "=", True)]])
custo_data = self.env["ir.model.data"].search(domain)
custo_data = self.env["studio.export.wizard.data"].create(
[
{"model": d.model, "res_id": d.res_id, "studio": d.studio}
for d in custo_data
]
)
studio_module = self.env["ir.module.module"].get_studio_module()
self._current_wizard = self.env["studio.export.wizard"].create(
{
"default_export_data": [Command.set(custo_data.ids)],
"additional_models": [Command.set(self._additional_models.ids)],
"include_additional_data": True,
"include_demo_data": True,
}
)
export_info = self._current_wizard._get_export_info()
content = generate_module(studio_module, export_info)
return StudioExportAssertor(export_case=self, content=content)
class StudioExportAssertor:
def __init__(self, export_case, content) -> None:
self.export_case = export_case
self.exported_cache = {}
self.content_iter = iter(content)
def get_exported(self, name=None):
"""If name not found (or None) will iterate on all generated files
Returns:
If name is provided, returns the exported file content associated with that name.
If name is not provided or not found, returns a dictionary of all exported files.
"""
with MockRequest(self.export_case.env):
while name not in self.exported_cache:
try:
path, content = next(self.content_iter)
except StopIteration:
break
if path.endswith(".xml"):
self.exported_cache[path] = ET.fromstring(content, parser=XMLPARSER)
elif path.endswith("__manifest__.py"):
self.exported_cache[path] = ast.literal_eval(content.decode("utf-8"))
else:
self.exported_cache[path] = content
return self.exported_cache[name] if name else self.exported_cache
def assertFileContains(self, path, content):
file = self.get_exported(path)
self.export_case.assertEqual(file, content)
def assertFileList(self, *filenames):
"""You can omit __init__.py and __manifest__.py"""
filenames += ("__init__.py", "__manifest__.py")
exported = self.get_exported()
self.export_case.assertEqual(set(exported.keys()), set(filenames))
def assertManifest(self, **expected):
exported = self.get_exported("__manifest__.py")
for key, value in expected.items():
if key == "depends":
with MockRequest(self.export_case.env):
self.export_case.assertEqual(
_clean_dependencies(set(exported["depends"] + value)),
exported["depends"],
)
else:
self.export_case.assertEqual(exported[key], value)
def assertXML(self, path, expected):
root = self.get_exported(path)
# parse expected then compare with nodes_equal
expected = ET.fromstring(expected, parser=XMLPARSER)
are_equal = nodes_equal(root, expected)
message = "Both XMLs are equal"
if not are_equal:
tostring_opts = {"encoding": "unicode", "pretty_print": True}
expected = ET.tostring(expected, **tostring_opts)
actual = ET.tostring(root, **tostring_opts)
message = "\nExpected:\n%s\nActual:\n%s" % (expected, actual)
self.export_case.assertTrue(are_equal, message)
# ----------------------------------- TESTS -----------------------------------
@tagged("-at_install", "post_install")
class TestStudioExports(StudioExportCase):
def test_export_customizations(self):
custom_model = self.create_customization(
"ir.model", name="Furnace Types", model="x_furnace_types"
)
custom_field = self.create_customization(
"ir.model.fields",
name="x_studio_max_temp",
complete_name="Max temperature",
ttype="integer",
model_id=custom_model.id,
)
custom_view = self.create_customization(
"ir.ui.view",
name="Kanban view for x_furnace_types",
model="x_furnace_types",
type="kanban",
arch="""
<kanban>
<templates>
<t t-name="card">
<field name="x_studio_max_temp" />
</t>
</templates>
</kanban>
""",
)
custom_action = self.create_customization(
"ir.actions.act_window",
name="Furnaces",
res_model="x_furnace_types",
view_mode="list,form,kanban",
help="<p>This is your new action.</p>",
)
custom_menu_1 = self.create_customization(
"ir.ui.menu",
name="My Furnaces",
)
custom_menu_2 = self.create_customization(
"ir.ui.menu",
name="Furnaces Types",
parent_id=custom_menu_1.id,
action=f"ir.actions.act_window,{custom_action.id}",
)
# Create a record to show that it is not exported
# (appears neither in manifest nor in filelist)
self.env[custom_model.model].create(
{"x_name": "Austenitization", "x_studio_max_temp": 1200}
)
export = self.studio_export()
export.assertManifest(
data=[
"data/ir_model.xml",
"data/ir_model_fields.xml",
"data/ir_ui_view.xml",
"data/ir_actions_act_window.xml",
"data/ir_ui_menu.xml",
],
depends=["web_studio"],
)
export.assertFileList(
"data/ir_model.xml",
"data/ir_model_fields.xml",
"data/ir_ui_view.xml",
"data/ir_actions_act_window.xml",
"data/ir_ui_menu.xml",
)
export.assertXML(
"data/ir_model.xml",
f"""<odoo>
<record id="{self.get_xmlid(custom_model)}" model="ir.model" context="{{'studio': True}}">
{IR_MODEL_INFO_FIELD}
<field name="model">x_furnace_types</field>
<field name="name">Furnace Types</field>
</record>
</odoo>""",
)
export.assertXML(
"data/ir_model_fields.xml",
f"""<odoo>
<record id="{self.get_xmlid(custom_field)}" model="ir.model.fields" context="{{'studio': True}}">
<field name="complete_name">Max temperature</field>
<field name="ttype">integer</field>
<field name="copied" eval="True"/>
<field name="field_description">X Studio Max Temp</field>
<field name="model">x_furnace_types</field>
<field name="model_id" ref="{self.get_xmlid(custom_model)}"/>
<field name="name">x_studio_max_temp</field>
<field name="on_delete" eval="False"/>
</record>
</odoo>""",
)
export.assertXML(
"data/ir_ui_view.xml",
f"""<odoo>
<record id="{self.get_xmlid(custom_view)}" model="ir.ui.view" context="{{'studio': True}}">
<field name="arch" type="xml">
<kanban>
<templates>
<t t-name="card">
<field name="x_studio_max_temp" />
</t>
</templates>
</kanban>
</field>
<field name="model">x_furnace_types</field>
<field name="name">Kanban view for x_furnace_types</field>
<field name="type">kanban</field>
</record>
</odoo>""",
)
export.assertXML(
"data/ir_actions_act_window.xml",
f"""<odoo>
<record id="{self.get_xmlid(custom_action)}" model="ir.actions.act_window" context="{{'studio': True}}">
<field name="help"><![CDATA[<p>This is your new action.</p>]]></field>
<field name="name">Furnaces</field>
<field name="res_model">x_furnace_types</field>
<field name="view_mode">list,form,kanban</field>
</record>
</odoo>""",
)
export.assertXML(
"data/ir_ui_menu.xml",
f"""<odoo>
<record id="{self.get_xmlid(custom_menu_1)}" model="ir.ui.menu" context="{{'studio': True}}">
<field name="name">My Furnaces</field>
</record>
<record id="{self.get_xmlid(custom_menu_2)}" model="ir.ui.menu" context="{{'studio': True}}">
<field name="action" ref="{self.get_xmlid(custom_action)}" />
<field name="name">Furnaces Types</field>
<field name="parent_id" ref="{self.get_xmlid(custom_menu_1)}" />
</record>
</odoo>""",
)
def test_export_customizations_with_export_model(self):
custom_model = self.create_customization(
"ir.model", name="Furnace Types", model="x_furnace_types"
)
self.create_customization(
"ir.model.fields",
name="x_studio_max_temp",
complete_name="Max temperature",
ttype="integer",
model_id=custom_model.id,
)
CustomModel = self.env[custom_model.model].with_user(2).sudo()
furnace_type = CustomModel.create(
{"x_name": "Austenitization", "x_studio_max_temp": 1200}
)
# Without export model, the custom model data are not exported
export = self.studio_export()
export.assertFileList("data/ir_model.xml", "data/ir_model_fields.xml")
# With the export model, the custom model data is exported
self.create_export_model(CustomModel._name)
export = self.studio_export()
export.assertFileList(
"data/ir_model.xml",
"data/ir_model_fields.xml",
"data/x_furnace_types.xml",
)
export.assertXML(
"data/x_furnace_types.xml",
f"""<odoo>
<record id="{self.get_xmlid(furnace_type)}" model="x_furnace_types">
<field name="x_studio_max_temp">1200</field>
<field name="x_name">Austenitization</field>
</record>
</odoo>""",
)
def test_simple_export_model(self):
export_model = self.create_export_model(self.TestModel._name)
# Without record, the export_model has no effect
export = self.studio_export()
export.assertFileList()
# Simple case
some_record = self.TestModel.create({"name": "Some record"})
export = self.studio_export()
export.assertFileList("data/test_studio_export_model1.xml")
export.assertXML("data/test_studio_export_model1.xml", f"""
<odoo>
<record id="{self.get_xmlid(some_record)}" model="test.studio_export.model1">
<field name="name">Some record</field>
</record>
</odoo>
""")
# Without updatable mode
export_model.updatable = False
export = self.studio_export()
export.assertFileList("data/test_studio_export_model1.xml")
export.assertXML("data/test_studio_export_model1.xml", f"""
<odoo noupdate="1">
<record id="{self.get_xmlid(some_record)}" model="test.studio_export.model1">
<field name="name">Some record</field>
</record>
</odoo>
""")
# With is_demo_data mode, with updatable
export_model.updatable = True
export_model.is_demo_data = True
export = self.studio_export()
export.assertFileList("demo/test_studio_export_model1.xml")
export.assertXML("demo/test_studio_export_model1.xml", f"""
<odoo>
<record id="{self.get_xmlid(some_record)}" model="test.studio_export.model1">
<field name="name">Some record</field>
</record>
</odoo>
""")
# With is_demo_data mode, without updatable
export_model.updatable = False
export_model.is_demo_data = True
export = self.studio_export()
export.assertFileList("demo/test_studio_export_model1.xml")
export.assertXML("demo/test_studio_export_model1.xml", f"""
<odoo noupdate="1">
<record id="{self.get_xmlid(some_record)}" model="test.studio_export.model1">
<field name="name">Some record</field>
</record>
</odoo>
""")
def test_export_model_with_demo_data(self):
some_record = self.TestModel.create({"name": "Some record"})
other_record = self.TestModel.create({"name": "Some other record"})
self.create_export_model(self.TestModel._name, is_demo_data=True)
export = self.studio_export()
export.assertFileList("demo/test_studio_export_model1.xml")
export.assertXML(
"demo/test_studio_export_model1.xml",
f"""<odoo>
<record id="{self.get_xmlid(some_record)}" model="test.studio_export.model1">
<field name="name">Some record</field>
</record>
<record id="{self.get_xmlid(other_record)}" model="test.studio_export.model1">
<field name="name">Some other record</field>
</record>
</odoo>""",
)
def test_export_model_with_binary_field(self):
some_record = self.TestModel.create(
{
"name": "Some record",
"binary_data": base64.b64encode(b"My binary attachment"),
}
)
export_model = self.create_export_model(self.TestModel._name)
# Without include_attachment
export = self.studio_export()
export.assertFileList(
"data/test_studio_export_model1.xml",
f"static/src/binary/test_studio_export_model1/{some_record.id}-binary_data",
)
export.assertXML(
"data/test_studio_export_model1.xml",
f"""<odoo>
<record id="{self.get_xmlid(some_record)}" model="test.studio_export.model1">
<field name="name">Some record</field>
<field name="binary_data" type="base64" file="studio_customization/static/src/binary/test_studio_export_model1/{some_record.id}-binary_data"/>
</record>
</odoo>""",
)
# With include_attachment we have the same export result
export_model.include_attachment = True
export = self.studio_export()
export.assertFileList(
"data/test_studio_export_model1.xml",
f"static/src/binary/test_studio_export_model1/{some_record.id}-binary_data",
)
export.assertXML(
"data/test_studio_export_model1.xml",
f"""<odoo>
<record id="{self.get_xmlid(some_record)}" model="test.studio_export.model1">
<field name="name">Some record</field>
<field name="binary_data" type="base64" file="studio_customization/static/src/binary/test_studio_export_model1/{some_record.id}-binary_data"/>
</record>
</odoo>""",
)
def test_export_model_with_many2one_attachment(self):
some_record = self.TestModel.create({"name": "Some record"})
attachment = self.env["ir.attachment"].create(
{
"name": "Some attachment",
"datas": base64.b64encode(b"My attachment"),
"res_model": self.TestModel._name,
"res_id": some_record.id,
"res_field": "attachment_id",
}
)
some_record.attachment_id = attachment
self.create_export_model(self.TestModel._name, include_attachment=True)
export = self.studio_export()
export.assertFileList(
"data/test_studio_export_model1.xml",
"data/ir_attachment_pre.xml",
f"static/src/binary/ir_attachment/{attachment.id}-Someattachment",
)
export.assertManifest(
depends=["test_web_studio"],
data=["data/ir_attachment_pre.xml", "data/test_studio_export_model1.xml"],
)
export.assertXML(
"data/ir_attachment_pre.xml",
f"""<odoo noupdate="1">
<record id="{self.get_xmlid(attachment)}" model="ir.attachment">
<field name="name">Some attachment</field>
<field name="datas" type="base64" file="studio_customization/static/src/binary/ir_attachment/{attachment.id}-Someattachment"/>
</record>
</odoo>""",
)
export.assertXML(
"data/test_studio_export_model1.xml",
f"""<odoo>
<record id="{self.get_xmlid(some_record)}" model="test.studio_export.model1">
<field name="name">Some record</field>
<field name="attachment_id" ref="{self.get_xmlid(attachment)}"/>
</record>
</odoo>""",
)
def test_export_model_with_one2many_attachment(self):
some_record = self.TestModel.create({"name": "Some record"})
attachment1 = self.env["ir.attachment"].create(
{
"name": "Some attachment",
"datas": base64.b64encode(b"My attachment"),
"res_model": self.TestModel._name,
"res_id": some_record.id,
"res_field": "attachment_ids",
}
)
attachment2 = self.env["ir.attachment"].create(
{
"name": "Another attachment",
"datas": base64.b64encode(b"My second attachment"),
"res_model": self.TestModel._name,
"res_id": some_record.id,
"res_field": "attachment_ids",
}
)
some_record.attachment_ids = [Command.set([attachment1.id, attachment2.id])]
self.create_export_model(self.TestModel._name, include_attachment=True)
export = self.studio_export()
export.assertFileList(
"data/test_studio_export_model1.xml",
"data/ir_attachment_post.xml",
f"static/src/binary/ir_attachment/{attachment1.id}-Someattachment",
f"static/src/binary/ir_attachment/{attachment2.id}-Anotherattachment",
)
export.assertManifest(
depends=["test_web_studio"],
data=["data/test_studio_export_model1.xml", "data/ir_attachment_post.xml"],
)
export.assertXML(
"data/ir_attachment_post.xml",
f"""<odoo noupdate="1">
<record id="{self.get_xmlid(attachment2)}" model="ir.attachment">
<field name="name">Another attachment</field>
<field name="datas" type="base64" file="studio_customization/static/src/binary/ir_attachment/{attachment2.id}-Anotherattachment" />
<field name="res_id" ref="{self.get_xmlid(some_record)}" />
<field name="res_model">test.studio_export.model1</field>
<field name="res_field">attachment_ids</field>
</record>
<record id="{self.get_xmlid(attachment1)}" model="ir.attachment">
<field name="name">Some attachment</field>
<field name="datas" type="base64" file="studio_customization/static/src/binary/ir_attachment/{attachment1.id}-Someattachment" />
<field name="res_id" ref="{self.get_xmlid(some_record)}" />
<field name="res_model">test.studio_export.model1</field>
<field name="res_field">attachment_ids</field>
</record>
</odoo>""",
)
export.assertXML(
"data/test_studio_export_model1.xml",
f"""<odoo>
<record id="{self.get_xmlid(some_record)}" model="test.studio_export.model1">
<field name="name">Some record</field>
</record>
</odoo>""",
)
def test_empty_models_and_fields(self):
# Test that models without records do not export any data
# and empty fields are not exported
model2_record1 = self.TestModel2.create({
"name": "Some Record"
})
model2_record2 = self.TestModel2.create({
"name": "",
"model2_id": model2_record1.id
})
self.create_export_model(self.TestModel2._name)
self.create_export_model(self.TestModel3._name)
export = self.studio_export()
export.assertManifest(
data=[
"data/test_studio_export_model2.xml",
],
)
export.assertFileList(
"data/test_studio_export_model2.xml",
)
export.assertXML(
"data/test_studio_export_model2.xml",
f"""<odoo>
<record id="{self.get_xmlid(model2_record1)}" model="test.studio_export.model2">
<field name="name">Some Record</field>
</record>
<record id="{self.get_xmlid(model2_record2)}" model="test.studio_export.model2">
<field name="model2_id" ref="{self.get_xmlid(model2_record1)}"/>
</record>
</odoo>""",
)
def test_export_data_related_to_demo(self):
# Test that master data (non demo) does not export fields related
# to demo records, but data records related to demo are also exported
# as demo with only the fields related to said demo records.
model3_record = self.TestModel3.create({"name": "Some record"})
model2_record = self.TestModel2.create({
"name": "Some other record",
"model3_id": model3_record.id
})
self.create_export_model(self.TestModel2._name, is_demo_data=False)
self.create_export_model(self.TestModel3._name, is_demo_data=True)
export = self.studio_export()
export.assertFileList(
"data/test_studio_export_model2.xml",
"demo/test_studio_export_model2.xml",
"demo/test_studio_export_model3.xml",
)
export.assertXML(
"data/test_studio_export_model2.xml",
f"""<odoo>
<record id="{self.get_xmlid(model2_record)}" model="test.studio_export.model2">
<field name="name">Some other record</field>
</record>
</odoo>""",
)
export.assertXML(
"demo/test_studio_export_model3.xml",
f"""<odoo>
<record id="{self.get_xmlid(model3_record)}" model="test.studio_export.model3">
<field name="name">Some record</field>
</record>
</odoo>""",
)
export.assertXML(
"demo/test_studio_export_model2.xml",
f"""<odoo>
<record id="{self.get_xmlid(model2_record)}" model="test.studio_export.model2">
<field name="model3_id" ref="{self.get_xmlid(model3_record)}"/>
</record>
</odoo>""",
)
def test_export_dependencies_order(self):
# Test that files and records order respects dependencies
model3_record = self.TestModel3.create({"name": "Some record"})
model2_record = self.TestModel2.create({
"name": "Some other record",
"model3_id": model3_record.id
})
model2b_record = self.TestModel2.create({
"name": "Some other record",
"model2_id": model2_record.id,
"model3_id": model3_record.id
})
self.create_export_model(self.TestModel2._name)
self.create_export_model(self.TestModel3._name)
export = self.studio_export()
export.assertManifest(
data=[
"data/test_studio_export_model3.xml",
"data/test_studio_export_model2.xml",
],
)
export.assertFileList(
"data/test_studio_export_model3.xml",
"data/test_studio_export_model2.xml",
)
export.assertXML(
"data/test_studio_export_model3.xml",
f"""<odoo>
<record id="{self.get_xmlid(model3_record)}" model="test.studio_export.model3">
<field name="name">Some record</field>
</record>
</odoo>""",
)
export.assertXML(
"data/test_studio_export_model2.xml",
f"""<odoo>
<record id="{self.get_xmlid(model2_record)}" model="test.studio_export.model2">
<field name="name">Some other record</field>
<field name="model3_id" ref="{self.get_xmlid(model3_record)}"/>
</record>
<record id="{self.get_xmlid(model2b_record)}" model="test.studio_export.model2">
<field name="name">Some other record</field>
<field name="model2_id" ref="{self.get_xmlid(model2_record)}"/>
<field name="model3_id" ref="{self.get_xmlid(model3_record)}"/>
</record>
</odoo>""",
)
def test_export_handles_circular_dependencies(self):
# Test that models circular dependencies appear in warning.txt
# and only if some records causes it
model3_record = self.TestModel3.create({
"name": "Record 3",
})
model2_record = self.TestModel2.create({
"name": "Record 2",
"model3_id": model3_record.id
})
model1_record = self.TestModel.create({
"name": "Record 1",
"model2_id": model2_record.id
})
model3_record.update({"model1_id": model1_record.id})
self.create_export_model(self.TestModel._name)
self.create_export_model(self.TestModel2._name)
self.create_export_model(self.TestModel3._name)
export = self.studio_export()
export.assertFileList(
"warnings.txt",
"data/test_studio_export_model1.xml",
"data/test_studio_export_model2.xml",
"data/test_studio_export_model3.xml",
)
export.assertXML(
"data/test_studio_export_model1.xml",
f"""<odoo>
<record id="{self.get_xmlid(model1_record)}" model="test.studio_export.model1">
<field name="name">Record 1</field>
<field name="model2_id" ref="{self.get_xmlid(model2_record)}"/>
</record>
</odoo>""",
)
export.assertXML(
"data/test_studio_export_model2.xml",
f"""<odoo>
<record id="{self.get_xmlid(model2_record)}" model="test.studio_export.model2">
<field name="name">Record 2</field>
<field name="model3_id" ref="{self.get_xmlid(model3_record)}"/>
</record>
</odoo>""",
)
export.assertXML(
"data/test_studio_export_model3.xml",
f"""<odoo>
<record id="{self.get_xmlid(model3_record)}" model="test.studio_export.model3">
<field name="name">Record 3</field>
<field name="model1_id" ref="{self.get_xmlid(model1_record)}"/>
</record>
</odoo>""",
)
export.assertFileContains(
"warnings.txt",
f"""Found 1 circular dependencies (you may have to change data loading order to avoid issues when importing):
(data) {self.TestModel._name} -> {self.TestModel2._name} -> {self.TestModel3._name} -> {self.TestModel._name}
""",
)
def test_export_abstract_actions_with_proper_types(self):
WindowActions = self.env["ir.actions.act_window"].with_user(2)
window_action = WindowActions.create({
"name": "Test action",
"type": "ir.actions.act_window",
"res_model": self.TestModel._name,
"view_mode": "form",
"target": "new",
})
URLActions = self.env["ir.actions.act_url"].with_user(2)
url_action = URLActions.create({
"name": "Test action",
"type": "ir.actions.act_url",
"url": "http://odoo.com",
})
self.create_export_model("ir.actions.actions", domain=[("id", "in", [window_action.id, url_action.id])])
export = self.studio_export()
export.assertFileList(
"data/ir_actions_act_window.xml",
"data/ir_actions_act_url.xml",
)
export.assertXML(
"data/ir_actions_act_window.xml",
f"""<odoo>
<record id="{self.get_xmlid(window_action)}" model="ir.actions.act_window">
<field name="name">Test action</field>
<field name="res_model">{self.TestModel._name}</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>""",
)
export.assertXML(
"data/ir_actions_act_url.xml",
f"""<odoo>
<record id="{self.get_xmlid(url_action)}" model="ir.actions.act_url">
<field name="name">Test action</field>
<field name="display_name">Test action</field>
<field name="url"><![CDATA[http://odoo.com]]></field>
</record>
</odoo>""",
)