Add dynamic size limit on the One2Many, Many2Many and Char

review396001
This commit is contained in:
Cédric Krier
2012-06-20 18:12:40 +02:00
parent 9a25299e25
commit 6858187fc8
13 changed files with 185 additions and 19 deletions
+1
View File
@@ -1,3 +1,4 @@
* Add dynamic size limit on the One2Many, Many2Many and Char
* Replace __tryton__.py by tryton.cfg
* Allow to use Reference field in One2Many & Many2Many
* Remove hexmd5 from ModelView.fields_view_get
+12 -2
View File
@@ -415,7 +415,7 @@ A many-to-one relation field.
One2Many
--------
.. class:: One2Many(model_name, field, string[, add_remove[, order[, datetime_field[, \**options]]]])
.. class:: One2Many(model_name, field, string[, add_remove[, order[, datetime_field[, size[, \**options]]]]])
A one-to-many relation field. It requires to have the opposite
:class:`Many2One` field or a :class:`Reference` field defined on the target
@@ -470,10 +470,15 @@ This field accepts as written value a list of tuples like this:
Same as :attr:`Many2One.datetime_field`
.. attribute:: One2Many.size
An integer or a PYSON expression denoting the maximum number of records
allowed in the relation.
Many2Many
---------
.. class:: Many2Many(relation_name, origin, target, string[, order[, datetime_field[, \**options]]])
.. class:: Many2Many(relation_name, origin, target, string[, order[, datetime_field[, size[, \**options]]]])
A many-to-many relation field.
@@ -510,6 +515,11 @@ A many-to-many relation field.
Same as :attr:`Many2One.datetime_field`
.. attribute:: Many2Many.size
An integer or a PYSON expression denoting the maximum number of records
allowed in the relation.
Instance methods:
.. method:: Many2Many.get_target()
+1 -1
View File
@@ -32,7 +32,7 @@ class Char(fields.Char):
@staticmethod
def sql_type(field):
if field.size:
if isinstance(field.size, int):
return ('varchar', 'varchar(%d)' % (field.size,))
return ('varchar', 'varchar(255)')
+1 -1
View File
@@ -29,7 +29,7 @@ class Char(fields.Char):
@staticmethod
def sql_type(field):
if field.size:
if isinstance(field.size, int):
return ('varchar', 'varchar(%d)' % (field.size,))
return ('varchar', 'varchar')
+11 -1
View File
@@ -1,7 +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.fields.field import Field
from trytond.model.fields.field import Field, size_validate
def autocomplete_validate(value):
@@ -32,6 +32,7 @@ class Char(Field):
self.__autocomplete = None
self.autocomplete = autocomplete if autocomplete else None
self.translate = translate
self.__size = None
self.size = size
__init__.__doc__ += Field.__init__.__doc__
@@ -43,3 +44,12 @@ class Char(Field):
self.__autocomplete = value
autocomplete = property(_get_autocomplete, _set_autocomplete)
def _get_size(self):
return self.__size
def _set_size(self, value):
size_validate(value)
self.__size = value
size = property(_get_size, _set_size)
+8
View File
@@ -57,6 +57,14 @@ def context_validate(value):
assert isinstance(value, dict), 'context must be a dict'
def size_validate(value):
if value is not None:
assert isinstance(value, (int, PYSON)), 'size must be PYSON'
if hasattr(value, 'types'):
assert value.types() == set([int]), \
'size must return integer'
class Field(object):
_type = None
+17 -4
View File
@@ -1,7 +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 itertools import chain
from trytond.model.fields.field import Field
from trytond.model.fields.field import Field, size_validate
from trytond.transaction import Transaction
from trytond.pool import Pool
@@ -13,9 +13,10 @@ class Many2Many(Field):
_type = 'many2many'
def __init__(self, relation_name, origin, target, string='', order=None,
datetime_field=None, help='', required=False, readonly=False,
domain=None, states=None, on_change=None, on_change_with=None,
depends=None, order_field=None, context=None, loading='lazy'):
datetime_field=None, size=None, help='', required=False,
readonly=False, domain=None, states=None, on_change=None,
on_change_with=None, depends=None, order_field=None, context=None,
loading='lazy'):
'''
:param relation_name: The name of the relation model
or the name of the target model for ModelView only.
@@ -42,8 +43,20 @@ class Many2Many(Field):
self.target = target
self.order = order
self.datetime_field = datetime_field
self.__size = None
self.size = size
__init__.__doc__ += Field.__init__.__doc__
def _get_size(self):
return self.__size
def _set_size(self, value):
size_validate(value)
self.__size = value
size = property(_get_size, _set_size)
def get(self, ids, model, name, values=None):
'''
Return target records ordered.
+16 -5
View File
@@ -1,7 +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 itertools import chain
from trytond.model.fields.field import Field
from trytond.model.fields.field import Field, size_validate
from trytond.transaction import Transaction
from trytond.pool import Pool
@@ -18,10 +18,10 @@ class One2Many(Field):
_type = 'one2many'
def __init__(self, model_name, field, string='', add_remove=None,
order=None, datetime_field=None, help='', required=False,
readonly=False, domain=None, states=None, on_change=None,
on_change_with=None, depends=None, order_field=None, context=None,
loading='lazy'):
order=None, datetime_field=None, size=None, help='',
required=False, readonly=False, domain=None, states=None,
on_change=None, on_change_with=None, depends=None,
order_field=None, context=None, loading='lazy'):
'''
:param model_name: The name of the target model.
:param field: The name of the field that handle the reverse many2one or
@@ -50,6 +50,8 @@ class One2Many(Field):
self.add_remove = add_remove
self.order = order
self.datetime_field = datetime_field
self.__size = None
self.size = size
__init__.__doc__ += Field.__init__.__doc__
@@ -62,6 +64,15 @@ class One2Many(Field):
add_remove = property(_get_add_remove, _set_add_remove)
def _get_size(self):
return self.__size
def _set_size(self, value):
size_validate(value)
self.__size = value
size = property(_get_size, _set_size)
def get(self, ids, model, name, values=None):
'''
Return target records ordered.
+1 -1
View File
@@ -555,7 +555,7 @@ class Model(WarningErrorMixin, URLMixin):
res[field]['format'] = copy.copy(self._columns[field].format)
# convert attributes into pyson
for attr in ('states', 'domain', 'context', 'digits',
for attr in ('states', 'domain', 'context', 'digits', 'size',
'add_remove', 'format'):
if attr in res[field]:
res[field][attr] = encoder.encode(res[field][attr])
+5 -2
View File
@@ -121,11 +121,14 @@ class ModelSQL(ModelStorage):
return unpack_result
default_fun = unpack_wrapper(default_fun)
if hasattr(field, 'size') and isinstance(field.size, int):
field_size = field.size
else:
field_size = None
table.add_raw_column(field_name,
FIELDS[field._type].sql_type(field),
FIELDS[field._type].sql_format, default_fun,
hasattr(field, 'size') and field.size or None,
string=field.string)
field_size, string=field.string)
if self._history:
history_table.add_raw_column(field_name,
FIELDS[field._type].sql_type(field), None,
+13 -2
View File
@@ -1090,9 +1090,20 @@ class ModelStorage(Model):
for record in records:
required_test(record[field_name], field_name)
# validate size
if hasattr(field, 'size') and field.size:
if hasattr(field, 'size') and field.size is not None:
for record in records:
if len(record[field_name] or '') > field.size:
if isinstance(field.size, PYSON):
pyson_size = PYSONEncoder().encode(field.size)
env = EvalEnvironment(record, self)
env.update(Transaction().context)
env['current_date'] = datetime.datetime.today()
env['time'] = time
env['context'] = Transaction().context
env['active_id'] = record.id
field_size = PYSONDecoder(env).decode(pyson_size)
else:
field_size = field.size
if (len(record[field_name] or '') > field_size >= 0):
self.raise_user_error(
'size_validation_record',
error_args=self._get_error_args(field_name))
+68
View File
@@ -536,6 +536,45 @@ class One2ManyReferenceTarget(ModelSQL):
One2ManyReferenceTarget()
class One2ManySize(ModelSQL):
'One2Many Size Relation'
_name = 'test.one2many_size'
_description = __doc__
targets = fields.One2Many('test.one2many_size.target', 'origin', 'Targets',
size=3)
One2ManySize()
class One2ManySizeTarget(ModelSQL):
'One2Many Size Target'
_name = 'test.one2many_size.target'
_description = __doc__
origin = fields.Many2One('test.one2many_size', 'Origin')
One2ManySizeTarget()
class One2ManySizePYSON(ModelSQL):
'One2Many Size PYSON Relation'
_name = 'test.one2many_size_pyson'
_description = __doc__
limit = fields.Integer('Limit')
targets = fields.One2Many('test.one2many_size_pyson.target', 'origin',
'Targets', size=Eval('limit', 0))
One2ManySizePYSON()
class One2ManySizePYSONTarget(ModelSQL):
'One2Many Size PYSON Target'
_name = 'test.one2many_size_pyson.target'
_description = __doc__
origin = fields.Many2One('test.one2many_size_pyson', 'Origin')
One2ManySizePYSONTarget()
class Many2Many(ModelSQL):
'Many2Many'
_name = 'test.many2many'
@@ -632,6 +671,35 @@ class Many2ManyReferenceRelation(ModelSQL):
Many2ManyReferenceRelation()
class Many2ManySize(ModelSQL):
'Many2Many Size Relation'
_name = 'test.many2many_size'
_description = __doc__
targets = fields.Many2Many('test.many2many_size.relation', 'origin',
'target', 'Targets', size=5)
Many2ManySize()
class Many2ManySizeTarget(ModelSQL):
'Many2Many Size Target'
_name = 'test.many2many_size.target'
_description = __doc__
name = fields.Char('Name')
Many2ManySizeTarget()
class Many2ManySizeRelation(ModelSQL):
'Many2Many Size Relation'
_name = 'test.many2many_size.relation'
_description = __doc__
origin = fields.Many2One('test.many2many_size', 'Origin')
target = fields.Many2One('test.many2many_size.target', 'Target')
Many2ManySizeRelation()
class Property(ModelSQL):
'Property'
_name = 'test.property'
+31
View File
@@ -82,6 +82,8 @@ class FieldsTestCase(unittest.TestCase):
self.one2many_reference = POOL.get('test.one2many_reference')
self.one2many_reference_target = POOL.get(
'test.one2many_reference.target')
self.one2many_size = POOL.get('test.one2many_size')
self.one2many_size_pyson = POOL.get('test.one2many_size_pyson')
self.many2many = POOL.get('test.many2many')
self.many2many_target = POOL.get('test.many2many.target')
@@ -89,6 +91,8 @@ class FieldsTestCase(unittest.TestCase):
self.many2many_reference = POOL.get('test.many2many_reference')
self.many2many_reference_target = POOL.get(
'test.many2many_reference.target')
self.many2many_size = POOL.get('test.many2many_size')
self.many2many_size_target = POOL.get('test.many2many_size.target')
self.property_ = POOL.get('test.property')
@@ -2644,6 +2648,21 @@ class FieldsTestCase(unittest.TestCase):
})
self.assert_(origin3_id)
self.one2many_size.create({
'targets': [('create', {})] * 3,
})
self.assertRaises(Exception, self.one2many_size.create, {
'targets': [('create', {})] * 4,
})
self.one2many_size_pyson.create({
'limit': 4,
'targets': [('create', {})] * 4,
})
self.assertRaises(Exception, self.one2many_size_pyson.create, {
'limit': 2,
'targets': [('create', {})] * 4,
})
transaction.cursor.rollback()
def test0130many2many(self):
@@ -2820,6 +2839,18 @@ class FieldsTestCase(unittest.TestCase):
})
self.assert_(origin3_id)
size_targets = [
self.many2many_size_target.create({
'name': str(i),
}) for i in range(6)]
self.many2many_size.create({
'targets': [('set', size_targets[:5])],
})
self.assertRaises(Exception, self.many2many_size.create, {
'targets': [('set', size_targets)],
})
transaction.cursor.rollback()
def test0140property(self):