diff --git a/openerp/addons/base/base.sql b/openerp/addons/base/base.sql
index 55c4d41aba5..8c64aa69c57 100644
--- a/openerp/addons/base/base.sql
+++ b/openerp/addons/base/base.sql
@@ -49,6 +49,8 @@ CREATE TABLE ir_model_fields (
primary key(id)
);
+ALTER TABLE ir_model_fields ADD column serialization_field_id int references ir_model_fields on delete cascade;
+
-------------------------------------------------------------------------
-- Actions
diff --git a/openerp/addons/base/ir/ir.xml b/openerp/addons/base/ir/ir.xml
index da6f1e167f9..ee775bf6fd5 100644
--- a/openerp/addons/base/ir/ir.xml
+++ b/openerp/addons/base/ir/ir.xml
@@ -1018,6 +1018,8 @@
+
+
@@ -1130,6 +1132,8 @@
+
+
diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py
index 014d5652891..82dca02d962 100644
--- a/openerp/addons/base/ir/ir_model.py
+++ b/openerp/addons/base/ir/ir_model.py
@@ -207,6 +207,7 @@ class ir_model_fields(osv.osv):
'view_load': fields.boolean('View Auto-Load'),
'selectable': fields.boolean('Selectable'),
'modules': fields.function(_in_modules, method=True, type='char', size=128, string='In modules', help='List of modules in which the field is defined'),
+ 'serialization_field_id': fields.many2one('ir.model.fields', 'Serialization Field', domain = "[('ttype','=','serialized')]", ondelete='cascade'),
}
_rec_name='field_description'
_defaults = {
@@ -299,6 +300,14 @@ class ir_model_fields(osv.osv):
if context and context.get('manual',False):
vals['state'] = 'manual'
+ #For the moment renaming a sparse field or changing the storing system is not allowed. This will be done later
+ if 'serialization_field_id' in vals or 'name' in vals:
+ for field in self.browse(cr, user, ids, context=context):
+ if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']:
+ raise except_orm(_('Error!'), _('Changing the storing system for the field "%s" is not allowed.'%field.name))
+ if field.serialization_field_id and (field.name != vals['name']):
+ raise except_orm(_('Error!'), _('Renaming the sparse field "%s" is not allowed'%field.name))
+
column_rename = None # if set, *one* column can be renamed here
obj = None
models_patch = {} # structs of (obj, [(field, prop, change_to),..])
diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py
index 72df86eeff9..04364a63074 100644
--- a/openerp/osv/fields.py
+++ b/openerp/osv/fields.py
@@ -45,6 +45,7 @@ import openerp
import openerp.netsvc as netsvc
import openerp.tools as tools
from openerp.tools.translate import _
+import json
def _symbol_set(symb):
if symb == None or symb == False:
@@ -1178,6 +1179,99 @@ class related(function):
obj_name = f['relation']
self._relations[-1]['relation'] = f['relation']
+
+class sparse(function):
+
+ 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':
+ #NOTE only the option (0, 0, { values }) is supported for many2many
+ if value[0][0] == 6:
+ return value[0][2]
+
+ elif self._type == 'one2many':
+ if not read_value:
+ read_value=[]
+ relation_obj = obj.pool.get(self.relation)
+ for vals in value:
+ 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])
+ read_value.remove(vals[1])
+ return read_value
+ return value
+
+
+ def _fnct_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 = record.__getattr__(self.serialization_field)
+ # we have to delete the key in the json when the value is null
+ if value is None:
+ if field_name in serialized:
+ del serialized[field_name]
+ else:
+ # nothing to do, we dont wan't to store the key with a null value
+ continue
+ 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
+
+ def _fnct_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 = record.__getattr__(self.serialization_field)
+ results[record.id] ={}
+ for field_name in field_names:
+ if obj._columns[field_name]._type in ['one2many']:
+ results[record.id].update({field_name : serialized.get(field_name, [])})
+ else:
+ results[record.id].update({field_name : serialized.get(field_name)})
+ return results
+
+ def __init__(self, serialization_field, **kwargs):
+ self.serialization_field = serialization_field
+ #assert serialization_field._type == 'serialized'
+ return super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='_json_multi', method=True, **kwargs)
+
+
+
+
+
# ---------------------------------------------------------
# Dummy fields
# ---------------------------------------------------------
@@ -1200,14 +1294,26 @@ class dummy(function):
# ---------------------------------------------------------
# Serialized fields
# ---------------------------------------------------------
+
class serialized(_column):
- def __init__(self, string='unknown', serialize_func=repr, deserialize_func=eval, type='text', **args):
- self._serialize_func = serialize_func
- self._deserialize_func = deserialize_func
- self._type = type
- self._symbol_set = (self._symbol_c, self._serialize_func)
- self._symbol_get = self._deserialize_func
- super(serialized, self).__init__(string=string, **args)
+ """ A field able to store an arbitrary python data structure.
+
+ Note: only plain components allowed.
+ """
+
+ def _symbol_set_struct(val):
+ return json.dumps(val)
+
+ def _symbol_get_struct(self, val):
+ return json.loads(val or '{}')
+
+ _prefetch = False
+ _type = 'serialized'
+
+ _symbol_c = '%s'
+ _symbol_f = _symbol_set_struct
+ _symbol_set = (_symbol_c, _symbol_f)
+ _symbol_get = _symbol_get_struct
# TODO: review completly this class for speed improvement
class property(function):
diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py
index 58d35e21775..357204bd374 100644
--- a/openerp/osv/orm.py
+++ b/openerp/osv/orm.py
@@ -552,6 +552,7 @@ FIELDS_TO_PGTYPES = {
fields.datetime: 'timestamp',
fields.binary: 'bytea',
fields.many2one: 'int4',
+ fields.serialized: 'text',
}
def get_pg_type(f, type_override=None):
@@ -754,7 +755,17 @@ class BaseModel(object):
for rec in cr.dictfetchall():
cols[rec['name']] = rec
- for (k, f) in self._columns.items():
+ ir_model_fields_obj = self.pool.get('ir.model.fields')
+
+ high_priority_items=[]
+ low_priority_items=[]
+ #sparse field should be created at the end, indeed this field depend of other field
+ for item in self._columns.items():
+ if item[1].__class__ == fields.sparse:
+ low_priority_items.append(item)
+ else:
+ high_priority_items.append(item)
+ for (k, f) in high_priority_items + low_priority_items:
vals = {
'model_id': model_id,
'model': self._name,
@@ -769,7 +780,14 @@ class BaseModel(object):
'selectable': (f.selectable and 1) or 0,
'translate': (f.translate and 1) or 0,
'relation_field': (f._type=='one2many' and isinstance(f, fields.one2many)) and f._fields_id or '',
+ 'serialization_field_id': None,
}
+ if 'serialization_field' in dir(f):
+ serialization_field_id = ir_model_fields_obj.search(cr, 1, [('model','=',vals['model']), ('name', '=', f.serialization_field)])
+ if not serialization_field_id:
+ raise except_orm(_('Error'), _("The field %s does not exist!" %f.serialization_field))
+ vals['serialization_field_id'] = serialization_field_id[0]
+
# When its a custom field,it does not contain f.select
if context.get('field_state', 'base') == 'manual':
if context.get('field_name', '') == k:
@@ -784,13 +802,13 @@ class BaseModel(object):
vals['id'] = id
cr.execute("""INSERT INTO ir_model_fields (
id, model_id, model, name, field_description, ttype,
- relation,view_load,state,select_level,relation_field, translate
+ relation,view_load,state,select_level,relation_field, translate, serialization_field_id
) VALUES (
- %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
+ %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
)""", (
id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'],
vals['relation'], bool(vals['view_load']), 'base',
- vals['select_level'], vals['relation_field'], bool(vals['translate'])
+ vals['select_level'], vals['relation_field'], bool(vals['translate']), vals['serialization_field_id']
))
if 'module' in context:
name1 = 'field_' + self._table + '_' + k
@@ -807,12 +825,12 @@ class BaseModel(object):
cr.commit()
cr.execute("""UPDATE ir_model_fields SET
model_id=%s, field_description=%s, ttype=%s, relation=%s,
- view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s, translate=%s
+ view_load=%s, select_level=%s, readonly=%s ,required=%s, selectable=%s, relation_field=%s, translate=%s, serialization_field_id=%s
WHERE
model=%s AND name=%s""", (
vals['model_id'], vals['field_description'], vals['ttype'],
vals['relation'], bool(vals['view_load']),
- vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], bool(vals['translate']), vals['model'], vals['name']
+ vals['select_level'], bool(vals['readonly']), bool(vals['required']), bool(vals['selectable']), vals['relation_field'], bool(vals['translate']), vals['serialization_field_id'], vals['model'], vals['name']
))
break
cr.commit()
@@ -1001,6 +1019,12 @@ class BaseModel(object):
#'select': int(field['select_level'])
}
+ if field['serialization_field_id']:
+ cr.execute('SELECT name FROM ir_model_fields WHERE id=%s', (field['serialization_field_id'],))
+ attrs.update({'serialization_field': cr.fetchone()[0], 'type': field['ttype']})
+ if field['ttype'] in ['many2one', 'one2many', 'many2many']:
+ attrs.update({'relation': field['relation']})
+ self._columns[field['name']] = fields.sparse(**attrs)
if field['ttype'] == 'selection':
self._columns[field['name']] = fields.selection(eval(field['selection']), **attrs)
elif field['ttype'] == 'reference':