[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:
committed by
Martin Trigaux
parent
ebaa132d5d
commit
4cd093a001
@@ -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. """
|
||||
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user