diff --git a/CHANGELOG b/CHANGELOG index 9f03745c79..7b4522ff0c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Allow to use the dotted notation for order parameters * Use action_id to find report to use * Allow custom StateView without Model * Remove Pool.object_name_list diff --git a/doc/ref/models/models.rst b/doc/ref/models/models.rst index 798a511f1f..773463e9bc 100644 --- a/doc/ref/models/models.rst +++ b/doc/ref/models/models.rst @@ -367,6 +367,10 @@ Class attributes are: second is the sort ordering as `ASC` for ascending or `DESC` for descending. + In case the field used for the first element is a :class:`fields.Many2One`, + it is also possible to use the dotted notation to sort on a specific field + from the target record. + .. attribute:: ModelSQL._order_name The name of the field (or an SQL statement) on which the records must be diff --git a/trytond/model/fields/many2one.py b/trytond/model/fields/many2one.py index 4c0ea667b8..a7051a5d3e 100644 --- a/trytond/model/fields/many2one.py +++ b/trytond/model/fields/many2one.py @@ -180,25 +180,33 @@ class Many2One(Field): return column.in_(query) def convert_order(self, name, tables, Model): - if getattr(Model, 'order_%s' % name, None): - return super(Many2One, self).convert_order(name, tables, Model) - assert name == self.name + fname, _, oexpr = name.partition('.') + if not oexpr and getattr(Model, 'order_%s' % fname, None): + return super(Many2One, self).convert_order(fname, tables, Model) + assert fname == self.name Target = self.get_target() - oname = 'id' - if Target._rec_name in Target._fields: - oname = Target._rec_name - if Target._order_name in Target._fields: - oname = Target._order_name + if oexpr: + oname, _, _ = oexpr.partition('.') + else: + oname = 'id' + if Target._rec_name in Target._fields: + oname = Target._rec_name + if Target._order_name in Target._fields: + oname = Target._order_name + oexpr = oname + + table, _ = tables[None] + if oname == 'id': + return [self.sql_column(table)] ofield = Target._fields[oname] - table, _ = tables[None] - target_tables = tables.get(name) + target_tables = tables.get(fname) if target_tables is None: target = Target.__table__() target_tables = { None: (target, target.id == self.sql_column(table)), } - tables[name] = target_tables - return ofield.convert_order(oname, target_tables, Target) + tables[fname] = target_tables + return ofield.convert_order(oexpr, target_tables, Target) diff --git a/trytond/model/modelsql.py b/trytond/model/modelsql.py index 01c81178f6..956cd2af06 100644 --- a/trytond/model/modelsql.py +++ b/trytond/model/modelsql.py @@ -997,10 +997,11 @@ class ModelSQL(ModelStorage): } if order is None or order is False: order = cls._order - for fname, otype in order: + for oexpr, otype in order: + fname, _, extra_expr = oexpr.partition('.') field = cls._fields[fname] Order = order_types[otype.upper()] - forder = field.convert_order(fname, tables, cls) + forder = field.convert_order(oexpr, tables, cls) order_by.extend((Order(o) for o in forder)) main_table, _ = tables[None] diff --git a/trytond/tests/__init__.py b/trytond/tests/__init__.py index dac5134679..9eb4c5bcac 100644 --- a/trytond/tests/__init__.py +++ b/trytond/tests/__init__.py @@ -154,6 +154,7 @@ def register(): CopyMany2ManyReferenceRelation, Many2OneTarget, Many2OneDomainValidation, + Many2OneOrderBy, TestHistory, TestHistoryLine, FieldContextChild, diff --git a/trytond/tests/test.py b/trytond/tests/test.py index 04a90fb9b6..cd73456283 100644 --- a/trytond/tests/test.py +++ b/trytond/tests/test.py @@ -34,7 +34,7 @@ __all__ = [ 'Selection', 'SelectionRequired', 'DictSchema', 'Dict', 'DictDefault', 'DictRequired', 'Binary', 'BinaryDefault', 'BinaryRequired', - 'Many2OneDomainValidation', 'Many2OneTarget', + 'Many2OneDomainValidation', 'Many2OneTarget', 'Many2OneOrderBy', ] @@ -683,6 +683,7 @@ class BinaryRequired(ModelSQL): class Many2OneTarget(ModelSQL): "Many2One Domain Validation Target" __name__ = 'test.many2one_target' + _order_name = 'value' active = fields.Boolean('Active') value = fields.Integer('Value') @@ -701,3 +702,9 @@ class Many2OneDomainValidation(ModelSQL): ('value', '>', 5), ]) dummy = fields.Char('Dummy') + + +class Many2OneOrderBy(ModelSQL): + "Many2One OrderBy" + __name__ = 'test.many2one_orderby' + many2one = fields.Many2One('test.many2one_target', 'many2one') diff --git a/trytond/tests/test_fields.py b/trytond/tests/test_fields.py index 0d5f24e015..2c6afeb4de 100644 --- a/trytond/tests/test_fields.py +++ b/trytond/tests/test_fields.py @@ -111,6 +111,7 @@ class FieldsTestCase(unittest.TestCase): self.binary_required = POOL.get('test.binary_required') self.m2o_domain_validation = POOL.get('test.many2one_domainvalidation') + self.m2o_orderby = POOL.get('test.many2one_orderby') self.m2o_target = POOL.get('test.many2one_target') def test0010boolean(self): @@ -3236,6 +3237,25 @@ class FieldsTestCase(unittest.TestCase): domain.dummy = 'Dummy' domain.save() + # Testing order_by + for value in (5, 3, 2): + m2o, = self.m2o_target.create([{'value': value}]) + self.m2o_orderby.create([{'many2one': m2o}]) + + search = self.m2o_orderby.search([], order=[('many2one', 'ASC')]) + self.assertTrue(all(x.many2one.value <= y.many2one.value + for x, y in zip(search, search[1:]))) + + search = self.m2o_orderby.search([], + order=[('many2one.id', 'ASC')]) + self.assertTrue(all(x.many2one.id <= y.many2one.id + for x, y in zip(search, search[1:]))) + + search = self.m2o_orderby.search([], + order=[('many2one.value', 'ASC')]) + self.assertTrue(all(x.many2one.value <= y.many2one.value + for x, y in zip(search, search[1:]))) + transaction.cursor.rollback()