[IMP] fields: extend selection with a given order

When extended with the selection_add parameter, a singleton (value,)
where `value` appears in the overriden selection, can be used.
The new values are inserted in an order that is consistent with the
overridden selection and this list.

selection = [('a', 'A'), ('b', 'B')]
selection_add = [('c', 'C'), ('b',)]
> result = [('a', 'A'), ('c', 'C'), ('b', 'B')]
This commit is contained in:
Raphael Collet
2019-08-21 13:47:17 +00:00
committed by Martin Trigaux
parent ebaa132d5d
commit 4cd093a001
5 changed files with 94 additions and 13 deletions
+29 -1
View File
@@ -5,7 +5,7 @@ import datetime
from dateutil.relativedelta import relativedelta
import pytz
from odoo.tools import misc, date_utils
from odoo.tools import misc, date_utils, merge_sequences
from odoo.tests.common import TransactionCase, BaseCase
@@ -40,6 +40,34 @@ class TestCountingStream(BaseCase):
self.assertEqual(s.index, 0)
class TestMergeSequences(BaseCase):
def test_merge_sequences(self):
# base case
seq = merge_sequences(['A', 'B', 'C'])
self.assertEqual(seq, ['A', 'B', 'C'])
# 'Z' can be anywhere
seq = merge_sequences(['A', 'B', 'C'], ['Z'])
self.assertEqual(seq, ['A', 'B', 'C', 'Z'])
# 'Y' must precede 'C';
seq = merge_sequences(['A', 'B', 'C'], ['Y', 'C'])
self.assertEqual(seq, ['A', 'B', 'Y', 'C'])
# 'X' must follow 'A' and precede 'C'
seq = merge_sequences(['A', 'B', 'C'], ['A', 'X', 'C'])
self.assertEqual(seq, ['A', 'B', 'X', 'C'])
# all cases combined
seq = merge_sequences(
['A', 'B', 'C'],
['Z'], # 'Z' can be anywhere
['Y', 'C'], # 'Y' must precede 'C';
['A', 'X', 'Y'], # 'X' must follow 'A' and precede 'Y'
)
self.assertEqual(seq, ['A', 'B', 'X', 'Y', 'C', 'Z'])
class TestDateRangeFunction(BaseCase):
""" Test on date_range generator. """
+2 -2
View File
@@ -55,8 +55,8 @@ class mother(models.Model):
class mother(models.Model):
_inherit = 'test.inherit.mother'
# extend again the selection of the state field
state = fields.Selection(selection_add=[('d', 'D')])
# extend again the selection of the state field: 'd' must precede 'b'
state = fields.Selection(selection_add=[('d', 'D'), ('b',)])
class daughter(models.Model):
@@ -66,7 +66,7 @@ class test_inherits(common.TransactionCase):
# the extra values are added, both in the field and the column
self.assertEqual(mother._fields['state'].selection,
[('a', 'A'), ('b', 'B'), ('c', 'C'), ('d', 'D')])
[('a', 'A'), ('d', 'D'), ('b', 'B'), ('c', 'C')])
def test_50_search_one2many(self):
""" check search on one2many field based on inherited many2one field. """
+33 -9
View File
@@ -23,7 +23,7 @@ except ImportError:
import psycopg2
from .tools import float_repr, float_round, frozendict, html_sanitize, human_size, pg_varchar, \
ustr, OrderedSet, pycompat, sql, date_utils, unique, IterableGenerator, image_process
ustr, OrderedSet, pycompat, sql, date_utils, unique, IterableGenerator, image_process, merge_sequences
from .tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
from .tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
from .tools.translate import html_translate, _
@@ -1995,10 +1995,18 @@ class Image(Binary):
class Selection(Field):
"""
:param selection: specifies the possible values for this field.
It is given as either a list of pairs (``value``, ``string``), or a
model method, or a method name.
It is given as either a list of pairs ``(value, label)``, or a model
method, or a method name.
:param selection_add: provides an extension of the selection in the case
of an overridden field. It is a list of pairs (``value``, ``string``).
of an overridden field. It is a list of pairs ``(value, label)`` or
singletons ``(value,)``, where singleton values must appear in the
overridden selection. The new values are inserted in an order that is
consistent with the overridden selection and this list::
selection = [('a', 'A'), ('b', 'B')]
selection_add = [('c', 'C'), ('b',)]
> result = [('a', 'A'), ('c', 'C'), ('b', 'B')]
The attribute ``selection`` is mandatory except in the case of
:ref:`related fields <field-related>` or :ref:`field extensions
@@ -2029,16 +2037,31 @@ class Selection(Field):
def _setup_attrs(self, model, name):
super(Selection, self)._setup_attrs(model, name)
# determine selection (applying 'selection_add' extensions)
values = None
labels = {}
for field in reversed(resolve_mro(model, name, self._can_setup_from)):
# We cannot use field.selection or field.selection_add here
# because those attributes are overridden by ``_setup_attrs``.
if 'selection' in field.args:
self.selection = field.args['selection']
selection = field.args['selection']
if isinstance(selection, list):
values = [kv[0] for kv in selection]
labels.update(selection)
else:
self.selection = selection
if 'selection_add' in field.args:
# use an OrderedDict to update existing values
selection_add = field.args['selection_add']
self.selection = list(OrderedDict(self.selection + selection_add).items())
assert isinstance(selection_add, list)
assert values is not None
values = merge_sequences(values, [kv[0] for kv in selection_add])
labels.update(kv for kv in selection_add if len(kv) == 2)
if values is not None:
self.selection = [(value, labels[value]) for value in values]
def _selection_modules(self, model):
""" Return a mapping from selection values to modules defining each value. """
@@ -2055,8 +2078,9 @@ class Selection(Field):
for value, label in field.args['selection']:
value_modules[value].add(module)
if 'selection_add' in field.args:
for value, label in field.args['selection_add']:
value_modules[value].add(module)
for value_label in field.args['selection_add']:
if len(value_label) > 1:
value_modules[value_label[0]].add(module)
return value_modules
def _description_selection(self, env):
+29
View File
@@ -349,6 +349,35 @@ def topological_sort(elems):
return result
def merge_sequences(*iterables):
""" Merge several iterables into a list. The result is the union of the
iterables, ordered following the partial order given by the iterables,
with a bias towards the end for the last iterable::
seq = merge_sequences(['A', 'B', 'C'])
assert seq == ['A', 'B', 'C']
seq = merge_sequences(
['A', 'B', 'C'],
['Z'], # 'Z' can be anywhere
['Y', 'C'], # 'Y' must precede 'C';
['A', 'X', 'Y'], # 'X' must follow 'A' and precede 'Y'
)
assert seq == ['A', 'B', 'X', 'Y', 'C', 'Z']
"""
# we use an OrderedDict to keep elements in order by default
deps = OrderedDict() # {item: elems_before_item}
for iterable in iterables:
prev = None
for index, item in enumerate(iterable):
if not index:
deps.setdefault(item, [])
else:
deps.setdefault(item, []).append(prev)
prev = item
return topological_sort(deps)
try:
import xlwt