Add selection_change_with on Selection and Reference fields
review587002
This commit is contained in:
1 parent
b2d11b6df5
commit
d49fb4318d
11 files changed
+141
-17
No files matched your search
@@ -1,3 +1,4 @@
|
||||
* Add selection_change_with on Selection and Reference fields
|
||||
* Add Dict fields
|
||||
* Remove unique constraint on model and field access
|
||||
* Use lists of values in create
|
||||
|
||||
@@ -310,7 +310,7 @@ A binary field. It will be represented in Python by a ``str`` instance.
|
||||
Selection
|
||||
---------
|
||||
|
||||
.. class:: Selection(selection, string[, sort[, translate[, \**options]]])
|
||||
.. class:: Selection(selection, string[, sort[, selection_change_with[, translate[, \**options]]])
|
||||
|
||||
A string field with limited values to choice.
|
||||
|
||||
@@ -328,7 +328,7 @@ A string field with limited values to choice.
|
||||
The first element in each tuple is the actual value stored. The second
|
||||
element is the human-readable name.
|
||||
|
||||
It can also be the name of a method on the model, that will return an
|
||||
It can also be the name of a class method on the model, that will return an
|
||||
appropriate list. The signature of the method is::
|
||||
|
||||
selection()
|
||||
@@ -345,6 +345,13 @@ A string field with limited values to choice.
|
||||
If true, the choices will be sorted by human-readable value. Default value
|
||||
is ``True``.
|
||||
|
||||
.. attribute:: Selection.selection_change_with
|
||||
|
||||
A list of field names. If this attribute is set, the client will call the
|
||||
``selection`` method of the model when the user changes on of the fields
|
||||
defined in the list and will give the values of each fields in the list.
|
||||
The ``selection`` method should be an instance method.
|
||||
|
||||
.. attribute:: Selection.translate_selection
|
||||
|
||||
If true, the human-readable values will be translated. Default value is
|
||||
@@ -353,7 +360,7 @@ A string field with limited values to choice.
|
||||
Reference
|
||||
---------
|
||||
|
||||
.. class:: Reference(string[, selection[, \**options]])
|
||||
.. class:: Reference(string[, selection[, selection_change_with[, \**options]])
|
||||
|
||||
A field that refers to a record of a model. It will be represented in Python by
|
||||
a ``str`` instance like this::
|
||||
@@ -368,6 +375,10 @@ But a ``tuple`` can be used to search or set value.
|
||||
|
||||
Same as :attr:`Selection.selection` but only for model name.
|
||||
|
||||
.. attribute:: Reference.selection_change_with
|
||||
|
||||
Same like :attr:`Selection.selection_change_with`.
|
||||
|
||||
Many2One
|
||||
--------
|
||||
|
||||
|
||||
@@ -965,6 +965,7 @@ class TranslationClean(Wizard):
|
||||
'foreign_model_exist',
|
||||
'search_function_missing',
|
||||
'time_format_validation_record',
|
||||
'selection_value_notfound',
|
||||
):
|
||||
return False
|
||||
if model_name in pool.object_name_list():
|
||||
|
||||
@@ -229,6 +229,16 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="module">ir</field>
|
||||
<field name="fuzzy" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.translation"
|
||||
id="translation_selection_value_notfound">
|
||||
<field name="name">selection_value_notfound</field>
|
||||
<field name="lang">en_US</field>
|
||||
<field name="type">error</field>
|
||||
<field name="src">Value not in the selection for field "%s".</field>
|
||||
<field name="value">Value not in the selection for field "%s".</field>
|
||||
<field name="module">ir</field>
|
||||
<field name="fuzzy" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="translation_set_report_start_view_form">
|
||||
<field name="model">ir.translation.set_report.start</field>
|
||||
|
||||
@@ -14,9 +14,9 @@ class Reference(Field):
|
||||
'''
|
||||
_type = 'reference'
|
||||
|
||||
def __init__(self, string='', selection=None, help='', required=False,
|
||||
readonly=False, domain=None, states=None, select=False,
|
||||
on_change=None, on_change_with=None, depends=None,
|
||||
def __init__(self, string='', selection=None, selection_change_with=None,
|
||||
help='', required=False, readonly=False, domain=None, states=None,
|
||||
select=False, on_change=None, on_change_with=None, depends=None,
|
||||
order_field=None, context=None, loading='lazy'):
|
||||
'''
|
||||
:param selection: A list or a function name that returns a list.
|
||||
@@ -29,6 +29,7 @@ class Reference(Field):
|
||||
depends=depends, order_field=order_field, context=context,
|
||||
loading=loading)
|
||||
self.selection = selection or None
|
||||
self.selection_change_with = selection_change_with
|
||||
|
||||
__init__.__doc__ += Field.__init__.__doc__
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@ class Selection(Field):
|
||||
'''
|
||||
_type = 'selection'
|
||||
|
||||
def __init__(self, selection, string='', sort=True, translate=True,
|
||||
help='', required=False, readonly=False, domain=None, states=None,
|
||||
def __init__(self, selection, string='', sort=True,
|
||||
selection_change_with=None, translate=True, help='',
|
||||
required=False, readonly=False, domain=None, states=None,
|
||||
select=False, on_change=None, on_change_with=None, depends=None,
|
||||
order_field=None, context=None, loading='eager'):
|
||||
'''
|
||||
@@ -20,7 +21,7 @@ class Selection(Field):
|
||||
to store and the second is the value to display.
|
||||
:param sort: A boolean to sort or not the selections.
|
||||
'''
|
||||
super(Selection, self).__init__(string=string, help=help,
|
||||
super(Selection, self).__init__(string=string, help=help,
|
||||
required=required, readonly=readonly, domain=domain, states=states,
|
||||
select=select, on_change=on_change, on_change_with=on_change_with,
|
||||
depends=depends, order_field=order_field, context=context,
|
||||
@@ -29,6 +30,7 @@ class Selection(Field):
|
||||
self.selection = selection.copy()
|
||||
else:
|
||||
self.selection = selection
|
||||
self.selection_change_with = selection_change_with
|
||||
self.sort = sort
|
||||
self.translate_selection = translate
|
||||
__init__.__doc__ += Field.__init__.__doc__
|
||||
@@ -475,6 +475,7 @@ class Model(WarningErrorMixin, URLMixin):
|
||||
'datetime_field',
|
||||
'loading',
|
||||
'filename',
|
||||
'selection_change_with',
|
||||
):
|
||||
if getattr(cls._fields[field], arg, None) is not None:
|
||||
res[field][arg] = copy.copy(getattr(cls._fields[field],
|
||||
@@ -563,6 +564,8 @@ class Model(WarningErrorMixin, URLMixin):
|
||||
cls._fields[field].field)
|
||||
if res[field]['type'] in ('datetime', 'time'):
|
||||
res[field]['format'] = copy.copy(cls._fields[field].format)
|
||||
if res[field]['type'] == 'selection':
|
||||
res[field]['context'] = copy.copy(cls._fields[field].context)
|
||||
if res[field]['type'] == 'dict':
|
||||
res[field]['context'] = copy.copy(cls._fields[field].context)
|
||||
res[field]['create'] = accesses.get(field, {}).get('create',
|
||||
|
||||
@@ -927,6 +927,13 @@ class ModelStorage(Model):
|
||||
if isinstance(i, (list, tuple)):
|
||||
if is_pyson(i):
|
||||
return True
|
||||
if isinstance(test, dict):
|
||||
for key, value in test.items():
|
||||
if isinstance(value, PYSON):
|
||||
return True
|
||||
if isinstance(value, (list, tuple, dict)):
|
||||
if is_pyson(value):
|
||||
return True
|
||||
return False
|
||||
|
||||
with Transaction().set_context(ctx_pref):
|
||||
@@ -1081,13 +1088,6 @@ class ModelStorage(Model):
|
||||
if hasattr(field, 'selection') and field.selection:
|
||||
if isinstance(field.selection, (tuple, list)):
|
||||
test = set(dict(field.selection).keys())
|
||||
else:
|
||||
test = set(dict(getattr(cls,
|
||||
field.selection)()).keys())
|
||||
# None and '' are equivalent
|
||||
if '' in test or None in test:
|
||||
test.add('')
|
||||
test.add(None)
|
||||
for record in records:
|
||||
value = getattr(record, field_name)
|
||||
if field._type == 'reference':
|
||||
@@ -1095,6 +1095,17 @@ class ModelStorage(Model):
|
||||
value = value.__class__.__name__
|
||||
elif value:
|
||||
value, _ = value.split(',')
|
||||
if not isinstance(field.selection, (tuple, list)):
|
||||
sel_func = getattr(cls, field.selection)
|
||||
if field.selection_change_with:
|
||||
test = sel_func(record)
|
||||
else:
|
||||
test = sel_func()
|
||||
test = set(dict(test))
|
||||
# None and '' are equivalent
|
||||
if '' in test or None in test:
|
||||
test.add('')
|
||||
test.add(None)
|
||||
if value not in test:
|
||||
cls.raise_user_error('selection_validation_record',
|
||||
error_args=cls._get_error_args(field_name))
|
||||
|
||||
@@ -83,6 +83,8 @@ def register():
|
||||
ReferenceTarget,
|
||||
ReferenceRequired,
|
||||
Property,
|
||||
Selection,
|
||||
SelectionRequired,
|
||||
Dict,
|
||||
DictDefault,
|
||||
DictRequired,
|
||||
|
||||
@@ -5,6 +5,7 @@ import datetime
|
||||
from decimal import Decimal
|
||||
from trytond.model import ModelSQL, fields
|
||||
from trytond.pyson import Eval
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
__all__ = [
|
||||
'Boolean', 'BooleanDefault',
|
||||
@@ -32,6 +33,7 @@ __all__ = [
|
||||
'Many2ManySize', 'Many2ManySizeTarget', 'Many2ManySizeRelation',
|
||||
'Reference', 'ReferenceTarget', 'ReferenceRequired',
|
||||
'Property',
|
||||
'Selection', 'SelectionRequired',
|
||||
'Dict', 'DictDefault', 'DictRequired',
|
||||
]
|
||||
|
||||
@@ -589,6 +591,35 @@ class Property(ModelSQL):
|
||||
], 'Test Selection'))
|
||||
|
||||
|
||||
class Selection(ModelSQL):
|
||||
'Selection'
|
||||
__name__ = 'test.selection'
|
||||
select = fields.Selection([
|
||||
('', ''), ('arabic', 'Arabic'), ('hexa', 'Hexadecimal')],
|
||||
'Selection')
|
||||
dyn_select = fields.Selection('get_selection',
|
||||
'Instance Dynamic Selection', selection_change_with=['select'])
|
||||
dyn_select_static = fields.Selection('static_selection',
|
||||
'Static Selection')
|
||||
|
||||
def get_selection(self):
|
||||
if self.select == 'arabic':
|
||||
return [('', '')] + [(str(i), str(i)) for i in range(1, 11)]
|
||||
else:
|
||||
return [('', '')] + [(hex(i), hex(i)) for i in range(1, 11)]
|
||||
|
||||
@staticmethod
|
||||
def static_selection():
|
||||
return [('', '')] + [(str(i), str(i)) for i in range(1, 11)]
|
||||
|
||||
|
||||
class SelectionRequired(ModelSQL):
|
||||
'Selection Required'
|
||||
__name__ = 'test.selection_required'
|
||||
select = fields.Selection([('arabic', 'Arabic'), ('latin', 'Latin')],
|
||||
'Selection', required=True)
|
||||
|
||||
|
||||
class Dict(ModelSQL):
|
||||
'Dict'
|
||||
__name__ = 'test.dict'
|
||||
|
||||
@@ -102,6 +102,9 @@ class FieldsTestCase(unittest.TestCase):
|
||||
self.ir_property = POOL.get('ir.property')
|
||||
self.model_field = POOL.get('ir.model.field')
|
||||
|
||||
self.selection = POOL.get('test.selection')
|
||||
self.selection_required = POOL.get('test.selection_required')
|
||||
|
||||
self.dict_ = POOL.get('test.dict')
|
||||
self.dict_default = POOL.get('test.dict_default')
|
||||
self.dict_required = POOL.get('test.dict_required')
|
||||
@@ -3133,7 +3136,55 @@ class FieldsTestCase(unittest.TestCase):
|
||||
|
||||
transaction.cursor.rollback()
|
||||
|
||||
def test0160dict(self):
|
||||
def test_0160selection(self):
|
||||
with Transaction().start(DB_NAME, USER,
|
||||
context=CONTEXT) as transaction:
|
||||
selection1, = self.selection.create([{'select': 'arabic'}])
|
||||
self.assert_(selection1)
|
||||
self.assertEqual(selection1.select, 'arabic')
|
||||
|
||||
selection2, = self.selection.create([{'select': None}])
|
||||
self.assert_(selection2)
|
||||
self.assertEqual(selection2.select, None)
|
||||
|
||||
self.assertRaises(Exception, self.selection.create,
|
||||
[{'select': 'chinese'}])
|
||||
|
||||
selection3, = self.selection.create(
|
||||
[{'select': 'arabic', 'dyn_select': '1'}])
|
||||
self.assert_(selection3)
|
||||
self.assertEqual(selection3.select, 'arabic')
|
||||
self.assertEqual(selection3.dyn_select, '1')
|
||||
|
||||
selection4, = self.selection.create(
|
||||
[{'select': 'hexa', 'dyn_select': '0x3'}])
|
||||
self.assert_(selection4)
|
||||
self.assertEqual(selection4.select, 'hexa')
|
||||
self.assertEqual(selection4.dyn_select, '0x3')
|
||||
|
||||
selection5, = self.selection.create(
|
||||
[{'select': 'hexa', 'dyn_select': None}])
|
||||
self.assert_(selection5)
|
||||
self.assertEqual(selection5.select, 'hexa')
|
||||
self.assertEqual(selection5.dyn_select, None)
|
||||
|
||||
self.assertRaises(Exception, self.selection.create,
|
||||
[{'select': 'arabic', 'dyn_select': '0x3'}])
|
||||
self.assertRaises(Exception, self.selection.create,
|
||||
[{'select': 'hexa', 'dyn_select': '3'}])
|
||||
|
||||
self.assertRaises(Exception, self.selection_required.create, [{}])
|
||||
transaction.cursor.rollback()
|
||||
|
||||
self.assertRaises(Exception, self.selection_required.create,
|
||||
[{'select': None}])
|
||||
transaction.cursor.rollback()
|
||||
|
||||
selection6, = self.selection_required.create([{'select': 'latin'}])
|
||||
self.assert_(selection6)
|
||||
self.assertEqual(selection6.select, 'latin')
|
||||
|
||||
def test0170dict(self):
|
||||
with Transaction().start(DB_NAME, USER,
|
||||
context=CONTEXT) as transaction:
|
||||
dict1, = self.dict_.create([{
|
||||
|
||||
Reference in new issue
Block a user