[REF] fields: implement sparse fields as computed fields
This commit is contained in:
@@ -11,4 +11,5 @@ access_test_new_api_foo,access_test_new_api_foo,model_test_new_api_foo,,1,1,1,1
|
||||
access_test_new_api_bar,access_test_new_api_bar,model_test_new_api_bar,,1,1,1,1
|
||||
access_test_new_api_related,access_test_new_api_related,model_test_new_api_related,,1,1,1,1
|
||||
access_test_new_api_company,access_test_new_api_company,model_test_new_api_company,,1,1,1,1
|
||||
access_test_new_api_sparse,access_test_new_api_sparse,model_test_new_api_sparse,,1,1,1,1
|
||||
access_test_new_api_compute_inverse,access_test_new_api_compute_inverse,model_test_new_api_compute_inverse,,1,1,1,1
|
||||
|
||||
|
@@ -280,3 +280,15 @@ class CompanyDependent(models.Model):
|
||||
_name = 'test_new_api.company'
|
||||
|
||||
foo = fields.Char(company_dependent=True)
|
||||
|
||||
|
||||
class Sparse(models.Model):
|
||||
_name = 'test_new_api.sparse'
|
||||
|
||||
data = fields.Serialized()
|
||||
boolean = fields.Boolean(sparse='data')
|
||||
integer = fields.Integer(sparse='data')
|
||||
float = fields.Float(sparse='data')
|
||||
char = fields.Char(sparse='data')
|
||||
selection = fields.Selection([('one', 'One'), ('two', 'Two')], sparse='data')
|
||||
partner = fields.Many2one('res.partner', sparse='data')
|
||||
|
||||
@@ -479,6 +479,32 @@ class TestFields(common.TransactionCase):
|
||||
self.assertEqual(record.sudo(user1).foo, 'alpha')
|
||||
self.assertEqual(record.sudo(user2).foo, 'default')
|
||||
|
||||
def test_28_sparse(self):
|
||||
""" test sparse fields. """
|
||||
record = self.env['test_new_api.sparse'].create({})
|
||||
self.assertFalse(record.data)
|
||||
|
||||
partner = self.env.ref('base.main_partner')
|
||||
values = [
|
||||
('boolean', True),
|
||||
('integer', 42),
|
||||
('float', 3.14),
|
||||
('char', 'John'),
|
||||
('selection', 'two'),
|
||||
('partner', partner.id),
|
||||
]
|
||||
for n, (key, val) in enumerate(values):
|
||||
record.write({key: val})
|
||||
self.assertEqual(record.data, dict(values[:n+1]))
|
||||
|
||||
for key, val in values[:-1]:
|
||||
self.assertEqual(record[key], val)
|
||||
self.assertEqual(record.partner, partner)
|
||||
|
||||
for n, (key, val) in enumerate(values):
|
||||
record.write({key: False})
|
||||
self.assertEqual(record.data, dict(values[n+1:]))
|
||||
|
||||
def test_30_read(self):
|
||||
""" test computed fields as returned by read(). """
|
||||
discussion = self.env.ref('test_new_api.discussion_0')
|
||||
|
||||
+45
-1
@@ -250,6 +250,17 @@ class Field(object):
|
||||
|
||||
:param company_dependent: whether the field is company-dependent (boolean)
|
||||
|
||||
.. _field-sparse:
|
||||
|
||||
.. rubric:: Sparse fields
|
||||
|
||||
Sparse fields have a very small probability of being not null. Therefore
|
||||
many such fields can be serialized compactly into a common location, the
|
||||
latter being a so-called "serialized" field.
|
||||
|
||||
:param sparse: the name of the field where the value of this field must
|
||||
be stored.
|
||||
|
||||
.. _field-incremental-definition:
|
||||
|
||||
.. rubric:: Incremental definition
|
||||
@@ -306,6 +317,7 @@ class Field(object):
|
||||
'related': None, # sequence of field names, for related fields
|
||||
'related_sudo': True, # whether related fields should be read as admin
|
||||
'company_dependent': False, # whether ``self`` is company-dependent (property field)
|
||||
'sparse': None, # the name of the corresponding serialized field, if any
|
||||
'default': None, # default(recs) returns the default value
|
||||
|
||||
'string': None, # field label
|
||||
@@ -427,6 +439,13 @@ class Field(object):
|
||||
if not attrs.get('readonly'):
|
||||
attrs['inverse'] = self._inverse_company_dependent
|
||||
attrs['search'] = self._search_company_dependent
|
||||
if attrs.get('sparse'):
|
||||
# by default, sparse fields are not stored and not copied
|
||||
attrs['store'] = False
|
||||
attrs['copy'] = attrs.get('copy', False)
|
||||
attrs['compute'] = self._compute_sparse
|
||||
if not attrs.get('readonly'):
|
||||
attrs['inverse'] = self._inverse_sparse
|
||||
|
||||
# fix for function fields overridden by regular columns
|
||||
if not isinstance(attrs.get('origin'), (NoneType, fields.function)):
|
||||
@@ -596,6 +615,31 @@ class Field(object):
|
||||
Property = records.env['ir.property']
|
||||
return Property.search_multi(self.name, self.model_name, operator, value)
|
||||
|
||||
#
|
||||
# Sparse fields
|
||||
#
|
||||
|
||||
def _compute_sparse(self, records):
|
||||
for record in records:
|
||||
values = record[self.sparse]
|
||||
record[self.name] = values.get(self.name)
|
||||
if self.relational:
|
||||
for record in records:
|
||||
record[self.name] = record[self.name].exists()
|
||||
|
||||
def _inverse_sparse(self, records):
|
||||
for record in records:
|
||||
values = record[self.sparse]
|
||||
value = self.convert_to_read(record[self.name], record, use_name_get=False)
|
||||
if value:
|
||||
if values.get(self.name) != value:
|
||||
values[self.name] = value
|
||||
record[self.sparse] = values
|
||||
else:
|
||||
if self.name in values:
|
||||
values.pop(self.name)
|
||||
record[self.sparse] = values
|
||||
|
||||
#
|
||||
# Setup of field triggers
|
||||
#
|
||||
@@ -2047,7 +2091,7 @@ class Many2many(_RelationalMulti):
|
||||
|
||||
|
||||
class Serialized(Field):
|
||||
""" Minimal support for existing sparse and serialized fields. """
|
||||
""" Serialized fields provide the storage for sparse fields. """
|
||||
type = 'serialized'
|
||||
|
||||
def convert_to_cache(self, value, record, validate=True):
|
||||
|
||||
+3
-82
@@ -1619,92 +1619,13 @@ class sparse(function):
|
||||
__slots__ = ['serialization_field']
|
||||
|
||||
def convert_value(self, obj, cr, uid, record, value, read_value, context=None):
|
||||
"""
|
||||
+ For a many2many field, a list of tuples is expected.
|
||||
Here is the list of tuple that are accepted, with the corresponding semantics ::
|
||||
|
||||
(0, 0, { values }) link to a new record that needs to be created with the given values dictionary
|
||||
(1, ID, { values }) update the linked record with id = ID (write *values* on it)
|
||||
(2, ID) remove and delete the linked record with id = ID (calls unlink on ID, that will delete the object completely, and the link to it as well)
|
||||
(3, ID) cut the link to the linked record with id = ID (delete the relationship between the two objects but does not delete the target object itself)
|
||||
(4, ID) link to existing record with id = ID (adds a relationship)
|
||||
(5) unlink all (like using (3,ID) for all linked records)
|
||||
(6, 0, [IDs]) replace the list of linked IDs (like using (5) then (4,ID) for each ID in the list of IDs)
|
||||
|
||||
Example:
|
||||
[(6, 0, [8, 5, 6, 4])] sets the many2many to ids [8, 5, 6, 4]
|
||||
|
||||
+ For a one2many field, a lits of tuples is expected.
|
||||
Here is the list of tuple that are accepted, with the corresponding semantics ::
|
||||
|
||||
(0, 0, { values }) link to a new record that needs to be created with the given values dictionary
|
||||
(1, ID, { values }) update the linked record with id = ID (write *values* on it)
|
||||
(2, ID) remove and delete the linked record with id = ID (calls unlink on ID, that will delete the object completely, and the link to it as well)
|
||||
|
||||
Example:
|
||||
[(0, 0, {'field_name':field_value_record1, ...}), (0, 0, {'field_name':field_value_record2, ...})]
|
||||
"""
|
||||
|
||||
if self._type == 'many2many':
|
||||
if not value:
|
||||
return []
|
||||
assert value[0][0] == 6, 'Unsupported m2m value for sparse field: %s' % value
|
||||
return value[0][2]
|
||||
|
||||
elif self._type == 'one2many':
|
||||
if not read_value:
|
||||
read_value = []
|
||||
relation_obj = obj.pool[self.relation]
|
||||
for vals in value:
|
||||
assert vals[0] in (0,1,2), 'Unsupported o2m value for sparse field: %s' % vals
|
||||
if vals[0] == 0:
|
||||
read_value.append(relation_obj.create(cr, uid, vals[2], context=context))
|
||||
elif vals[0] == 1:
|
||||
relation_obj.write(cr, uid, vals[1], vals[2], context=context)
|
||||
elif vals[0] == 2:
|
||||
relation_obj.unlink(cr, uid, vals[1], context=context)
|
||||
read_value.remove(vals[1])
|
||||
return read_value
|
||||
return value
|
||||
raise TypeError("Dead method")
|
||||
|
||||
def _sparse_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
|
||||
if not type(ids) == list:
|
||||
ids = [ids]
|
||||
records = obj.browse(cr, uid, ids, context=context)
|
||||
for record in records:
|
||||
# grab serialized value as object - already deserialized
|
||||
serialized = getattr(record, self.serialization_field)
|
||||
if value is None:
|
||||
# simply delete the key to unset it.
|
||||
serialized.pop(field_name, None)
|
||||
else:
|
||||
serialized[field_name] = self.convert_value(obj, cr, uid, record, value, serialized.get(field_name), context=context)
|
||||
obj.write(cr, uid, ids, {self.serialization_field: serialized}, context=context)
|
||||
return True
|
||||
raise TypeError("Dead method")
|
||||
|
||||
def _sparse_read(self, obj, cr, uid, ids, field_names, args, context=None):
|
||||
results = {}
|
||||
records = obj.browse(cr, uid, ids, context=context)
|
||||
for record in records:
|
||||
# grab serialized value as object - already deserialized
|
||||
serialized = getattr(record, self.serialization_field)
|
||||
results[record.id] = {}
|
||||
for field_name in field_names:
|
||||
field_type = obj._columns[field_name]._type
|
||||
value = serialized.get(field_name, False)
|
||||
if field_type in ('one2many','many2many'):
|
||||
value = value or []
|
||||
if value:
|
||||
# filter out deleted records as superuser
|
||||
relation_obj = obj.pool[obj._columns[field_name].relation]
|
||||
value = relation_obj.exists(cr, openerp.SUPERUSER_ID, value)
|
||||
if type(value) in (int,long) and field_type == 'many2one':
|
||||
relation_obj = obj.pool[obj._columns[field_name].relation]
|
||||
# check for deleted record as superuser
|
||||
if not relation_obj.exists(cr, openerp.SUPERUSER_ID, [value]):
|
||||
value = False
|
||||
results[record.id][field_name] = value
|
||||
return results
|
||||
raise TypeError("Dead method")
|
||||
|
||||
def __init__(self, serialization_field, **kwargs):
|
||||
self.serialization_field = serialization_field
|
||||
|
||||
Reference in New Issue
Block a user