[MERGE] fields: relational fields automatically behave as mapped

In other words, `recs.foo_id == recs.mapped('foo_id')`.

closes odoo/odoo#32897

Signed-off-by: Raphael Collet (rco) <rco@openerp.com>
This commit is contained in:
Robot Odoo
2019-04-30 17:25:56 +02:00
committed by GitHub
10 changed files with 36 additions and 21 deletions
+1 -1
View File
@@ -409,7 +409,7 @@ class Module(models.Model):
todo = result = module
while todo:
result |= todo
todo = todo.mapped('dependencies_id.depend_id')
todo = todo.dependencies_id.depend_id
return result
exclusives = self.env['ir.module.category'].search([('exclusive', '=', True)])
+2 -2
View File
@@ -473,8 +473,8 @@ actual arch.
def unlink(self):
# if in uninstall mode and has children views, emulate an ondelete cascade
if self.env.context.get('_force_unlink', False) and self.mapped('inherit_children_ids'):
self.mapped('inherit_children_ids').unlink()
if self.env.context.get('_force_unlink', False) and self.inherit_children_ids:
self.inherit_children_ids.unlink()
super(View, self).unlink()
@api.multi
+1 -1
View File
@@ -584,7 +584,7 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin)
groups.write({'implied_ids': [(4, implied_group.id)]})
else:
groups.write({'implied_ids': [(3, implied_group.id)]})
implied_group.write({'users': [(3, user.id) for user in groups.mapped('users')]})
implied_group.write({'users': [(3, user.id) for user in groups.users]})
self.recompute()
# config fields: store ir.config_parameters
+9 -9
View File
@@ -110,7 +110,7 @@ class Groups(models.Model):
@api.multi
@api.constrains('users')
def _check_one_user_type(self):
self.mapped('users')._check_one_user_type()
self.users._check_one_user_type()
@api.depends('category_id.name', 'name')
def _compute_full_name(self):
@@ -367,8 +367,8 @@ class Users(models.Model):
def _compute_accesses_count(self):
for user in self:
groups = user.groups_id
user.accesses_count = len(groups.mapped('model_access'))
user.rules_count = len(groups.mapped('rule_groups'))
user.accesses_count = len(groups.model_access)
user.rules_count = len(groups.rule_groups)
user.groups_count = len(groups)
@api.onchange('login')
@@ -378,7 +378,7 @@ class Users(models.Model):
@api.onchange('parent_id')
def onchange_parent_id(self):
return self.mapped('partner_id').onchange_parent_id()
return self.partner_id.onchange_parent_id()
def _read_from_database(self, field_names, inherited_field_names=[]):
super(Users, self)._read_from_database(field_names, inherited_field_names)
@@ -748,7 +748,7 @@ class Users(models.Model):
'res_model': 'ir.model.access',
'type': 'ir.actions.act_window',
'context': {'create': False, 'delete': False},
'domain': [('id', 'in', self.mapped('groups_id.model_access').ids)],
'domain': [('id', 'in', self.groups_id.model_access.ids)],
'target': 'current',
}
@@ -761,7 +761,7 @@ class Users(models.Model):
'res_model': 'ir.rule',
'type': 'ir.actions.act_window',
'context': {'create': False, 'delete': False},
'domain': [('id', 'in', self.mapped('groups_id.rule_groups').ids)],
'domain': [('id', 'in', self.groups_id.rule_groups.ids)],
'target': 'current',
}
@@ -917,7 +917,7 @@ class GroupsImplied(models.Model):
# is good, because the record cache behaves as a memo (the field is
# never computed twice on a given group.)
for g in self:
g.trans_implied_ids = g.implied_ids | g.mapped('implied_ids.trans_implied_ids')
g.trans_implied_ids = g.implied_ids | g.implied_ids.trans_implied_ids
@api.model_create_multi
def create(self, vals_list):
@@ -973,7 +973,7 @@ class UsersImplied(models.Model):
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.mapped('trans_implied_ids')
gs = user.groups_id | user.groups_id.trans_implied_ids
values['groups_id'] = type(self).groups_id.convert_to_write(gs, user.groups_id)
return super(UsersImplied, self).create(vals_list)
@@ -1329,7 +1329,7 @@ class ChangePasswordWizard(models.TransientModel):
def change_password_button(self):
self.ensure_one()
self.user_ids.change_password_button()
if self.env.user in self.mapped('user_ids.user_id'):
if self.env.user in self.user_ids.user_id:
return {'type': 'ir.actions.client', 'tag': 'reload'}
return {'type': 'ir.actions.act_window_close'}
+8 -2
View File
@@ -311,7 +311,7 @@ class TestAPI(common.TransactionCase):
partner.state_id.name
break
state_ids_with_field = [state.id
for state in partners.mapped('state_id')
for state in partners.state_id
if 'name' in state._cache]
self.assertItemsEqual(state_ids_with_field, state_ids)
@@ -491,11 +491,13 @@ class TestAPI(common.TransactionCase):
""" Check map on recordsets. """
ps = self.env['res.partner'].search([])
parents = ps.browse()
for p in ps: parents |= p.parent_id
for p in ps:
parents |= p.parent_id
# map a single field
self.assertEqual(ps.mapped(lambda p: p.parent_id), parents)
self.assertEqual(ps.mapped('parent_id'), parents)
self.assertEqual(ps.parent_id, parents)
# map a sequence of fields
self.assertEqual(
@@ -506,6 +508,10 @@ class TestAPI(common.TransactionCase):
ps.mapped('parent_id.name'),
[p.name for p in parents]
)
self.assertEqual(
ps.parent_id.mapped('name'),
[p.name for p in parents]
)
# map an empty sequence of fields
self.assertEqual(ps.mapped(''), ps)
@@ -56,5 +56,5 @@ class BaseModuleUninstall(models.TransientModel):
@api.multi
def action_uninstall(self):
modules = self.mapped('module_id')
modules = self.module_id
return modules.button_immediate_uninstall()
@@ -302,7 +302,7 @@ class MergePartnerAutomatic(models.TransientModel):
raise UserError(_("Only the destination contact may be linked to existing Journal Items. Please ask the Administrator if you need to merge several contacts linked to existing Journal Items."))
# Make the company of all related users consistent
for user in partner_ids.mapped('user_ids'):
for user in partner_ids.user_ids:
user.sudo().write({'company_ids': [(6, 0, [dst_partner.company_id.id])],
'company_id': dst_partner.company_id.id})
@@ -1229,7 +1229,7 @@ class TestX2many(common.TransactionCase):
recY = recs.create({'lines': [(0, 0, {})]})
recZ = recs.create({})
recs = recX + recY + recZ
line1, line2, line3 = recs.mapped('lines')
line1, line2, line3 = recs.lines
line4 = recs.create({'lines': [(0, 0, {})]}).lines
line0 = line4.create({})
@@ -104,7 +104,7 @@ class One2manyCase(TransactionCase):
movies = self.Movie.search([])
movies_without_edition = movies.filtered(lambda r: not r.editions)
movies_with_edition = movies.filtered(lambda r: r.editions)
movie_editions = movies_with_edition.mapped('editions')
movie_editions = movies_with_edition.editions
one_movie_edition = movie_editions[0]
res_movies_without_edition = self.Movie.search([('editions', '=', False)])
+11 -2
View File
@@ -989,7 +989,7 @@ class Field(MetaField('DummyField', (object,), {})):
if record is None:
return self # the field is accessed through the owner class
if record:
if record._ids:
# only a single record may be accessed
record.ensure_one()
try:
@@ -2033,6 +2033,15 @@ class _Relational(Field):
'context': {}, # context for searching values
}
def __get__(self, records, owner):
# base case: do the regular access
if records is None or len(records._ids) <= 1:
return super().__get__(records, owner)
# multirecord case: return the union of the values of 'self' on records
get = super().__get__
comodel = records.env[self.comodel_name]
return comodel.union(*[get(record, owner) for record in records])
def _setup_regular_base(self, model):
super(_Relational, self)._setup_regular_base(model)
if self.comodel_name not in model.pool:
@@ -2356,7 +2365,7 @@ class _RelationalMulti(_Relational):
super(_RelationalMulti, self)._compute_related(records)
if self.related_sudo:
# determine which records in the relation are actually accessible
target = records.mapped(self.name)
target = records[self.name]
target_ids = set(target.search([('id', 'in', target.ids)]).ids)
accessible = lambda target: target.id in target_ids
# filter values to keep the accessible records only