[MERGE] ir.model.fields: changes to custom fields now take effect immediately (courte...
authorOlivier Dony <odo@openerp.com>
Mon, 10 Jan 2011 12:33:02 +0000 (13:33 +0100)
committerOlivier Dony <odo@openerp.com>
Mon, 10 Jan 2011 12:33:02 +0000 (13:33 +0100)
lp bug: https://launchpad.net/bugs/631547 fixed

bzr revid: odo@openerp.com-20110110123302-weq6zt1ivshhgogq

1  2 
bin/addons/base/ir/ir_model.py

@@@ -196,23 -196,28 +196,28 @@@ class ir_model_fields(osv.osv)
      }
      _order = "name"
  
-     def _check_selection(self, cr, uid, ids, context=None):
-         selection_field = self.browse(cr, uid, ids[0], context=context)
+     def _check_selection(self, cr, uid, selection, context=None):
          try:
-             selection_list = eval(selection_field.selection)
+             selection_list = eval(selection)
          except Exception:
-             logging.getLogger('ir.model').error('Invalid selection list definition for fields.selection %s', selection_field.name , exc_info=True)
-             return False
+             logging.getLogger('ir.model').warning('Invalid selection list definition for fields.selection', exc_info=True)
 -            raise except_orm(_('Error'), 
++            raise except_orm(_('Error'),
+                     _("The Selection Options expression is not a valid Pythonic expression." \
+                       "Please provide an expression in the [('key','Label'), ...] format."))
+         check = True
          if not (isinstance(selection_list, list) and selection_list):
-             return False
-         for selection_item in selection_list:
-              if not (isinstance(selection_item, (tuple,list)) and len(selection_item) == 2):
-                 return False
-         return True
+             check = False
+         else:
+             for item in selection_list:
+                 if not (isinstance(item, (tuple,list)) and len(item) == 2):
+                     check = False
+                     break
  
-     _constraints = [
-         (_check_selection, "Wrong list of values specified for a field of type selection, it should be written as [('key','value')]", ['selection'])
-     ]
+         if not check:
 -                raise except_orm(_('Error'), 
++                raise except_orm(_('Error'),
+                     _("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
+         return True
  
      def _size_gt_zero_msg(self, cr, user, ids, context=None):
          return _('Size of the field can never be less than 1 !')
  
          return res
  
+     def write(self, cr, user, ids, vals, context=None):
+         if context is None:
+             context = {}
+         if context and context.get('manual',False):
+             vals['state'] = 'manual'
 -        have_custom_fields = False
+         column_rename = None # if set, *one* column can be renamed here
+         obj = None
 -        models = {}    # structs of (obj, [(field, prop, change_to),..])
 -                       # data to be updated on the orm model
++        models_patch = {}    # structs of (obj, [(field, prop, change_to),..])
++                             # data to be updated on the orm model
+         # static table of properties
+         model_props = [ # (our-name, fields.prop, set_fn)
+             ('field_description', 'string', lambda a: a),
+             ('required', 'required', bool),
+             ('readonly', 'readonly', bool),
+             ('domain', '_domain', lambda a: a),
+             ('size', 'size', int),
+             ('on_delete', 'ondelete', str),
+             ('translate', 'translate', bool),
+             ('view_load', 'view_load', bool),
+             ('selectable', 'selectable', bool),
+             ('select_level', 'select', int),
+             ('selection', 'selection', eval),
+             ]
+         if vals and ids:
+             checked_selection = False # need only check it once, so defer
 -            
++
+             for item in self.browse(cr, user, ids, context=context):
+                 if not (obj and obj._name == item.model):
+                     obj = self.pool.get(item.model)
 -                
 -                if item.state == 'manual':
 -                    have_custom_fields = True
 -                else:
 -                    for prop in ['name', 'model_id', 'ttype', 'relation', 'relation_field',
 -                        'size', 'selection', 'required', 'readonly', 'translate', 
 -                        'domain', 'groups']:
 -                            if prop in vals and getattr(item, prop) != vals[prop]:
 -                                raise except_orm(_('Error!'),
 -                                    _('Properties of base fields cannot be set here! '
 -                                      'Please modify them through Python code, '
 -                                      'preferably through a custom addon!'))
++
++                if item.state != 'manual':
++                    raise except_orm(_('Error!'),
++                        _('Properties of base fields cannot be altered in this manner! '
++                          'Please modify them through Python code, '
++                          'preferably through a custom addon!'))
+                 if item.ttype == 'selection' and 'selection' in vals \
+                         and not checked_selection:
+                     self._check_selection(cr, user, vals['selection'], context=context)
+                     checked_selection = True
+                 final_name = item.name
+                 if 'name' in vals and vals['name'] != item.name:
+                     # We need to rename the column
+                     if column_rename:
+                         raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
+                     if vals['name'] in obj._columns:
+                         raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
+                     if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
+                         raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
+                     if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
+                         raise ValueError('Invalid character in column name')
+                     column_rename = (obj, (obj._table, item.name, vals['name']))
+                     final_name = vals['name']
 -                
++
+                 if 'model_id' in vals and vals['model_id'] != item.model_id:
+                     raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
 -                
++
+                 if 'ttype' in vals and vals['ttype'] != item.ttype:
+                     raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
+                                 "Please drop it and create it again!"))
 -                
++
+                 # We don't check the 'state', because it might come from the context
+                 # (thus be set for multiple fields) and will be ignored anyway.
 -                
+                 if obj:
 -                    models.setdefault(obj._name, (obj,[]))
 -                    # find out which values (per model) we need to update there
 -                    for vname, fprop, set_fn in model_props:
 -                        if vname in vals:
 -                            prop_val = set_fn(vals[vname])
 -                            if getattr(obj._columns[item.name], fprop) != prop_val:
 -                                models[obj._name][1].append((final_name, fprop, prop_val))
++                    models_patch.setdefault(obj._name, (obj,[]))
++                    # find out which properties (per model) we need to update
++                    for field_name, field_property, set_fn in model_props:
++                        if field_name in vals:
++                            property_value = set_fn(vals[field_name])
++                            if getattr(obj._columns[item.name], field_property) != property_value:
++                                models_patch[obj._name][1].append((final_name, field_property, property_value))
+                         # our dict is ready here, but no properties are changed so far
+         # These shall never be written (modified)
 -        if 'model_id' in vals:
 -            del vals['model_id']
 -        if 'model' in vals:
 -            del vals['model']
 -        if 'state' in vals:
 -            del vals['state']
 -        
++        for column_name in ('model_id', 'model', 'state'):
++            if column_name in vals:
++                del vals[column_name]
++
+         res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
+         if column_rename:
+             cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
+             # This is VERY risky, but let us have this feature:
+             # we want to change the key of column in obj._columns dict
+             col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
+             column_rename[0]._columns[column_rename[1][2]] = col
 -            
 -        if models:
 -            # We have to update _columns of the model(s) and then call their 
++
++        if models_patch:
++            # We have to update _columns of the model(s) and then call their
+             # _auto_init to sync the db with the model. Hopefully, since write()
+             # was called earlier, they will be in-sync before the _auto_init.
+             # Anything we don't update in _columns now will be reset from
+             # the model into ir.model.fields (db).
+             ctx = context.copy()
 -            if have_custom_fields:
 -                ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
 -            
 -            for mkey, mstruct in models.items():
 -                obj = mstruct[0]
 -                
 -                for col_name, col_prop, val in mstruct[1]:
++            ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
++
++            for model_key, patch_struct in models_patch.items():
++                obj = patch_struct[0]
++                for col_name, col_prop, val in patch_struct[1]:
+                     setattr(obj._columns[col_name], col_prop, val)
+                 obj._auto_init(cr, ctx)
+         return res
  ir_model_fields()
  
  class ir_model_access(osv.osv):