Skip to content

Commit

Permalink
[IMP] fields: x2many fields of new records are new records
Browse files Browse the repository at this point in the history
The value of an x2many field on a new record are new records, possibly
related to a real record, called its "origin".
  • Loading branch information
rco-odoo committed May 23, 2019
1 parent 60be9f2 commit d0bb596
Show file tree
Hide file tree
Showing 9 changed files with 378 additions and 124 deletions.
2 changes: 1 addition & 1 deletion odoo/addons/base/models/ir_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ def _onchange_relation_table(self):
# check whether other fields use the same table
others = self.search([('ttype', '=', 'many2many'),
('relation_table', '=', self.relation_table),
('id', 'not in', self._origin.ids)])
('id', 'not in', self.ids)])
if others:
for other in others:
if (other.model, other.relation) == (self.relation, self.model):
Expand Down
8 changes: 3 additions & 5 deletions odoo/addons/base/models/res_partner.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,8 @@ def _compute_partner_share(self):
@api.depends('vat')
def _compute_same_vat_partner_id(self):
for partner in self:
partner_id = partner.id
if isinstance(partner_id, models.NewId):
# deal with onchange(), which is always called on a single record
partner_id = self._origin.id
# use _origin to deal with onchange()
partner_id = partner._origin.id
domain = [('vat', '=', partner.vat)]
if partner_id:
domain += [('id', '!=', partner_id), '!', ('id', 'child_of', partner_id)]
Expand Down Expand Up @@ -379,7 +377,7 @@ def onchange_parent_id(self):
if not self.parent_id:
return
result = {}
partner = getattr(self, '_origin', self)
partner = self._origin
if partner.parent_id and partner.parent_id != self.parent_id:
result['warning'] = {
'title': _('Warning'),
Expand Down
14 changes: 7 additions & 7 deletions odoo/addons/base/models/res_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -962,15 +962,15 @@ def create(self, vals_list):
if 'groups_id' in values:
# complete 'groups_id' with implied groups
user = self.new(values)
gs = user.groups_id._origin
group_public = self.env.ref('base.group_public', raise_if_not_found=False)
group_portal = self.env.ref('base.group_portal', raise_if_not_found=False)
if group_public and group_public in user.groups_id:
gs = self.env.ref('base.group_public') | self.env.ref('base.group_public').trans_implied_ids
elif group_portal and group_portal in user.groups_id:
gs = self.env.ref('base.group_portal') | self.env.ref('base.group_portal').trans_implied_ids
else:
gs = user.groups_id | user.groups_id.trans_implied_ids
values['groups_id'] = type(self).groups_id.convert_to_write(gs, user.groups_id)
if group_public and group_public in gs:
gs = group_public
elif group_portal and group_portal in gs:
gs = group_portal
gs = gs | gs.trans_implied_ids
values['groups_id'] = type(self).groups_id.convert_to_write(gs, user)
return super(UsersImplied, self).create(vals_list)

@api.multi
Expand Down
1 change: 1 addition & 0 deletions odoo/addons/test_new_api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ class Multi(models.Model):
name = fields.Char(related='partner.name', readonly=True)
partner = fields.Many2one('res.partner')
lines = fields.One2many('test_new_api.multi.line', 'multi')
partners = fields.One2many(related='partner.child_ids')

@api.onchange('name')
def _onchange_name(self):
Expand Down
158 changes: 156 additions & 2 deletions odoo/addons/test_new_api/tests/test_new_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -956,10 +956,154 @@ def test_40_new_defaults(self):
discussion = discussion.with_context(default_categories=[(4, cat1.id)])
# no value gives the default value
new_disc = discussion.new({'name': "Foo"})
self.assertEqual(new_disc.categories, cat1)
self.assertEqual(new_disc.categories._origin, cat1)
# value is combined with default value
new_disc = discussion.new({'name': "Foo", 'categories': [(4, cat2.id)]})
self.assertEqual(new_disc.categories, cat1 + cat2)
self.assertEqual(new_disc.categories._origin, cat1 + cat2)

def test_40_new_fields(self):
""" Test new records with relational fields. """
# create a new discussion with all kinds of relational fields
msg0 = self.env['test_new_api.message'].create({'body': "XXX"})
msg1 = self.env['test_new_api.message'].create({'body': "WWW"})
cat0 = self.env['test_new_api.category'].create({'name': 'AAA'})
cat1 = self.env['test_new_api.category'].create({'name': 'DDD'})
new_disc = self.env['test_new_api.discussion'].new({
'name': "Stuff",
'moderator': self.env.uid,
'messages': [
(4, msg0.id),
(4, msg1.id), (1, msg1.id, {'body': "YYY"}),
(0, 0, {'body': "ZZZ"})
],
'categories': [
(4, cat0.id),
(4, cat1.id), (1, cat1.id, {'name': "BBB"}),
(0, 0, {'name': "CCC"})
],
})
self.assertFalse(new_disc.id)

# many2one field values are actual records
self.assertEqual(new_disc.moderator.id, self.env.uid)

# x2many fields values are new records
new_msg0, new_msg1, new_msg2 = new_disc.messages
self.assertFalse(new_msg0.id)
self.assertFalse(new_msg1.id)
self.assertFalse(new_msg2.id)

new_cat0, new_cat1, new_cat2 = new_disc.categories
self.assertFalse(new_cat0.id)
self.assertFalse(new_cat1.id)
self.assertFalse(new_cat2.id)

# the x2many has its inverse field set
self.assertEqual(new_msg0.discussion, new_disc)
self.assertEqual(new_msg1.discussion, new_disc)
self.assertEqual(new_msg2.discussion, new_disc)

self.assertFalse(msg0.discussion)
self.assertFalse(msg1.discussion)

self.assertEqual(new_cat0.discussions, new_disc) # add other discussions
self.assertEqual(new_cat1.discussions, new_disc)
self.assertEqual(new_cat2.discussions, new_disc)

self.assertNotIn(new_disc, cat0.discussions)
self.assertNotIn(new_disc, cat1.discussions)

# new lines are connected to their origin
self.assertEqual(new_msg0._origin, msg0)
self.assertEqual(new_msg1._origin, msg1)
self.assertFalse(new_msg2._origin)

self.assertEqual(new_cat0._origin, cat0)
self.assertEqual(new_cat1._origin, cat1)
self.assertFalse(new_cat2._origin)

# the field values are either specific, or the same as the origin
self.assertEqual(new_msg0.body, "XXX")
self.assertEqual(new_msg1.body, "YYY")
self.assertEqual(new_msg2.body, "ZZZ")

self.assertEqual(msg0.body, "XXX")
self.assertEqual(msg1.body, "WWW")

self.assertEqual(new_cat0.name, "AAA")
self.assertEqual(new_cat1.name, "BBB")
self.assertEqual(new_cat2.name, "CCC")

self.assertEqual(cat0.name, "AAA")
self.assertEqual(cat1.name, "DDD")

# special case for many2one fields that define _inherits
new_email = self.env['test_new_api.emailmessage'].new({'body': "XXX"})
self.assertFalse(new_email.id)
self.assertTrue(new_email.message)
self.assertFalse(new_email.message.id)
self.assertEqual(new_email.body, "XXX")

new_email = self.env['test_new_api.emailmessage'].new({'message': msg0.id})
self.assertFalse(new_email.id)
self.assertFalse(new_email._origin)
self.assertFalse(new_email.message.id)
self.assertEqual(new_email.message._origin, msg0)
self.assertEqual(new_email.body, "XXX")

def test_40_new_ref_origin(self):
""" Test the behavior of new records with ref/origin. """
Discussion = self.env['test_new_api.discussion']
new = Discussion.new

# new records with identical/different refs
xs = new() + new(ref='a') + new(ref='b') + new(ref='b')
self.assertEqual([x == y for x in xs for y in xs], [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 1,
0, 0, 1, 1,
])
for x in xs:
self.assertFalse(x._origin)

# new records with identical/different origins
a, b = Discussion.create([{'name': "A"}, {'name': "B"}])
xs = new() + new(origin=a) + new(origin=b) + new(origin=b)
self.assertEqual([x == y for x in xs for y in xs], [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 1,
0, 0, 1, 1,
])
self.assertFalse(xs[0]._origin)
self.assertEqual(xs[1]._origin, a)
self.assertEqual(xs[2]._origin, b)
self.assertEqual(xs[3]._origin, b)
self.assertEqual(xs._origin, a + b + b)
self.assertEqual(xs._origin._origin, a + b + b)

# new records with refs and origins
x1 = new(ref='a')
x2 = new(origin=b)
self.assertNotEqual(x1, x2)

# new discussion based on existing discussion
disc = self.env.ref('test_new_api.discussion_0')
new_disc = disc.new(origin=disc)
self.assertFalse(new_disc.id)
self.assertEqual(new_disc._origin, disc)
self.assertEqual(new_disc.name, disc.name)
# many2one field
self.assertEqual(new_disc.moderator, disc.moderator)
# one2many field
self.assertTrue(new_disc.messages)
self.assertNotEqual(new_disc.messages, disc.messages)
self.assertEqual(new_disc.messages._origin, disc.messages)
# many2many field
self.assertTrue(new_disc.participants)
self.assertNotEqual(new_disc.participants, disc.participants)
self.assertEqual(new_disc.participants._origin, disc.participants)

@mute_logger('odoo.addons.base.models.ir_model')
def test_41_new_related(self):
Expand Down Expand Up @@ -1005,6 +1149,16 @@ def test_42_new_related(self):
self.assertNotEqual(message.sudo().env, message.env)
self.assertEqual(message.discussion_name, discussion.name)

def test_43_new_related(self):
""" test the behavior of one2many related fields """
partner = self.env['res.partner'].create({
'name': 'Foo',
'child_ids': [(0, 0, {'name': 'Bar'})],
})
multi = self.env['test_new_api.multi'].new()
multi.partner = partner
self.assertEqual(multi.partners.mapped('name'), ['Bar'])

def test_50_defaults(self):
""" test default values. """
fields = ['discussion', 'body', 'author', 'size']
Expand Down
12 changes: 12 additions & 0 deletions odoo/addons/test_performance/tests/test_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,14 +355,26 @@ def test_several_prefetch(self):
)
records = self.env['test_performance.base'].search([])
self.assertEqual(len(records), 1280)

# should only cause 2 queries thanks to prefetching
with self.assertQueryCount(__system__=2, demo=2):
records.mapped('value')

records.invalidate_cache(['value'])
with self.assertQueryCount(__system__=2, demo=2):
records.mapped('value')

records.invalidate_cache(['value'])
with self.assertQueryCount(__system__=2, demo=2):
new_recs = records.browse(records.new(origin=record).id for record in records)
new_recs.mapped('value')

records.invalidate_cache(['value'])
with self.assertQueryCount(__system__=2, demo=2):
with self.env.do_in_onchange():
records.mapped('value')

# clean up after each pass
self.env.cr.execute(
'delete from test_performance_base where id not in %s',
(tuple(initial_records.ids),)
Expand Down
Loading

0 comments on commit d0bb596

Please sign in to comment.