[FIX] fields: in to_column(), returning self.column is generally not correct

Consider the following example:

    class Foo(models.Model):
        _name = 'foo'
        _columns = {
            'state': fields.selection([('a', 'A')]),
        }

    class Bar(models.Model):
        _inherit = 'foo'
        state = fields.Selection(selection_add=[('b', 'B')])

The attribute 'column' of the field does not have the full selection list,
therefore the column object cannot not be reused, even a copy of it.  The
solution is to systematically recreate the column from the field's final
specification, except for function fields that have no sensible way for being
recreated.
This commit is contained in:
Raphael Collet 2014-10-08 10:47:25 +02:00
parent 10142d7dc7
commit 619a844428
6 changed files with 55 additions and 42 deletions

View File

@ -8,7 +8,10 @@
'maintainer': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['base'],
'data': ['ir.model.access.csv'],
'data': [
'ir.model.access.csv',
'demo_data.xml',
],
'installable': True,
'auto_install': False,
}

View File

@ -0,0 +1,23 @@
<openerp>
<data>
<record id="mother_a" model="test.inherit.mother">
<field name="name">Mother A</field>
<field name="state">a</field>
</record>
<record id="mother_b" model="test.inherit.mother">
<field name="name">Mother B</field>
<field name="state">b</field>
</record>
<record id="mother_c" model="test.inherit.mother">
<field name="name">Mother C</field>
<field name="state">c</field>
</record>
<record id="mother_d" model="test.inherit.mother">
<field name="name">Mother D</field>
<field name="state">d</field>
</record>
</data>
</openerp>

View File

@ -8,10 +8,10 @@ class mother(models.Model):
_columns = {
# check interoperability of field inheritance with old-style fields
'name': osv.fields.char('Name', required=True),
'state': osv.fields.selection([('a', 'A'), ('b', 'B')], string='State'),
}
surname = fields.Char(compute='_compute_surname')
state = fields.Selection([('a', 'A'), ('b', 'B')])
@api.one
@api.depends('name')

View File

@ -43,10 +43,12 @@ class test_inherits(common.TransactionCase):
def test_selection_extension(self):
""" check that attribute selection_add=... extends selection on fields. """
mother = self.env['test.inherit.mother']
field = mother._fields['state']
# the extra values are added
self.assertEqual(field.selection, [('a', 'A'), ('b', 'B'), ('c', 'C'), ('d', 'D')])
# the extra values are added, both in the field and the column
self.assertEqual(mother._fields['state'].selection,
[('a', 'A'), ('b', 'B'), ('c', 'C'), ('d', 'D')])
self.assertEqual(mother._columns['state'].selection,
[('a', 'A'), ('b', 'B'), ('c', 'C'), ('d', 'D')])
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -592,11 +592,9 @@ class Field(object):
def to_column(self):
""" return a low-level field object corresponding to `self` """
assert self.store
if self.column:
# some columns are registry-dependent, like float fields (digits);
# duplicate them to avoid sharing between registries
return copy(self.column)
# some columns are registry-dependent, like float fields (digits);
# duplicate them to avoid sharing between registries
_logger.debug("Create fields._column for Field %s", self)
args = {}
for attr, prop in self.column_attrs:
@ -610,6 +608,11 @@ class Field(object):
args['relation'] = self.comodel_name
return fields.property(**args)
if isinstance(self.column, fields.function):
# it is too tricky to recreate a function field, so for that case,
# we make a stupid (and possibly incorrect) copy of the column
return copy(self.column)
return getattr(fields, self.type)(**args)
# properties used by to_column() to create a column instance
@ -1182,12 +1185,11 @@ class Selection(Field):
field = self.related_field
self.selection = lambda model: field._description_selection(model.env)
def _setup_regular(self, env):
super(Selection, self)._setup_regular(env)
# determine selection (applying extensions)
cls = type(env[self.model_name])
def set_class_name(self, cls, name):
super(Selection, self).set_class_name(cls, name)
# determine selection (applying 'selection_add' extensions)
selection = None
for field in resolve_all_mro(cls, self.name, reverse=True):
for field in resolve_all_mro(cls, name, reverse=True):
if isinstance(field, type(self)):
# We cannot use field.selection or field.selection_add here
# because those attributes are overridden by `set_class_name`.
@ -1348,13 +1350,11 @@ class Many2one(_Relational):
def __init__(self, comodel_name=None, string=None, **kwargs):
super(Many2one, self).__init__(comodel_name=comodel_name, string=string, **kwargs)
def _setup_regular(self, env):
super(Many2one, self)._setup_regular(env)
# self.inverse_fields is populated by the corresponding One2many field
def set_class_name(self, cls, name):
super(Many2one, self).set_class_name(cls, name)
# determine self.delegate
self.delegate = self.name in env[self.model_name]._inherits.values()
if not self.delegate:
self.delegate = name in cls._inherits.values()
_column_ondelete = property(attrgetter('ondelete'))
_column_auto_join = property(attrgetter('auto_join'))

View File

@ -2271,28 +2271,13 @@ class BaseModel(object):
if val is not False:
cr.execute(update_query, (ss[1](val), key))
def _check_selection_field_value(self, cr, uid, field, value, context=None):
"""Raise except_orm if value is not among the valid values for the selection field"""
if self._columns[field]._type == 'reference':
val_model, val_id_str = value.split(',', 1)
val_id = False
try:
val_id = long(val_id_str)
except ValueError:
pass
if not val_id:
raise except_orm(_('ValidateError'),
_('Invalid value for reference field "%s.%s" (last part must be a non-zero integer): "%s"') % (self._table, field, value))
val = val_model
else:
val = value
if isinstance(self._columns[field].selection, (tuple, list)):
if val in dict(self._columns[field].selection):
return
elif val in dict(self._columns[field].selection(self, cr, uid, context=context)):
return
raise except_orm(_('ValidateError'),
_('The value "%s" for the field "%s.%s" is not in the selection') % (value, self._name, field))
@api.model
def _check_selection_field_value(self, field, value):
""" Check whether value is among the valid values for the given
selection/reference field, and raise an exception if not.
"""
field = self._fields[field]
field.convert_to_cache(value, self)
def _check_removed_columns(self, cr, log=False):
# iterate on the database columns to drop the NOT NULL constraints