diff options
| author | Xavier Morel <xmo@odoo.com> | 2017-11-14 11:47:44 +0100 |
|---|---|---|
| committer | Xavier Morel <xmo@odoo.com> | 2017-12-04 13:11:53 +0100 |
| commit | 4bf97bdbe28cc3dc3456a272b0acc3741fdf6e1b (patch) | |
| tree | 22b22bafa3a8c763fb30e984fdbb411968dd8c02 /odoo/addons/test_testing_utilities | |
| parent | 026979c278175de4fa27fba1843b5b9320894d3e (diff) | |
[ADD] base: server-side formview
Useful to better test business flows & replace yaml test files:
instantiating records & calling onchanges by hand is very
error-prone (it's easy to miss onchanges, or to manually set fields
which can't be set through the view we're interested in, ...)
odoo.tests.common.Form implements the basic *creation*
flow (fields_get, default_get, onchange) up to saving the record,
including proper handling of the required and readonly modifiers &
their domains.
* edition of existing records, including filtering out unmodified
fields when saving
* possibly buttons
* ensuring the API is convenient
Diffstat (limited to 'odoo/addons/test_testing_utilities')
| -rw-r--r-- | odoo/addons/test_testing_utilities/__init__.py | 2 | ||||
| -rw-r--r-- | odoo/addons/test_testing_utilities/__manifest__.py | 15 | ||||
| -rw-r--r-- | odoo/addons/test_testing_utilities/ir.model.access.csv | 14 | ||||
| -rw-r--r-- | odoo/addons/test_testing_utilities/menu.xml | 94 | ||||
| -rw-r--r-- | odoo/addons/test_testing_utilities/models.py | 171 | ||||
| -rw-r--r-- | odoo/addons/test_testing_utilities/tests/__init__.py | 2 | ||||
| -rw-r--r-- | odoo/addons/test_testing_utilities/tests/test_form_impl.py | 461 |
7 files changed, 759 insertions, 0 deletions
diff --git a/odoo/addons/test_testing_utilities/__init__.py b/odoo/addons/test_testing_utilities/__init__.py new file mode 100644 index 00000000000..a0fdc10fe11 --- /dev/null +++ b/odoo/addons/test_testing_utilities/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/odoo/addons/test_testing_utilities/__manifest__.py b/odoo/addons/test_testing_utilities/__manifest__.py new file mode 100644 index 00000000000..eb7731a7826 --- /dev/null +++ b/odoo/addons/test_testing_utilities/__manifest__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +{ + 'name': "Test testing utilities", + 'category': "Tests", + 'description': """Non-trivial testing utilities can require models & all + +This here module is useful to validate that they're doing what they're +supposed to do + """, + 'depends': ['base'], + 'data': [ + 'ir.model.access.csv', + 'menu.xml', + ] +} diff --git a/odoo/addons/test_testing_utilities/ir.model.access.csv b/odoo/addons/test_testing_utilities/ir.model.access.csv new file mode 100644 index 00000000000..ced191d38e2 --- /dev/null +++ b/odoo/addons/test_testing_utilities/ir.model.access.csv @@ -0,0 +1,14 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_test_testing_utilities_a,access_test_testing_utilities_a,model_test_testing_utilities_a,,1,0,0,0 +access_test_testing_utilities_readonly,access_test_testing_utilities_readonly,model_test_testing_utilities_readonly,,1,0,0,0 +access_test_testing_utilities_c,access_test_testing_utilities_c,model_test_testing_utilities_c,,1,0,0,0 +access_test_testing_utilities_d,access_test_testing_utilities_d,model_test_testing_utilities_d,,1,0,0,0 +access_test_testing_utilities_e,access_test_testing_utilities_e,model_test_testing_utilities_e,,1,0,0,0 +access_test_testing_utilities_f,access_test_testing_utilities_f,model_test_testing_utilities_f,,1,0,0,0 +access_test_testing_utilities_g,access_test_testing_utilities_g,model_test_testing_utilities_g,,1,0,0,0 +access_test_testing_utilities_m2o,access_test_testing_utilities_m2o,model_test_testing_utilities_m2o,,1,0,0,0 +access_test_testing_utilities_default,access_test_testing_utilities_default,model_test_testing_utilities_default,,1,0,0,0 +access_test_testing_utilities_parent,access_test_testing_utilities_parent,model_test_testing_utilities_parent,,1,0,0,0 +access_test_testing_utilities_sub,access_test_testing_utilities_sub,model_test_testing_utilities_sub,,1,0,0,0 +access_test_testing_utilities_sub2,access_test_testing_utilities_sub2,model_test_testing_utilities_sub2,,1,0,0,0 +access_test_testing_utilities_sub3,access_test_testing_utilities_sub3,model_test_testing_utilities_sub3,,1,0,0,0 diff --git a/odoo/addons/test_testing_utilities/menu.xml b/odoo/addons/test_testing_utilities/menu.xml new file mode 100644 index 00000000000..72a4ba79157 --- /dev/null +++ b/odoo/addons/test_testing_utilities/menu.xml @@ -0,0 +1,94 @@ +<odoo> + <record id="action_test_o2m_onchange" model="ir.actions.act_window"> + <field name="name">Test o2m onchange thing</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">test_testing_utilities.parent</field> + <field name="view_mode">tree,form</field> + </record> + + <menuitem id="menu_o2m" parent="base.menu_tests" + name="O2M onchange test thing" + action="action_test_o2m_onchange"/> + + <record id="o2m_parent" model="ir.ui.view"> + <field name="name">Parent External Form</field> + <field name="priority">1</field> + <field name="model">test_testing_utilities.parent</field> + <field name="arch" type="xml"> + <form> + <sheet> + <group> + <field name="value"/> + <field name="v"/> + </group> + <group> + <field name="subs"/> + </group> + </sheet> + </form> + </field> + </record> + <record id="o2m_parent_readonly" model="ir.ui.view"> + <field name="name">Parent readonly o2m field</field> + <field name="model">test_testing_utilities.parent</field> + <field name="inherit_id" ref="o2m_parent"/> + <field name="mode">primary</field> + <field name="arch" type="xml"> + <xpath expr="//field[@name='subs']" position="attributes"> + <attribute name="readonly">1</attribute> + </xpath> + </field> + </record> + <record id="o2m_tree" model="ir.ui.view"> + <field name="name">Sub regular</field> + <field name="priority">1</field> + <field name="model">test_testing_utilities.sub</field> + <field name="arch" type="xml"> + <tree> + <field name="value"/> + <field name="v"/> + <field name="name"/> + </tree> + </field> + </record> + + <record id="o2m_parent_ed" model="ir.ui.view"> + <field name="name">Parent External Tree</field> + <field name="model">test_testing_utilities.parent</field> + <field name="arch" type="xml"> + <form> + <field name="value"/> + <field name="v"/> + <field name="subs" context="{'tree_view_ref': 'test_testing_utilities.editable_external'}"/> + </form> + </field> + </record> + <record id="editable_external" model="ir.ui.view"> + <field name="name">Sub regular</field> + <field name="model">test_testing_utilities.sub</field> + <field name="arch" type="xml"> + <tree editable="bottom"> + <field name="value"/> + <field name="v"/> + <field name="name"/> + </tree> + </field> + </record> + + <record id="o2m_parent_inline" model="ir.ui.view"> + <field name="name">Parent Inline Tree</field> + <field name="model">test_testing_utilities.parent</field> + <field name="arch" type="xml"> + <form> + <field name="value"/> + <field name="v"/> + <field name="subs"> + <tree editable="bottom"> + <field name="value"/> + <field name="name"/> + </tree> + </field> + </form> + </field> + </record> +</odoo> diff --git a/odoo/addons/test_testing_utilities/models.py b/odoo/addons/test_testing_utilities/models.py new file mode 100644 index 00000000000..2bb6e5e5dd6 --- /dev/null +++ b/odoo/addons/test_testing_utilities/models.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- +from __future__ import division +from odoo import api, fields, models + +class A(models.Model): + _name = 'test_testing_utilities.a' + + f1 = fields.Integer(required=True) + f2 = fields.Integer(default=42) + f3 = fields.Integer() + f4 = fields.Integer(compute='_compute_f4') + + @api.onchange('f2') + def _on_change_f2(self): + self.f3 = int(self.f2 / 2) + + @api.depends('f1', 'f2') + def _compute_f4(self): + for r in self: + r.f4 = r.f2 / (r.f1 or 1) + +class B(models.Model): + _name = 'test_testing_utilities.readonly' + + f1 = fields.Integer(default=1, readonly=True) + f2 = fields.Integer(compute='_compute_f2') + + @api.depends('f1') + def _compute_f2(self): + for r in self: + r.f2 = 2 * r.f1 + +class C(models.Model): + _name = 'test_testing_utilities.c' + + name = fields.Char("name", required=True) + f2 = fields.Many2one('test_testing_utilities.m2o') + + @api.onchange('f2') + def _on_change_f2(self): + self.name = self.f2.name + +class M2O(models.Model): + _name = 'test_testing_utilities.m2o' + + name = fields.Char(required=True) + +class M2Onchange(models.Model): + _name = 'test_testing_utilities.d' + + # used to check that defaults & onchange to m2o work + f = fields.Many2one( + 'test_testing_utilities.m2o', + required=True, + default=lambda self: self.env['test_testing_utilities.m2o'].search( + [], limit=1 + ) + ) + f2 = fields.Char() + + @api.onchange('f2') + def _on_change_f2(self): + self.f = self.env['test_testing_utilities.m2o'].search([ + ('name', 'ilike', self.f2), + ], limit=1) + +class M2MChange(models.Model): + _name = 'test_testing_utilities.e' + + m2m = fields.Many2many('test_testing_utilities.sub2') + count = fields.Integer(compute='_m2m_count') + + @api.depends('m2m') + def _m2m_count(self): + for r in self: + r.count = len(r.m2m) + +class M2MSub(models.Model): + _name = 'test_testing_utilities.sub2' + + name = fields.Char() + +class M2MChange2(models.Model): + _name = 'test_testing_utilities.f' + + def _get_some(self): + r = self.env['test_testing_utilities.sub2'].search([], limit=2) + return r + + m2m = fields.Many2many( + 'test_testing_utilities.sub2', + default=_get_some, + ) + m2o = fields.Many2one('test_testing_utilities.sub2') + + @api.onchange('m2o') + def _on_change_m2o(self): + self.m2m = self.m2m | self.m2o + +class M2MReadonly(models.Model): + _name = 'test_testing_utilities.g' + + m2m = fields.Many2many('test_testing_utilities.sub3', readonly=True) + +class M2MSub3(models.Model): + _name = 'test_testing_utilities.sub3' + + name = fields.Char() + +class O2MChange(models.Model): + _name = 'test_testing_utilities.parent' + + value = fields.Integer(default=1) + v = fields.Integer() + subs = fields.One2many('test_testing_utilities.sub', 'parent_id') + + @api.onchange('value', 'subs') + def _onchange_values(self): + self.v = self.value + sum(s.value for s in self.subs) + +class O2MSub(models.Model): + _name = 'test_testing_utilities.sub' + + name = fields.Char(compute='_compute_name') + value = fields.Integer(default=2) + v = fields.Integer() + parent_id = fields.Many2one('test_testing_utilities.parent') + has_parent = fields.Boolean() + + @api.onchange('value') + def _onchange_value(self): + self.v = self.value + + @api.depends('v') + def _compute_name(self): + for r in self: + r.name = str(r.v) + + @api.onchange('has_parent') + def _onchange_has_parent(self): + self.has_parent = bool(self.parent_id) + if self.has_parent: + self.value = self.parent_id.value + +class O2MDefault(models.Model): + _name = 'test_testing_utilities.default' + + def _default_subs(self): + return [ + (0, 0, {'v': 5}) + ] + value = fields.Integer(default=1) + v = fields.Integer() + subs = fields.One2many('test_testing_utilities.sub3', 'parent_id', default=_default_subs) + +class O2MSub3(models.Model): + _name = 'test_testing_utilities.sub3' + + name = fields.Char(compute='_compute_name') + value = fields.Integer(default=2) + v = fields.Integer(default=6) + parent_id = fields.Many2one('test_testing_utilities.default') + + @api.onchange('value') + def _onchange_value(self): + self.v = self.value + + @api.depends('v') + def _compute_name(self): + for r in self: + r.name = str(r.v) diff --git a/odoo/addons/test_testing_utilities/tests/__init__.py b/odoo/addons/test_testing_utilities/tests/__init__.py new file mode 100644 index 00000000000..0c0095dfd37 --- /dev/null +++ b/odoo/addons/test_testing_utilities/tests/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import test_form_impl diff --git a/odoo/addons/test_testing_utilities/tests/test_form_impl.py b/odoo/addons/test_testing_utilities/tests/test_form_impl.py new file mode 100644 index 00000000000..19d192365a5 --- /dev/null +++ b/odoo/addons/test_testing_utilities/tests/test_form_impl.py @@ -0,0 +1,461 @@ +# -*- coding: utf-8 -*- +""" +Test for the pseudo-form implementation (odoo.tests.common.Form), which should +basically be a server-side implementation of form views (though probably not +complete) intended for properly validating business "view" flows (onchanges, +readonly, required, ...) and make it easier to generate sensible & coherent +business objects. +""" +from operator import itemgetter + +from odoo.tests.common import TransactionCase, Form + + +class TestBasic(TransactionCase): + def test_defaults(self): + """ + Checks that we can load a default form view and perform trivial + default_get & onchanges & computations + """ + f = Form(self.env['test_testing_utilities.a']) + self.assertEqual(f.id, False, "check that our record is not in db (yet)") + + self.assertEqual(f.f2, 42) + self.assertEqual(f.f3, 21) + self.assertEqual(f.f4, 42) + + f.f1 = 4 + self.assertEqual(f.f2, 42) + self.assertEqual(f.f3, 21) + self.assertEqual(f.f4, 10) + + f.f2 = 8 + self.assertEqual(f.f3, 4) + self.assertEqual(f.f4, 2) + + r = f.save() + self.assertEqual( + (r.f1, r.f2, r.f3, r.f4), + (4, 8, 4, 2), + ) + + def test_required(self): + f = Form(self.env['test_testing_utilities.a']) + # f1 no default & no value => should fail + with self.assertRaisesRegexp(AssertionError, 'f1 is a required field'): + f.save() + # set f1 and unset f2 => should work + f.f1 = 1 + f.f2 = False + r = f.save() + self.assertEqual( + (r.f1, r.f2, r.f3, r.f4), + (1, 0, 0, 0) + ) + + def test_readonly(self): + """ + Checks that fields with readonly modifiers (marked as readonly or + computed w/o set) raise an error when set. + """ + f = Form(self.env['test_testing_utilities.readonly']) + + with self.assertRaises(AssertionError): + f.f1 = 5 + with self.assertRaises(AssertionError): + f.f2 = 42 + +class TestM2O(TransactionCase): + def test_default_and_onchange(self): + """ Checks defaults & onchanges impacting m2o fields + """ + Sub = self.env['test_testing_utilities.m2o'] + a = Sub.create({'name': "A"}) + b = Sub.create({'name': "B"}) + + f = Form(self.env['test_testing_utilities.d']) + + self.assertEqual( + f.f, a, + "The default value for the m2o should be the first Sub record" + ) + f.f2 = "B" + self.assertEqual( + f.f, b, + "The new m2o value should match the second field by name" + ) + + f.save() + + def test_set(self): + """ + Checks that we get/set recordsets for m2o & that set correctly + triggers onchange + """ + r1 = self.env['test_testing_utilities.m2o'].create({'name': "A"}) + r2 = self.env['test_testing_utilities.m2o'].create({'name': "B"}) + + f = Form(self.env['test_testing_utilities.c']) + + # check that basic manipulations work + f.f2 = r1 + self.assertEqual(f.f2, r1) + self.assertEqual(f.name, 'A') + f.f2 = r2 + self.assertEqual(f.name, 'B') + + # can't set an int to an m2o field + with self.assertRaises(AssertionError): + f.f2 = r1.id + self.assertEqual(f.f2, r2) + self.assertEqual(f.name, 'B') + + # can't set a record of the wrong model + temp = self.env['test_testing_utilities.readonly'].create({}) + with self.assertRaises(AssertionError): + f.f2 = temp + self.assertEqual(f.f2, r2) + self.assertEqual(f.name, 'B') + + r = f.save() + self.assertEqual(r.f2, r2) + +class TestM2M(TransactionCase): + def test_add(self): + Sub = self.env['test_testing_utilities.sub2'] + f = Form(self.env['test_testing_utilities.e']) + + r1 = Sub.create({'name': "Item"}) + r2 = Sub.create({'name': "Item2"}) + + f.m2m.add(r1) + f.m2m.add(r2) + + r = f.save() + + self.assertEqual( + r.m2m, + r1 | r2 + ) + + def test_remove_by_index(self): + Sub = self.env['test_testing_utilities.sub2'] + f = Form(self.env['test_testing_utilities.e']) + + r1 = Sub.create({'name': "Item"}) + r2 = Sub.create({'name': "Item2"}) + + f.m2m.add(r1) + f.m2m.add(r2) + f.m2m.remove(index=0) + + r = f.save() + + self.assertEqual( + r.m2m, + r2 + ) + + def test_remove_by_id(self): + Sub = self.env['test_testing_utilities.sub2'] + f = Form(self.env['test_testing_utilities.e']) + + r1 = Sub.create({'name': "Item"}) + r2 = Sub.create({'name': "Item2"}) + + f.m2m.add(r1) + f.m2m.add(r2) + f.m2m.remove(id=r1.id) + + r = f.save() + + self.assertEqual( + r.m2m, + r2 + ) + + def test_on_m2m_change(self): + Sub = self.env['test_testing_utilities.sub2'] + f = Form(self.env['test_testing_utilities.e']) + + self.assertEqual(f.count, 0) + f.m2m.add(Sub.create({'name': 'a'})) + self.assertEqual(f.count, 1) + f.m2m.add(Sub.create({'name': 'a'})) + f.m2m.add(Sub.create({'name': 'a'})) + f.m2m.add(Sub.create({'name': 'a'})) + self.assertEqual(f.count, 4) + f.m2m.remove(index=0) + f.m2m.remove(index=0) + f.m2m.remove(index=0) + self.assertEqual(f.count, 1) + + def test_m2m_changed(self): + Sub = self.env['test_testing_utilities.sub2'] + a = Sub.create({'name': 'a'}) + b = Sub.create({'name': 'b'}) + c = Sub.create({'name': 'c'}) + d = Sub.create({'name': 'd'}) + + f = Form(self.env['test_testing_utilities.f']) + # check default_get + self.assertEqual(f.m2m[:], a | b) + + f.m2o = c + self.assertEqual(f.m2m[:], a | b | c) + + f.m2o = d + self.assertEqual(f.m2m[:], a | b | c | d) + + def test_m2m_readonly(self): + Sub = self.env['test_testing_utilities.sub3'] + a = Sub.create({'name': 'a'}) + b = Sub.create({'name': 'b'}) + r = self.env['test_testing_utilities.g'].create({ + 'm2m': [(6, 0, a.ids)] + }) + + f = Form(r) + + with self.assertRaises(AssertionError): + f.m2m.add(b) + with self.assertRaises(AssertionError): + f.m2m.remove(id=a.id) + + f.save() + self.assertEqual(r.m2m, a) + + +get = itemgetter('name', 'value', 'v') +class TestO2M(TransactionCase): + def test_basic_alterations(self): + """ Tests that the o2m proxy allows adding, removing and editing o2m + records + """ + f = Form(self.env['test_testing_utilities.parent'], view='test_testing_utilities.o2m_parent') + + f.subs.new().save() + f.subs.new().save() + f.subs.new().save() + f.subs.remove(index=0) + + r = f.save() + + self.assertEqual( + [get(s) for s in r.subs], + [("2", 2, 2), ("2", 2, 2)] + ) + + with Form(r, view='test_testing_utilities.o2m_parent') as f: + with f.subs.new() as sub: + sub.value = 5 + f.subs.new().save() + + with f.subs.edit(index=2) as sub: + self.assertEqual(sub.v, 5) + + f.subs.remove(index=0) + + self.assertEqual( + [get(s) for s in r.subs], + [("2", 2, 2), ("5", 5, 5), ("2", 2, 2)] + ) + + with Form(r, view='test_testing_utilities.o2m_parent') as f, \ + f.subs.edit(index=0) as sub,\ + self.assertRaises(AssertionError): + sub.name = "whop whop" + + def test_o2m_editable_list(self): + """ Tests the o2m proxy when the list view is editable rather than + delegating to a separate form view + """ + f = Form(self.env['test_testing_utilities.parent'], view='test_testing_utilities.o2m_parent_ed') + + with f.subs.new() as s: + s.value = 1 + with f.subs.new() as s: + s.value = 3 + with f.subs.new() as s: + s.value = 7 + + r = f.save() + + self.assertEqual(r.v, 12) + self.assertEqual( + [get(s) for s in r.subs], + [('1', 1, 1), ('3', 3, 3), ('7', 7, 7)] + ) + + def test_o2m_inline(self): + """ Tests the o2m proxy when the list and form views are provided + inline rather than fetched separately + """ + f = Form(self.env['test_testing_utilities.parent'], view='test_testing_utilities.o2m_parent_inline') + + with f.subs.new() as s: + s.value = 42 + + r = f.save() + + self.assertEqual( + [get(s) for s in r.subs], + [("0", 42, 0)], + "should not have set v (and thus not name)" + ) + + def test_o2m_default(self): + """ Tests that default_get can return defaults for the o2m + """ + f = Form(self.env['test_testing_utilities.default']) + + with f.subs.edit(index=0) as s: + self.assertEqual(s.v, 5) + self.assertEqual(s.value, False) + + r = f.save() + + self.assertEqual( + [get(s) for s in r.subs], + [("5", 2, 5)] + ) + + def test_o2m_inner_default(self): + """ Tests that creating an o2m record will get defaults for it + """ + f = Form(self.env['test_testing_utilities.default']) + + with f.subs.new() as s: + self.assertEqual(s.value, 2) + self.assertEqual(s.v, 2, "should have onchanged value to v") + + def test_o2m_onchange_parent(self): + """ Tests that changing o2m content triggers onchange in the parent + """ + f = Form(self.env['test_testing_utilities.parent']) + + self.assertEqual(f.value, 1, "value should have its default") + self.assertEqual(f.v, 1, "v should be equal to value") + + f.subs.new().save() + + self.assertEqual(f.v, 3, "should be sum of value & children v") + + def test_o2m_onchange_inner(self): + """ Tests that editing a field of an o2m record triggers onchange + in the o2m record and its parent + """ + f = Form(self.env['test_testing_utilities.parent']) + + # only apply the onchange on edition end (?) + with f.subs.new() as sub: + sub.value = 6 + self.assertEqual(sub.v, 6) + self.assertEqual(f.v, 1) + self.assertEqual(f.v, 7) + + def test_o2m_parent_content(self): + """ Tests that when editing a field of an o2m the data sent contains + the parent data + """ + f = Form(self.env['test_testing_utilities.parent']) + + # only apply the onchange on edition end (?) + with f.subs.new() as sub: + sub.has_parent = True + self.assertEqual(sub.has_parent, True) + self.assertEqual(sub.value, 1) + self.assertEqual(sub.v, 1) + + def test_m2o_readonly(self): + r = self.env['test_testing_utilities.parent'].create({ + 'subs': [(0, 0, {})] + }) + f = Form(r, view='test_testing_utilities.o2m_parent_readonly') + + with self.assertRaises(AssertionError): + f.subs.new() + with self.assertRaises(AssertionError): + f.subs.edit(index=0) + with self.assertRaises(AssertionError): + f.subs.remove(index=0) + +class TestEdition(TransactionCase): + """ These use the context manager form as we don't need the record + post-save (we already have it) and it's easier to see what bits act on + the form (inside `with`) versus outside. That let me catch a few + mistakes. + """ + def test_trivial(self): + r = self.env['test_testing_utilities.a'].create({ + 'f1': 5, + }) + + with Form(r) as f: + self.assertEqual(f.id, r.id) + self.assertEqual(f.f1, 5) + self.assertEqual(f.f4, 8) + + f.f2 = 5 + self.assertEqual(f.f3, 2) + self.assertEqual(f.f4, 1) + + self.assertEqual(r.f2, 5) + self.assertEqual(r.f3, 2) + + def test_m2o(self): + Sub = self.env['test_testing_utilities.m2o'] + a = Sub.create({'name': 'a'}) + b = Sub.create({'name': 'b'}) + c = Sub.create({'name': 'c'}) + + r = self.env['test_testing_utilities.d'].create({ + 'f': c.id, + 'f2': "OK", + }) + + with Form(r) as f: + # no default/onchange should have run so loading an incoherent + # record should still be incoherent + self.assertEqual(f.f, c) + self.assertEqual(f.f2, 'OK') + + f.f2 = "b" + self.assertEqual(f.f, b) + f.f2 = "Whoops" + self.assertEqual(f.f, Sub) + f.f2 = "a" + self.assertEqual(f.f, a) + + self.assertEqual(r.f2, "a") + self.assertEqual(r.f, a) + + def test_m2m_empty(self): + sub = self.env['test_testing_utilities.sub2'].create({'name': 'a'}) + + r = self.env['test_testing_utilities.f'].create({ + 'm2m': [] + }) + + with Form(r) as f: + f.m2o = sub + + self.assertEqual(r.m2o, sub) + self.assertEqual(r.m2m, sub) + + def test_m2m_nonempty(self): + Sub = self.env['test_testing_utilities.sub2'] + a = Sub.create({'name': 'a'}) + b = Sub.create({'name': 'b'}) + c = Sub.create({'name': 'c'}) + + r = self.env['test_testing_utilities.f'].create({ + 'm2m': [(6, 0, (a | b | c).ids)] + }) + + with Form(r) as f: + self.assertEqual(f.m2m[:], (a | b | c)) + f.m2m.remove(index=0) + self.assertEqual(f.m2m[:], (b | c)) + + self.assertEqual(r.m2m, (b | c)) |
