diff --git a/CHANGELOG b/CHANGELOG index 6d0f424c7d..4eef906442 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add sequence_ordered * Remove most country specific code in languages * Add support for derivative translations * Remove IDENTIFIER regexp on ir.model and ir.model.field diff --git a/doc/ref/models/models.rst b/doc/ref/models/models.rst index a106dd0dd1..583b546051 100644 --- a/doc/ref/models/models.rst +++ b/doc/ref/models/models.rst @@ -683,6 +683,19 @@ Class methods: Return the SQL table and columns to use for the UNION for the model name. +================ +sequence_ordered +================ + +.. method:: sequence_ordered([field_name, [field_label, [order, [null_first]]]]) + +Retuns a mixin_ class which defines the order of a :class:`ModelSQL` with an +:class:`trytond.model.fields.Integer` field. field_name indicates the name of +the field to be created and its default values is `sequence`. field_label +defines the label which will be used by the field and defaults to `Sequence`. +Order specifies the order direction and defaults to `ASC` and null first +if the null values should be ordered first and defaults to `True`. + .. _mixin: http://en.wikipedia.org/wiki/Mixin .. _JSON: http://en.wikipedia.org/wiki/Json diff --git a/trytond/ir/ui/menu.py b/trytond/ir/ui/menu.py index 00ac991f35..5a5a0e55fe 100644 --- a/trytond/ir/ui/menu.py +++ b/trytond/ir/ui/menu.py @@ -2,10 +2,7 @@ # this repository contains the full copyright notices and license terms. from itertools import groupby -from sql import Null -from sql.conditionals import Case - -from trytond.model import ModelView, ModelSQL, fields +from trytond.model import ModelView, ModelSQL, fields, sequence_ordered from trytond.transaction import Transaction from trytond.pool import Pool from trytond.rpc import RPC @@ -72,12 +69,11 @@ CLIENT_ICONS = [(x, x) for x in ( SEPARATOR = ' / ' -class UIMenu(ModelSQL, ModelView): +class UIMenu(sequence_ordered(), ModelSQL, ModelView): "UI menu" __name__ = 'ir.ui.menu' name = fields.Char('Menu', required=True, translate=True) - sequence = fields.Integer('Sequence', required=True) childs = fields.One2Many('ir.ui.menu', 'parent', 'Children') parent = fields.Many2One('ir.ui.menu', 'Parent Menu', select=True, ondelete='CASCADE') @@ -102,7 +98,6 @@ class UIMenu(ModelSQL, ModelView): @classmethod def __setup__(cls): super(UIMenu, cls).__setup__() - cls._order.insert(0, ('sequence', 'ASC')) cls._error_messages.update({ 'wrong_name': ('"%%s" is not a valid menu name because it is ' 'not allowed to contain "%s".' % SEPARATOR), @@ -271,13 +266,12 @@ class UIMenu(ModelSQL, ModelView): return menu2favorite -class UIMenuFavorite(ModelSQL, ModelView): +class UIMenuFavorite(sequence_ordered(), ModelSQL, ModelView): "Menu Favorite" __name__ = 'ir.ui.menu.favorite' menu = fields.Many2One('ir.ui.menu', 'Menu', required=True, ondelete='CASCADE') - sequence = fields.Integer('Sequence') user = fields.Many2One('res.user', 'User', required=True, ondelete='CASCADE') @@ -289,15 +283,6 @@ class UIMenuFavorite(ModelSQL, ModelView): 'set': RPC(readonly=False), 'unset': RPC(readonly=False), }) - cls._order = [ - ('sequence', 'ASC'), - ('id', 'DESC'), - ] - - @staticmethod - def order_sequence(tables): - table, _ = tables[None] - return [Case((table.sequence == Null, 0), else_=1), table.sequence] @staticmethod def default_user(): diff --git a/trytond/model/__init__.py b/trytond/model/__init__.py index 54e9620923..a551e51ca1 100644 --- a/trytond/model/__init__.py +++ b/trytond/model/__init__.py @@ -10,8 +10,9 @@ from .dictschema import DictSchemaMixin from .match import MatchMixin from .union import UnionMixin from .descriptors import dualmethod +from .order import sequence_ordered __all__ = ['Model', 'ModelView', 'ModelStorage', 'ModelSingleton', 'ModelSQL', 'Check', 'Unique', 'Workflow', 'DictSchemaMixin', 'MatchMixin', 'UnionMixin', 'dualmethod', - 'EvalEnvironment'] + 'EvalEnvironment', 'sequence_ordered'] diff --git a/trytond/model/order.py b/trytond/model/order.py new file mode 100644 index 0000000000..d5a59b26a6 --- /dev/null +++ b/trytond/model/order.py @@ -0,0 +1,33 @@ +# This file is part of Tryton. The COPYRIGHT file at the toplevel of this +# repository contains the full copyright notices and license terms. +from sql import Null, Column +from sql.operators import Equal, NotEqual +from sql.conditionals import Case + +from trytond.model import fields + + +def sequence_ordered(field_name='sequence', field_label='Sequence', + order='ASC', null_first=True): + "Returns a mixin to order the model by order fields" + + class SequenceOrderedMixin(object): + "Mixin to order model by a sequence field" + + @classmethod + def __setup__(cls): + super(SequenceOrderedMixin, cls).__setup__() + cls._order = [(field_name, order)] + cls._order + + setattr(SequenceOrderedMixin, field_name, fields.Integer(field_label)) + + @classmethod + def order_function(cls, tables): + table, _ = tables[None] + operator = Equal + if not null_first: + operator = NotEqual + field = Column(table, field_name) + return [Case((operator(field, Null), 0), else_=1), field] + setattr(SequenceOrderedMixin, 'order_%s' % field_name, order_function) + return SequenceOrderedMixin diff --git a/trytond/tests/__init__.py b/trytond/tests/__init__.py index 968bcfe163..cf8e2f8d68 100644 --- a/trytond/tests/__init__.py +++ b/trytond/tests/__init__.py @@ -120,6 +120,7 @@ def register(): Model4UnionTree1, Model4UnionTree2, UnionTree, + SequenceOrderedModel, ModelViewChangedValues, ModelViewChangedValuesTarget, ModelViewButton, diff --git a/trytond/tests/model.py b/trytond/tests/model.py index b30a672194..69174e6def 100644 --- a/trytond/tests/model.py +++ b/trytond/tests/model.py @@ -1,6 +1,7 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from trytond.model import ModelSingleton, ModelSQL, UnionMixin, fields +from trytond.model import (ModelSingleton, ModelSQL, UnionMixin, fields, + sequence_ordered) from trytond.transaction import Transaction __all__ = [ @@ -11,6 +12,7 @@ __all__ = [ 'Model4Union1', 'Model4Union2', 'Model4Union3', 'Model4Union4', 'Union', 'UnionUnion', 'Model4UnionTree1', 'Model4UnionTree2', 'UnionTree', + 'SequenceOrderedModel', ] @@ -154,3 +156,8 @@ class UnionTree(UnionMixin, ModelSQL): @staticmethod def union_models(): return ['test.model.union.tree1', 'test.model.union.tree2'] + + +class SequenceOrderedModel(sequence_ordered(), ModelSQL): + 'Sequence Ordered Model' + __name__ = 'test.order.sequence' diff --git a/trytond/tests/test_order.py b/trytond/tests/test_order.py new file mode 100644 index 0000000000..3dd78c44ea --- /dev/null +++ b/trytond/tests/test_order.py @@ -0,0 +1,39 @@ +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. +import unittest + +from trytond.tests.test_tryton import install_module, with_transaction +from trytond.pool import Pool + + +class SequenceOrderedMixinTestCase(unittest.TestCase): + 'Test SequenceOrderedMixin' + + @classmethod + def setUpClass(cls): + install_module('tests') + + @with_transaction() + def test_order(self): + 'Test order' + pool = Pool() + Order = pool.get('test.order.sequence') + + models = [] + for i in reversed(range(1, 4)): + models.append(Order(sequence=i)) + Order.save(models) + models.reverse() + self.assertListEqual(Order.search([]), models) + + model = models.pop() + model.sequence = None + model.save() + models.insert(0, model) + + self.assertListEqual(Order.search([]), models) + + +def suite(): + return unittest.TestLoader().loadTestsFromTestCase( + SequenceOrderedMixinTestCase)