[IMP] base: support group_expand for custom fields
Allow a naive `group_expand` for manual fields where having the attribute set to True causes the ORM to include all records from the relation model of the m2o field in the read_group. This is particularly useful for custom m2o fields which represent stages - a grouped list view or a kanban view should include all possible stage, not only the currently used values. Co-Authored-By: Raphaël Collet <rco@odoo.com>
This commit is contained in:
co-authored by
Raphaël Collet
parent
4d2aa27157
commit
00205aae2a
@@ -397,6 +397,12 @@ class IrModelFields(models.Model):
|
||||
"specified as a Python expression defining a list of triplets. "
|
||||
"For example: [('color','=','red')]")
|
||||
groups = fields.Many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id') # CLEANME unimplemented field (empty table)
|
||||
group_expand = fields.Boolean(string="Expand Groups",
|
||||
help="If checked, all the records of the target model will be included\n"
|
||||
"in a grouped result (e.g. 'Group By' filters, Kanban columns, etc.).\n"
|
||||
"Note that it can significantly reduce performance if the target model\n"
|
||||
"of the field contains a lot of records; usually used on models with\n"
|
||||
"few records (e.g. Stages, Job Positions, Event Types, etc.).")
|
||||
selectable = fields.Boolean(default=True)
|
||||
modules = fields.Char(compute='_in_modules', string='In Apps', help='List of modules in which the field is defined')
|
||||
relation_table = fields.Char(help="Used for custom many2many fields to define a custom relation table name")
|
||||
@@ -994,6 +1000,7 @@ class IrModelFields(models.Model):
|
||||
attrs['comodel_name'] = field_data['relation']
|
||||
attrs['ondelete'] = field_data['on_delete']
|
||||
attrs['domain'] = safe_eval(field_data['domain'] or '[]')
|
||||
attrs['group_expand'] = '_read_group_expand_full' if field_data['group_expand'] else None
|
||||
elif field_data['ttype'] == 'one2many':
|
||||
if not self.pool.loaded and not (
|
||||
field_data['relation'] in self.env and (
|
||||
|
||||
@@ -186,6 +186,19 @@ class TestIrModel(SavepointCase):
|
||||
cls.registry.enter_test_mode(cls.cr)
|
||||
cls.addClassCleanup(cls.registry.leave_test_mode)
|
||||
|
||||
# model and records for banana stages
|
||||
cls.env['ir.model'].create({
|
||||
'name': 'Banana Ripeness',
|
||||
'model': 'x_banana_ripeness',
|
||||
'field_id': [
|
||||
(0, 0, {'name': 'x_name', 'ttype': 'char', 'field_description': 'Name'}),
|
||||
]
|
||||
})
|
||||
# stage values are pairs (id, display_name)
|
||||
cls.ripeness_green = cls.env['x_banana_ripeness'].name_create('Green')
|
||||
cls.ripeness_okay = cls.env['x_banana_ripeness'].name_create('Okay, I guess?')
|
||||
cls.ripeness_gone = cls.env['x_banana_ripeness'].name_create('Walked away on its own')
|
||||
|
||||
# model and records for bananas
|
||||
cls.bananas_model = cls.env['ir.model'].create({
|
||||
'name': 'Bananas',
|
||||
@@ -194,6 +207,9 @@ class TestIrModel(SavepointCase):
|
||||
(0, 0, {'name': 'x_name', 'ttype': 'char', 'field_description': 'Name'}),
|
||||
(0, 0, {'name': 'x_length', 'ttype': 'float', 'field_description': 'Length'}),
|
||||
(0, 0, {'name': 'x_color', 'ttype': 'integer', 'field_description': 'Color'}),
|
||||
(0, 0, {'name': 'x_ripeness_id', 'ttype': 'many2one',
|
||||
'field_description': 'Ripeness','relation': 'x_banana_ripeness',
|
||||
'group_expand': True})
|
||||
]
|
||||
})
|
||||
# add non-stored field that is not valid in order
|
||||
@@ -206,6 +222,8 @@ class TestIrModel(SavepointCase):
|
||||
'depends': 'x_color',
|
||||
'compute': "for banana in self:\n banana['x_is_yellow'] = banana.x_color == 9"
|
||||
})
|
||||
# default stage is ripeness_green
|
||||
cls.env['ir.default'].set('x_bananas', 'x_ripeness_id', cls.ripeness_green[0])
|
||||
cls.env['x_bananas'].create([{
|
||||
'x_name': 'Banana #1',
|
||||
'x_length': 3.14159,
|
||||
@@ -271,3 +289,23 @@ class TestIrModel(SavepointCase):
|
||||
|
||||
bananas = self.env['x_bananas'].search([])
|
||||
self.assertEqual(bananas.mapped('x_name'), names, 'failed to order by %s' % order)
|
||||
|
||||
def test_group_expansion(self):
|
||||
"""Check that the basic custom group expansion works."""
|
||||
groups = self.env['x_bananas'].read_group(domain=[],
|
||||
fields=['x_ripeness_id'],
|
||||
groupby=['x_ripeness_id'])
|
||||
expected = [{
|
||||
'x_ripeness_id': self.ripeness_green,
|
||||
'x_ripeness_id_count': 3,
|
||||
'__domain': [('x_ripeness_id', '=', self.ripeness_green[0])],
|
||||
}, {
|
||||
'x_ripeness_id': self.ripeness_okay,
|
||||
'x_ripeness_id_count': 0,
|
||||
'__domain': [('x_ripeness_id', '=', self.ripeness_okay[0])],
|
||||
}, {
|
||||
'x_ripeness_id': self.ripeness_gone,
|
||||
'x_ripeness_id_count': 0,
|
||||
'__domain': [('x_ripeness_id', '=', self.ripeness_gone[0])],
|
||||
}]
|
||||
self.assertEqual(groups, expected, 'should include 2 empty ripeness stages')
|
||||
|
||||
@@ -274,6 +274,9 @@
|
||||
attrs="{'required': [('ttype','in',['many2one','one2many','many2many'])],
|
||||
'readonly': [('ttype','not in',['many2one','one2many','many2many'])],
|
||||
'invisible': [('ttype','not in',['many2one','one2many','many2many'])]}"/>
|
||||
<field name="group_expand" groups="base.group_no_one"
|
||||
attrs="{'readonly': [('ttype','!=','many2one')],
|
||||
'invisible': [('ttype','!=','many2one')]}"/>
|
||||
<field name="on_delete" groups="base.group_no_one"
|
||||
attrs="{'readonly': [('ttype','!=','many2one')],
|
||||
'invisible': [('ttype','!=','many2one')]}"/>
|
||||
|
||||
@@ -1754,6 +1754,11 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
|
||||
"""
|
||||
cls.pool._clear_cache()
|
||||
|
||||
@api.model
|
||||
def _read_group_expand_full(self, groups, domain, order):
|
||||
"""Extend the group to include all targer records by default."""
|
||||
return groups.search([], order=order)
|
||||
|
||||
@api.model
|
||||
def _read_group_fill_results(self, domain, groupby, remaining_groupbys,
|
||||
aggregated_fields, count_field,
|
||||
|
||||
Reference in New Issue
Block a user