[MERGE] Introduction of field.sparse, courtesy of Sebastien Beau (Akretion)
bzr revid: odo@openerp.com-20111222205754-0nsrqyeyrzhyakiy
This commit is contained in:
commit
6f09a2b467
|
@ -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
|
||||
|
|
|
@ -1018,6 +1018,8 @@
|
|||
<field name="selection" attrs="{'required': [('ttype','in',['selection','reference'])], 'readonly': [('ttype','not in',['selection','reference'])]}"/>
|
||||
<field name="size" attrs="{'required': [('ttype','in',['char','reference'])], 'readonly': [('ttype','not in',['char','reference'])]}"/>
|
||||
<field name="domain" attrs="{'readonly': [('relation','=','')]}"/>
|
||||
<field name="model_id" invisible="1"/>
|
||||
<field name="serialization_field_id" attrs="{'readonly': [('state','=','base')]}" domain = "[('ttype','=','serialized'), ('model_id', '=', model_id)]"/>
|
||||
</group>
|
||||
<group colspan="2" col="2">
|
||||
<field name="required"/>
|
||||
|
@ -1130,6 +1132,8 @@
|
|||
<field name="selection" attrs="{'required': [('ttype','in',['selection','reference'])], 'readonly': [('ttype','not in',['selection','reference'])]}"/>
|
||||
<field name="size" attrs="{'required': [('ttype','in',['char','reference'])], 'readonly': [('ttype','not in',['char','reference'])]}"/>
|
||||
<field name="domain" attrs="{'readonly': [('relation','=','')]}"/>
|
||||
<field name="model_id" invisible="1"/>
|
||||
<field name="serialization_field_id" attrs="{'readonly': [('state','=','base')]}" domain = "[('ttype','=','serialized'), ('model_id', '=', model_id)]"/>
|
||||
</group>
|
||||
|
||||
<group colspan="2" col="2">
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
import logging
|
||||
import re
|
||||
import time
|
||||
import types
|
||||
|
||||
from osv import fields,osv
|
||||
import netsvc
|
||||
|
@ -32,13 +33,12 @@ from tools.translate import _
|
|||
import pooler
|
||||
|
||||
def _get_fields_type(self, cr, uid, context=None):
|
||||
cr.execute('select distinct ttype,ttype from ir_model_fields')
|
||||
field_types = cr.fetchall()
|
||||
field_types_copy = field_types
|
||||
for types in field_types_copy:
|
||||
if not hasattr(fields,types[0]):
|
||||
field_types.remove(types)
|
||||
return field_types
|
||||
return sorted([(k,k) for k,v in fields.__dict__.iteritems()
|
||||
if type(v) == types.TypeType
|
||||
if issubclass(v, fields._column)
|
||||
if v != fields._column
|
||||
if not v._deprecated
|
||||
if not issubclass(v, fields.function)])
|
||||
|
||||
def _in_modules(self, cr, uid, ids, field_name, arg, context=None):
|
||||
#pseudo-method used by fields.function in ir.model/ir.model.fields
|
||||
|
@ -207,6 +207,11 @@ 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', help="If set, this field will be stored in the sparse "
|
||||
"structure of the serialization field, instead "
|
||||
"of having its own database column. This cannot be "
|
||||
"changed after creation."),
|
||||
}
|
||||
_rec_name='field_description'
|
||||
_defaults = {
|
||||
|
@ -299,6 +304,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 may 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),..])
|
||||
|
|
|
@ -46,6 +46,7 @@ import openerp.netsvc as netsvc
|
|||
import openerp.tools as tools
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import float_round, float_repr
|
||||
import json
|
||||
|
||||
def _symbol_set(symb):
|
||||
if symb == None or symb == False:
|
||||
|
@ -74,6 +75,9 @@ class _column(object):
|
|||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
_symbol_get = None
|
||||
|
||||
# used to hide a certain field type in the list of field types
|
||||
_deprecated = False
|
||||
|
||||
def __init__(self, string='unknown', required=False, readonly=False, domain=None, context=None, states=None, priority=0, change_default=False, size=None, ondelete=None, translate=False, select=False, manual=False, **args):
|
||||
"""
|
||||
|
||||
|
@ -167,6 +171,7 @@ class integer_big(_column):
|
|||
_symbol_f = lambda x: int(x or 0)
|
||||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
_symbol_get = lambda self,x: x or 0
|
||||
_deprecated = True
|
||||
|
||||
def __init__(self, string='unknown', required=False, **args):
|
||||
super(integer_big, self).__init__(string=string, required=required, **args)
|
||||
|
@ -273,6 +278,7 @@ class datetime(_column):
|
|||
|
||||
class time(_column):
|
||||
_type = 'time'
|
||||
_deprecated = True
|
||||
@staticmethod
|
||||
def now( *args):
|
||||
""" Returns the current time in a format fit for being a
|
||||
|
@ -346,6 +352,7 @@ class one2one(_column):
|
|||
_classic_read = False
|
||||
_classic_write = True
|
||||
_type = 'one2one'
|
||||
_deprecated = True
|
||||
|
||||
def __init__(self, obj, string='unknown', **args):
|
||||
warnings.warn("The one2one field doesn't work anymore", DeprecationWarning)
|
||||
|
@ -1187,6 +1194,102 @@ class related(function):
|
|||
result[-1]['relation'] = f['relation']
|
||||
self._relations = result
|
||||
|
||||
|
||||
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':
|
||||
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.get(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
|
||||
|
||||
|
||||
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 = 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
|
||||
|
||||
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 = 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.get(self.relation)
|
||||
value = relation_obj.exists(cr, openerp.SUPERUSER_ID, value)
|
||||
if type(value) in (int,long) and field_type == 'many2one':
|
||||
relation_obj = obj.pool.get(self.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
|
||||
|
||||
def __init__(self, serialization_field, **kwargs):
|
||||
self.serialization_field = serialization_field
|
||||
return super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', method=True, **kwargs)
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Dummy fields
|
||||
# ---------------------------------------------------------
|
||||
|
@ -1209,14 +1312,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):
|
||||
|
|
|
@ -551,6 +551,7 @@ FIELDS_TO_PGTYPES = {
|
|||
fields.datetime: 'timestamp',
|
||||
fields.binary: 'bytea',
|
||||
fields.many2one: 'int4',
|
||||
fields.serialized: 'text',
|
||||
}
|
||||
|
||||
def get_pg_type(f, type_override=None):
|
||||
|
@ -753,7 +754,11 @@ 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')
|
||||
|
||||
# sparse field should be created at the end, as it depends on its serialized field already existing
|
||||
model_fields = sorted(self._columns.items(), key=lambda x: 1 if x[1]._type == 'sparse' else 0)
|
||||
for (k, f) in model_fields:
|
||||
vals = {
|
||||
'model_id': model_id,
|
||||
'model': self._name,
|
||||
|
@ -768,7 +773,15 @@ 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 getattr(f, 'serialization_field', None):
|
||||
# resolve link to serialization_field if specified by name
|
||||
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'), _("Serialization field `%s` not found for sparse field `%s`!") % (f.serialization_field, k))
|
||||
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:
|
||||
|
@ -783,13 +796,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
|
||||
|
@ -806,12 +819,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()
|
||||
|
@ -1000,7 +1013,13 @@ class BaseModel(object):
|
|||
#'select': int(field['select_level'])
|
||||
}
|
||||
|
||||
if field['ttype'] == 'selection':
|
||||
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)
|
||||
elif field['ttype'] == 'selection':
|
||||
self._columns[field['name']] = fields.selection(eval(field['selection']), **attrs)
|
||||
elif field['ttype'] == 'reference':
|
||||
self._columns[field['name']] = fields.reference(selection=eval(field['selection']), **attrs)
|
||||
|
|
Loading…
Reference in New Issue