[IMP] use model._fields instead of model._all_columns to cover all fields
authorRaphael Collet <rco@openerp.com>
Mon, 3 Nov 2014 15:00:50 +0000 (16:00 +0100)
committerRaphael Collet <rco@openerp.com>
Tue, 4 Nov 2014 12:47:57 +0000 (13:47 +0100)
The old-api model._all_columns contains information about model._columns and
inherited columns.  This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...

This commit contains the following changes:

 - adapt several methods of BaseModel to use fields instead of columns and
   _all_columns

 - copy all semantic-free attributes of related fields from their source

 - add attribute 'group_operator' on integer and float fields

 - base, base_action_rule, crm, edi, hr, mail, mass_mailing, pad,
   payment_acquirer, share, website, website_crm, website_mail: simply use
   _fields instead of _all_columns

 - base, decimal_precision, website: adapt qweb rendering methods to use fields
   instead of columns

41 files changed:
addons/base_action_rule/base_action_rule.py
addons/crm/crm.py
addons/crm/crm_lead.py
addons/decimal_precision/decimal_precision.py
addons/decimal_precision/tests/test_qweb_float.py
addons/edi/models/edi.py
addons/hr/hr.py
addons/mail/mail_mail.py
addons/mail/mail_thread.py
addons/mail/tests/test_mail_features.py
addons/mail/update.py
addons/mass_mailing/controllers/main.py
addons/mass_mailing/models/mail_thread.py
addons/mass_mailing/models/mass_mailing.py
addons/pad/pad.py
addons/payment/models/payment_acquirer.py
addons/share/wizard/share_wizard.py
addons/website/controllers/main.py
addons/website/models/ir_qweb.py
addons/website/models/ir_ui_view.py
addons/website/models/website.py
addons/website/tests/test_converter.py
addons/website_crm/controllers/main.py
addons/website_mail/controllers/email_designer.py
openerp/addons/base/ir/ir_actions.py
openerp/addons/base/ir/ir_fields.py
openerp/addons/base/ir/ir_qweb.py
openerp/addons/base/ir/ir_translation.py
openerp/addons/base/ir/ir_ui_view.py
openerp/addons/base/ir/ir_values.py
openerp/addons/base/res/res_partner.py
openerp/addons/base/res/res_users.py
openerp/addons/base/tests/base_test.yml
openerp/addons/base/tests/test_api.py
openerp/addons/test_converter/tests/test_html.py
openerp/fields.py
openerp/models.py
openerp/osv/expression.py
openerp/osv/fields.py
openerp/tools/convert.py
openerp/tools/yaml_import.py

index ac95da9..2bf83f9 100644 (file)
@@ -131,9 +131,9 @@ class base_action_rule(osv.osv):
 
         # modify records
         values = {}
-        if 'date_action_last' in model._all_columns:
+        if 'date_action_last' in model._fields:
             values['date_action_last'] = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
-        if action.act_user_id and 'user_id' in model._all_columns:
+        if action.act_user_id and 'user_id' in model._fields:
             values['user_id'] = action.act_user_id.id
         if values:
             model.write(cr, uid, record_ids, values, context=context)
@@ -320,7 +320,7 @@ class base_action_rule(osv.osv):
 
             # determine when action should occur for the records
             date_field = action.trg_date_id.name
-            if date_field == 'date_action_last' and 'create_date' in model._all_columns:
+            if date_field == 'date_action_last' and 'create_date' in model._fields:
                 get_record_dt = lambda record: record[date_field] or record.create_date
             else:
                 get_record_dt = lambda record: record[date_field]
index f694f52..983ae1b 100644 (file)
@@ -80,21 +80,22 @@ class crm_tracking_mixin(osv.AbstractModel):
         return [('utm_campaign', 'campaign_id'), ('utm_source', 'source_id'), ('utm_medium', 'medium_id')]
 
     def tracking_get_values(self, cr, uid, vals, context=None):
-        for key, field in self.tracking_fields():
-            column = self._all_columns[field].column
-            value = vals.get(field) or (request and request.httprequest.cookies.get(key))  # params.get should be always in session by the dispatch from ir_http
-            if column._type in ['many2one'] and isinstance(value, basestring):  # if we receive a string for a many2one, we search / create the id
+        for key, fname in self.tracking_fields():
+            field = self._fields[fname]
+            value = vals.get(fname) or (request and request.httprequest.cookies.get(key))  # params.get should be always in session by the dispatch from ir_http
+            if field.type == 'many2one' and isinstance(value, basestring):
+                # if we receive a string for a many2one, we search/create the id
                 if value:
-                    Model = self.pool[column._obj]
+                    Model = self.pool[field.comodel_name]
                     rel_id = Model.name_search(cr, uid, value, context=context)
                     if rel_id:
                         rel_id = rel_id[0][0]
                     else:
                         rel_id = Model.create(cr, uid, {'name': value}, context=context)
-                vals[field] = rel_id
-            # Here the code for others cases that many2one
+                vals[fname] = rel_id
             else:
-                vals[field] = value
+                # Here the code for others cases that many2one
+                vals[fname] = value
         return vals
 
     def _get_default_track(self, cr, uid, field, context=None):
index c5aa47f..866680e 100644 (file)
@@ -485,15 +485,14 @@ class crm_lead(format_address, osv.osv):
         # Process the fields' values
         data = {}
         for field_name in fields:
-            field_info = self._all_columns.get(field_name)
-            if field_info is None:
+            field = self._fields.get(field_name)
+            if field is None:
                 continue
-            field = field_info.column
-            if field._type in ('many2many', 'one2many'):
+            if field.type in ('many2many', 'one2many'):
                 continue
-            elif field._type == 'many2one':
+            elif field.type == 'many2one':
                 data[field_name] = _get_first_not_null_id(field_name)  # !!
-            elif field._type == 'text':
+            elif field.type == 'text':
                 data[field_name] = _concat_all(field_name)  #not lost
             else:
                 data[field_name] = _get_first_not_null(field_name)  #not lost
@@ -508,22 +507,21 @@ class crm_lead(format_address, osv.osv):
             body.append("%s\n" % (title))
 
         for field_name in fields:
-            field_info = self._all_columns.get(field_name)
-            if field_info is None:
+            field = self._fields.get(field_name)
+            if field is None:
                 continue
-            field = field_info.column
             value = ''
 
-            if field._type == 'selection':
-                if hasattr(field.selection, '__call__'):
+            if field.type == 'selection':
+                if callable(field.selection):
                     key = field.selection(self, cr, uid, context=context)
                 else:
                     key = field.selection
                 value = dict(key).get(lead[field_name], lead[field_name])
-            elif field._type == 'many2one':
+            elif field.type == 'many2one':
                 if lead[field_name]:
                     value = lead[field_name].name_get()[0][1]
-            elif field._type == 'many2many':
+            elif field.type == 'many2many':
                 if lead[field_name]:
                     for val in lead[field_name]:
                         field_value = val.name_get()[0][1]
index f06e88d..945825e 100644 (file)
@@ -85,14 +85,14 @@ class DecimalPrecisionFloat(orm.AbstractModel):
     _inherit = 'ir.qweb.field.float'
 
 
-    def precision(self, cr, uid, column, options=None, context=None):
+    def precision(self, cr, uid, field, options=None, context=None):
         dp = options and options.get('decimal_precision')
         if dp:
             return self.pool['decimal.precision'].precision_get(
                 cr, uid, dp)
 
         return super(DecimalPrecisionFloat, self).precision(
-            cr, uid, column, options=options, context=context)
+            cr, uid, field, options=options, context=context)
 
 class DecimalPrecisionTestModel(orm.Model):
     _name = 'decimal.precision.test'
index 9333417..c481dba 100644 (file)
@@ -8,10 +8,10 @@ class TestFloatExport(common.TransactionCase):
 
     def get_converter(self, name):
         converter = self.registry('ir.qweb.field.float')
-        column = self.Model._all_columns[name].column
+        field = self.Model._fields[name]
 
         return lambda value, options=None: converter.value_to_html(
-            self.cr, self.uid, value, column, options=options, context=None)
+            self.cr, self.uid, value, field, options=options, context=None)
 
     def test_basic_float(self):
         converter = self.get_converter('float')
index f2ca15b..fea26cd 100644 (file)
@@ -384,18 +384,18 @@ class EDIMixin(object):
         results = []
         for record in records:
             edi_dict = self.edi_metadata(cr, uid, [record], context=context)[0]
-            for field in fields_to_export:
-                column = self._all_columns[field].column
-                value = getattr(record, field)
+            for field_name in fields_to_export:
+                field = self._fields[field_name]
+                value = getattr(record, field_name)
                 if not value and value not in ('', 0):
                     continue
-                elif column._type == 'many2one':
+                elif field.type == 'many2one':
                     value = self.edi_m2o(cr, uid, value, context=context)
-                elif column._type == 'many2many':
+                elif field.type == 'many2many':
                     value = self.edi_m2m(cr, uid, value, context=context)
-                elif column._type == 'one2many':
-                    value = self.edi_o2m(cr, uid, value, edi_struct=edi_struct.get(field, {}), context=context)
-                edi_dict[field] = value
+                elif field.type == 'one2many':
+                    value = self.edi_o2m(cr, uid, value, edi_struct=edi_struct.get(field_name, {}), context=context)
+                edi_dict[field_name] = value
             results.append(edi_dict)
         return results
 
@@ -558,25 +558,24 @@ class EDIMixin(object):
             # skip metadata and empty fields
             if field_name.startswith('__') or field_value is None or field_value is False:
                 continue
-            field_info = self._all_columns.get(field_name)
-            if not field_info:
+            field = self._fields.get(field_name)
+            if not field:
                 _logger.warning('Ignoring unknown field `%s` when importing `%s` EDI document.', field_name, self._name)
                 continue
-            field = field_info.column
             # skip function/related fields
-            if isinstance(field, fields.function):
+            if not field.store:
                 _logger.warning("Unexpected function field value is found in '%s' EDI document: '%s'." % (self._name, field_name))
                 continue
-            relation_model = field._obj
-            if field._type == 'many2one':
+            relation_model = field.comodel_name
+            if field.type == 'many2one':
                 record_values[field_name] = self.edi_import_relation(cr, uid, relation_model,
                                                                       field_value[1], field_value[0],
                                                                       context=context)
-            elif field._type == 'many2many':
+            elif field.type == 'many2many':
                 record_values[field_name] = [self.edi_import_relation(cr, uid, relation_model, m2m_value[1],
                                                                        m2m_value[0], context=context)
                                              for m2m_value in field_value]
-            elif field._type == 'one2many':
+            elif field.type == 'one2many':
                 # must wait until parent report is imported, as the parent relationship
                 # is often required in o2m child records
                 o2m_todo[field_name] = field_value
@@ -591,11 +590,12 @@ class EDIMixin(object):
 
         # process o2m values, connecting them to their parent on-the-fly
         for o2m_field, o2m_value in o2m_todo.iteritems():
-            field = self._all_columns[o2m_field].column
-            dest_model = self.pool[field._obj]
+            field = self._fields[o2m_field]
+            dest_model = self.pool[field.comodel_name]
+            dest_field = field.inverse_name
             for o2m_line in o2m_value:
                 # link to parent record: expects an (ext_id, name) pair
-                o2m_line[field._fields_id] = (ext_id_members['full'], record_display[1])
+                o2m_line[dest_field] = (ext_id_members['full'], record_display[1])
                 dest_model.edi_import(cr, uid, o2m_line, context=context)
 
         # process the attachments, if any
index efec3e6..7e433e9 100644 (file)
@@ -347,8 +347,8 @@ class hr_employee(osv.osv):
         if auto_follow_fields is None:
             auto_follow_fields = ['user_id']
         user_field_lst = []
-        for name, column_info in self._all_columns.items():
-            if name in auto_follow_fields and name in updated_fields and column_info.column._obj == 'res.users':
+        for name, field in self._fields.items():
+            if name in auto_follow_fields and name in updated_fields and field.comodel_name == 'res.users':
                 user_field_lst.append(name)
         return user_field_lst
 
index f474a07..d849b4b 100644 (file)
@@ -73,7 +73,7 @@ class mail_mail(osv.Model):
     def default_get(self, cr, uid, fields, context=None):
         # protection for `default_type` values leaking from menu action context (e.g. for invoices)
         # To remove when automatic context propagation is removed in web client
-        if context and context.get('default_type') and context.get('default_type') not in self._all_columns['type'].column.selection:
+        if context and context.get('default_type') and context.get('default_type') not in self._fields['type'].selection:
             context = dict(context, default_type=None)
         return super(mail_mail, self).default_get(cr, uid, fields, context=context)
 
index 4356ee6..fa4d7cf 100644 (file)
@@ -460,12 +460,12 @@ class mail_thread(osv.AbstractModel):
     def _get_tracked_fields(self, cr, uid, updated_fields, context=None):
         """ Return a structure of tracked fields for the current model.
             :param list updated_fields: modified field names
-            :return list: a list of (field_name, column_info obj), containing
+            :return dict: a dict mapping field name to description, containing
                 always tracked fields and modified on_change fields
         """
         tracked_fields = []
-        for name, column_info in self._all_columns.items():
-            visibility = getattr(column_info.column, 'track_visibility', False)
+        for name, field in self._fields.items():
+            visibility = getattr(field, 'track_visibility', False)
             if visibility == 'always' or (visibility == 'onchange' and name in updated_fields) or name in self._track:
                 tracked_fields.append(name)
 
@@ -507,17 +507,22 @@ class mail_thread(osv.AbstractModel):
 
             # generate tracked_values data structure: {'col_name': {col_info, new_value, old_value}}
             for col_name, col_info in tracked_fields.items():
+                field = self._fields[col_name]
                 initial_value = initial[col_name]
                 record_value = getattr(browse_record, col_name)
 
-                if record_value == initial_value and getattr(self._all_columns[col_name].column, 'track_visibility', None) == 'always':
-                    tracked_values[col_name] = dict(col_info=col_info['string'],
-                                                        new_value=convert_for_display(record_value, col_info))
+                if record_value == initial_value and getattr(field, 'track_visibility', None) == 'always':
+                    tracked_values[col_name] = dict(
+                        col_info=col_info['string'],
+                        new_value=convert_for_display(record_value, col_info),
+                    )
                 elif record_value != initial_value and (record_value or initial_value):  # because browse null != False
-                    if getattr(self._all_columns[col_name].column, 'track_visibility', None) in ['always', 'onchange']:
-                        tracked_values[col_name] = dict(col_info=col_info['string'],
-                                                            old_value=convert_for_display(initial_value, col_info),
-                                                            new_value=convert_for_display(record_value, col_info))
+                    if getattr(field, 'track_visibility', None) in ['always', 'onchange']:
+                        tracked_values[col_name] = dict(
+                            col_info=col_info['string'],
+                            old_value=convert_for_display(initial_value, col_info),
+                            new_value=convert_for_display(record_value, col_info),
+                        )
                     if col_name in tracked_fields:
                         changes.add(col_name)
             if not changes:
@@ -681,11 +686,11 @@ class mail_thread(osv.AbstractModel):
         res = {}
         for record in self.browse(cr, SUPERUSER_ID, ids, context=context):
             recipient_ids, email_to, email_cc = set(), False, False
-            if 'partner_id' in self._all_columns and record.partner_id:
+            if 'partner_id' in self._fields and record.partner_id:
                 recipient_ids.add(record.partner_id.id)
-            elif 'email_from' in self._all_columns and record.email_from:
+            elif 'email_from' in self._fields and record.email_from:
                 email_to = record.email_from
-            elif 'email' in self._all_columns:
+            elif 'email' in self._fields:
                 email_to = record.email
             res[record.id] = {'partner_ids': list(recipient_ids), 'email_to': email_to, 'email_cc': email_cc}
         return res
@@ -1398,11 +1403,11 @@ class mail_thread(osv.AbstractModel):
         """ Returns suggested recipients for ids. Those are a list of
             tuple (partner_id, partner_name, reason), to be managed by Chatter. """
         result = dict((res_id, []) for res_id in ids)
-        if self._all_columns.get('user_id'):
+        if 'user_id' in self._fields:
             for obj in self.browse(cr, SUPERUSER_ID, ids, context=context):  # SUPERUSER because of a read on res.users that would crash otherwise
                 if not obj.user_id or not obj.user_id.partner_id:
                     continue
-                self._message_add_suggested_recipient(cr, uid, result, obj, partner=obj.user_id.partner_id, reason=self._all_columns['user_id'].column.string, context=context)
+                self._message_add_suggested_recipient(cr, uid, result, obj, partner=obj.user_id.partner_id, reason=self._fields['user_id'].string, context=context)
         return result
 
     def _find_partner_from_emails(self, cr, uid, id, emails, model=None, context=None, check_followers=True):
@@ -1775,8 +1780,8 @@ class mail_thread(osv.AbstractModel):
         if auto_follow_fields is None:
             auto_follow_fields = ['user_id']
         user_field_lst = []
-        for name, column_info in self._all_columns.items():
-            if name in auto_follow_fields and name in updated_fields and getattr(column_info.column, 'track_visibility', False) and column_info.column._obj == 'res.users':
+        for name, field in self._fields.items():
+            if name in auto_follow_fields and name in updated_fields and getattr(field, 'track_visibility', False) and field.comodel_name == 'res.users':
                 user_field_lst.append(name)
         return user_field_lst
 
@@ -1916,7 +1921,7 @@ class mail_thread(osv.AbstractModel):
 
         # TDE HACK: originally by MAT from portal/mail_mail.py but not working until the inheritance graph bug is not solved in trunk
         # TDE FIXME: relocate in portal when it won't be necessary to reload the hr.employee model in an additional bridge module
-        if self.pool['res.groups']._all_columns.get('is_portal'):
+        if 'is_portal' in self.pool['res.groups']._fields:
             user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context)
             if any(group.is_portal for group in user.groups_id):
                 return []
index 8726cf3..44b573f 100644 (file)
@@ -827,7 +827,9 @@ class test_mail(TestMail):
         self.ir_model_data.create(cr, uid, {'name': 'mt_group_public', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_group_public_id})
 
         # Data: alter mail_group model for testing purposes (test on classic, selection and many2one fields)
-        self.mail_group._track = {
+        cls = type(self.mail_group)
+        self.assertNotIn('_track', cls.__dict__)
+        cls._track = {
             'public': {
                 'mail.mt_private': lambda self, cr, uid, obj, ctx=None: obj.public == 'private',
             },
@@ -839,12 +841,16 @@ class test_mail(TestMail):
                 'mail.mt_group_public': lambda self, cr, uid, obj, ctx=None: True,
             },
         }
-        public_col = self.mail_group._columns.get('public')
-        name_col = self.mail_group._columns.get('name')
-        group_public_col = self.mail_group._columns.get('group_public_id')
-        public_col.track_visibility = 'onchange'
-        name_col.track_visibility = 'always'
-        group_public_col.track_visibility = 'onchange'
+        visibility = {'public': 'onchange', 'name': 'always', 'group_public_id': 'onchange'}
+        for key in visibility:
+            self.assertFalse(hasattr(getattr(cls, key), 'track_visibility'))
+            getattr(cls, key).track_visibility = visibility[key]
+
+        @self.addCleanup
+        def cleanup():
+            delattr(cls, '_track')
+            for key in visibility:
+                del getattr(cls, key).track_visibility
 
         # Test: change name -> always tracked, not related to a subtype
         self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'public': 'public'})
@@ -903,9 +909,3 @@ class test_mail(TestMail):
         self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'description': 'Dummy'})
         self.group_pigs.refresh()
         self.assertEqual(len(self.group_pigs.message_ids), 6, 'tracked: No message should have been produced')
-
-        # Data: removed changes
-        public_col.track_visibility = None
-        name_col.track_visibility = None
-        group_public_col.track_visibility = None
-        self.mail_group._track = {}
index 9096473..5644c45 100644 (file)
@@ -32,7 +32,7 @@ class publisher_warranty_contract(AbstractModel):
         nbr_active_users = user_count([("login_date", ">=", limit_date_str)])
         nbr_share_users = 0
         nbr_active_share_users = 0
-        if "share" in Users._all_columns:
+        if "share" in Users._fields:
             nbr_share_users = user_count([("share", "=", True)])
             nbr_active_share_users = user_count([("share", "=", True), ("login_date", ">=", limit_date_str)])
         user = Users.browse(cr, uid, uid)
index 2fcc6af..69317cb 100644 (file)
@@ -31,14 +31,15 @@ class MassMailController(http.Controller):
             request.registry[mailing.mailing_model].write(cr, SUPERUSER_ID, record_ids, {'opt_out': True}, context=context)
         else:
             email_fname = None
-            if 'email_from' in request.registry[mailing.mailing_model]._all_columns:
+            model = request.registry[mailing.mailing_model]
+            if 'email_from' in model._fields:
                 email_fname = 'email_from'
-            elif 'email' in request.registry[mailing.mailing_model]._all_columns:
+            elif 'email' in model._fields:
                 email_fname = 'email'
             if email_fname:
-                record_ids = request.registry[mailing.mailing_model].search(cr, SUPERUSER_ID, [('id', '=', res_id), (email_fname, 'ilike', email)], context=context)
-            if 'opt_out' in request.registry[mailing.mailing_model]._all_columns:
-                request.registry[mailing.mailing_model].write(cr, SUPERUSER_ID, record_ids, {'opt_out': True}, context=context)
+                record_ids = model.search(cr, SUPERUSER_ID, [('id', '=', res_id), (email_fname, 'ilike', email)], context=context)
+            if 'opt_out' in model._fields:
+                model.write(cr, SUPERUSER_ID, record_ids, {'opt_out': True}, context=context)
         return 'OK'
 
     @http.route(['/website_mass_mailing/is_subscriber'], type='json', auth="public", website=True)
index 7bfce70..e473252 100644 (file)
@@ -77,7 +77,7 @@ class MailThread(osv.AbstractModel):
         Mail Returned to Sender) is received for an existing thread. The default
         behavior is to check is an integer  ``message_bounce`` column exists.
         If it is the case, its content is incremented. """
-        if self._all_columns.get('message_bounce'):
+        if 'message_bounce' in self._fields:
             for obj in self.browse(cr, uid, ids, context=context):
                 self.write(cr, uid, [obj.id], {'message_bounce': obj.message_bounce + 1}, context=context)
 
index bee3e85..8aed21d 100644 (file)
@@ -276,8 +276,8 @@ class MassMailing(osv.Model):
                            'tooltip': ustr((date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y')),
                            } for i in range(0, self._period_number)]
         group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
-        field_col_info = obj._all_columns.get(groupby_field.split(':')[0])
-        pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field_col_info.column._type == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
+        field = obj._fields.get(groupby_field.split(':')[0])
+        pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field.type == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
         for group in group_obj:
             group_begin_date = datetime.strptime(group['__domain'][0][2], pattern).date()
             timedelta = relativedelta.relativedelta(group_begin_date, date_begin)
index 68b19c3..3b84c79 100644 (file)
@@ -52,8 +52,8 @@ class pad_common(osv.osv_memory):
 
             #get attr on the field model
             model = self.pool[context["model"]]
-            field = model._all_columns[context['field_name']]
-            real_field = field.column.pad_content_field
+            field = model._fields[context['field_name']]
+            real_field = field.pad_content_field
 
             #get content of the real field
             for record in model.browse(cr, uid, [context["object_id"]]):
@@ -94,15 +94,14 @@ class pad_common(osv.osv_memory):
     # Set the pad content in vals
     def _set_pad_value(self, cr, uid, vals, context=None):
         for k,v in vals.items():
-            field = self._all_columns[k].column
+            field = self._fields[k]
             if hasattr(field,'pad_content_field'):
                 vals[field.pad_content_field] = self.pad_get_content(cr, uid, v, context=context)        
 
     def copy(self, cr, uid, id, default=None, context=None):
         if not default:
             default = {}
-        for k, v in self._all_columns.iteritems():
-            field = v.column
+        for k, field in self._fields.iteritems():
             if hasattr(field,'pad_content_field'):
                 pad = self.pad_generate_url(cr, uid, context)
                 default[k] = pad.get('url')
index d060c8f..aee8911 100644 (file)
@@ -94,7 +94,7 @@ class PaymentAcquirer(osv.Model):
         """ If the field has 'required_if_provider="<provider>"' attribute, then it
         required if record.provider is <provider>. """
         for acquirer in self.browse(cr, uid, ids, context=context):
-            if any(c for c, f in self._all_columns.items() if getattr(f.column, 'required_if_provider', None) == acquirer.provider and not acquirer[c]):
+            if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in self._fields.items()):
                 return False
         return True
 
index e4c5044..6d3ba0e 100644 (file)
@@ -384,38 +384,37 @@ class share_wizard(osv.TransientModel):
         models = [x[1].model for x in relation_fields]
         model_obj = self.pool.get('ir.model')
         model_osv = self.pool[model.model]
-        for colinfo in model_osv._all_columns.itervalues():
-            coldef = colinfo.column
-            coltype = coldef._type
+        for field in model_osv._fields.itervalues():
+            ftype = field.type
             relation_field = None
-            if coltype in ttypes and colinfo.column._obj not in models:
-                relation_model_id = model_obj.search(cr, UID_ROOT, [('model','=',coldef._obj)])[0]
+            if ftype in ttypes and field.comodel_name not in models:
+                relation_model_id = model_obj.search(cr, UID_ROOT, [('model','=',field.comodel_name)])[0]
                 relation_model_browse = model_obj.browse(cr, UID_ROOT, relation_model_id, context=context)
-                relation_osv = self.pool[coldef._obj]
+                relation_osv = self.pool[field.comodel_name]
                 #skip virtual one2many fields (related, ...) as there is no reverse relationship
-                if coltype == 'one2many' and hasattr(coldef, '_fields_id'):
+                if ftype == 'one2many' and field.inverse_name:
                     # don't record reverse path if it's not a real m2o (that happens, but rarely)
-                    dest_model_ci = relation_osv._all_columns
-                    reverse_rel = coldef._fields_id
-                    if reverse_rel in dest_model_ci and dest_model_ci[reverse_rel].column._type == 'many2one':
+                    dest_fields = relation_osv._fields
+                    reverse_rel = field.inverse_name
+                    if reverse_rel in dest_fields and dest_fields[reverse_rel].type == 'many2one':
                         relation_field = ('%s.%s'%(reverse_rel, suffix)) if suffix else reverse_rel
                 local_rel_fields.append((relation_field, relation_model_browse))
                 for parent in relation_osv._inherits:
                     if parent not in models:
                         parent_model = self.pool[parent]
-                        parent_colinfos = parent_model._all_columns
+                        parent_fields = parent_model._fields
                         parent_model_browse = model_obj.browse(cr, UID_ROOT,
                                                                model_obj.search(cr, UID_ROOT, [('model','=',parent)]))[0]
-                        if relation_field and coldef._fields_id in parent_colinfos:
+                        if relation_field and field.inverse_name in parent_fields:
                             # inverse relationship is available in the parent
                             local_rel_fields.append((relation_field, parent_model_browse))
                         else:
                             # TODO: can we setup a proper rule to restrict inherited models
                             # in case the parent does not contain the reverse m2o?
                             local_rel_fields.append((None, parent_model_browse))
-                if relation_model_id != model.id and coltype in ['one2many', 'many2many']:
+                if relation_model_id != model.id and ftype in ['one2many', 'many2many']:
                     local_rel_fields += self._get_recursive_relations(cr, uid, relation_model_browse,
-                        [coltype], relation_fields + local_rel_fields, suffix=relation_field, context=context)
+                        [ftype], relation_fields + local_rel_fields, suffix=relation_field, context=context)
         return local_rel_fields
 
     def _get_relationship_classes(self, cr, uid, model, context=None):
index c808141..5c808c4 100644 (file)
@@ -367,7 +367,7 @@ class Website(openerp.addons.web.controllers.main.Home):
         obj = _object.browse(request.cr, request.uid, _id)
 
         values = {}
-        if 'website_published' in _object._all_columns:
+        if 'website_published' in _object._fields:
             values['website_published'] = not obj.website_published
         _object.write(request.cr, request.uid, [_id],
                       values, context=request.context)
index f0a75dc..3555b33 100644 (file)
@@ -76,12 +76,12 @@ class Field(orm.AbstractModel):
     def attributes(self, cr, uid, field_name, record, options,
                    source_element, g_att, t_att, qweb_context, context=None):
         if options is None: options = {}
-        column = record._model._all_columns[field_name].column
-        attrs = [('data-oe-translate', 1 if column.translate else 0)]
+        field = record._model._fields[field_name]
+        attrs = [('data-oe-translate', 1 if getattr(field, 'translate', False) else 0)]
 
         placeholder = options.get('placeholder') \
                    or source_element.get('placeholder') \
-                   or getattr(column, 'placeholder', None)
+                   or getattr(field, 'placeholder', None)
         if placeholder:
             attrs.append(('placeholder', placeholder))
 
@@ -95,7 +95,7 @@ class Field(orm.AbstractModel):
     def value_from_string(self, value):
         return value
 
-    def from_html(self, cr, uid, model, column, element, context=None):
+    def from_html(self, cr, uid, model, field, element, context=None):
         return self.value_from_string(element.text_content().strip())
 
     def qweb_object(self):
@@ -111,7 +111,7 @@ class Float(orm.AbstractModel):
     _name = 'website.qweb.field.float'
     _inherit = ['website.qweb.field', 'ir.qweb.field.float']
 
-    def from_html(self, cr, uid, model, column, element, context=None):
+    def from_html(self, cr, uid, model, field, element, context=None):
         lang = self.user_lang(cr, uid, context=context)
 
         value = element.text_content().strip()
@@ -142,7 +142,7 @@ class Date(orm.AbstractModel):
             qweb_context, context=None)
         return itertools.chain(attrs, [('data-oe-original', record[field_name])])
 
-    def from_html(self, cr, uid, model, column, element, context=None):
+    def from_html(self, cr, uid, model, field, element, context=None):
         value = element.text_content().strip()
         if not value: return False
 
@@ -173,7 +173,7 @@ class DateTime(orm.AbstractModel):
             ('data-oe-original', value)
         ])
 
-    def from_html(self, cr, uid, model, column, element, context=None):
+    def from_html(self, cr, uid, model, field, element, context=None):
         if context is None: context = {}
         value = element.text_content().strip()
         if not value: return False
@@ -204,16 +204,17 @@ class Text(orm.AbstractModel):
     _name = 'website.qweb.field.text'
     _inherit = ['website.qweb.field', 'ir.qweb.field.text']
 
-    def from_html(self, cr, uid, model, column, element, context=None):
+    def from_html(self, cr, uid, model, field, element, context=None):
         return html_to_text(element)
 
 class Selection(orm.AbstractModel):
     _name = 'website.qweb.field.selection'
     _inherit = ['website.qweb.field', 'ir.qweb.field.selection']
 
-    def from_html(self, cr, uid, model, column, element, context=None):
+    def from_html(self, cr, uid, model, field, element, context=None):
+        record = self.browse(cr, uid, [], context=context)
         value = element.text_content().strip()
-        selection = column.reify(cr, uid, model, column, context=context)
+        selection = field.get_description(record.env)['selection']
         for k, v in selection:
             if isinstance(v, str):
                 v = ustr(v)
@@ -227,11 +228,11 @@ class ManyToOne(orm.AbstractModel):
     _name = 'website.qweb.field.many2one'
     _inherit = ['website.qweb.field', 'ir.qweb.field.many2one']
 
-    def from_html(self, cr, uid, model, column, element, context=None):
+    def from_html(self, cr, uid, model, field, element, context=None):
         # FIXME: layering violations all the things
         Model = self.pool[element.get('data-oe-model')]
-        M2O = self.pool[column._obj]
-        field = element.get('data-oe-field')
+        M2O = self.pool[field.comodel_name]
+        field_name = element.get('data-oe-field')
         id = int(element.get('data-oe-id'))
         # FIXME: weird things are going to happen for char-type _rec_name
         value = html_to_text(element)
@@ -239,16 +240,16 @@ class ManyToOne(orm.AbstractModel):
         # if anything blows up, just ignore it and bail
         try:
             # get parent record
-            [obj] = Model.read(cr, uid, [id], [field])
+            [obj] = Model.read(cr, uid, [id], [field_name])
             # get m2o record id
-            (m2o_id, _) = obj[field]
+            (m2o_id, _) = obj[field_name]
             # assume _rec_name and write directly to it
             M2O.write(cr, uid, [m2o_id], {
                 M2O._rec_name: value
             }, context=context)
         except:
             logger.exception("Could not save %r to m2o field %s of model %s",
-                             value, field, Model._name)
+                             value, field_name, Model._name)
 
         # not necessary, but might as well be explicit about it
         return None
@@ -257,7 +258,7 @@ class HTML(orm.AbstractModel):
     _name = 'website.qweb.field.html'
     _inherit = ['website.qweb.field', 'ir.qweb.field.html']
 
-    def from_html(self, cr, uid, model, column, element, context=None):
+    def from_html(self, cr, uid, model, field, element, context=None):
         content = []
         if element.text: content.append(element.text)
         content.extend(html.tostring(child)
@@ -286,7 +287,7 @@ class Image(orm.AbstractModel):
             cr, uid, field_name, record, options,
             source_element, t_att, g_att, qweb_context, context=context)
 
-    def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None):
+    def record_to_html(self, cr, uid, field_name, record, options=None, context=None):
         if options is None: options = {}
         aclasses = ['img', 'img-responsive'] + options.get('class', '').split()
         classes = ' '.join(itertools.imap(escape, aclasses))
@@ -301,7 +302,8 @@ class Image(orm.AbstractModel):
         return ir_qweb.HTMLSafe(img)
 
     local_url_re = re.compile(r'^/(?P<module>[^]]+)/static/(?P<rest>.+)$')
-    def from_html(self, cr, uid, model, column, element, context=None):
+
+    def from_html(self, cr, uid, model, field, element, context=None):
         url = element.find('img').get('src')
 
         url_object = urlparse.urlsplit(url)
@@ -373,7 +375,7 @@ class Monetary(orm.AbstractModel):
     _name = 'website.qweb.field.monetary'
     _inherit = ['website.qweb.field', 'ir.qweb.field.monetary']
 
-    def from_html(self, cr, uid, model, column, element, context=None):
+    def from_html(self, cr, uid, model, field, element, context=None):
         lang = self.user_lang(cr, uid, context=context)
 
         value = element.find('span').text.strip()
@@ -396,7 +398,7 @@ class Duration(orm.AbstractModel):
             qweb_context, context=None)
         return itertools.chain(attrs, [('data-oe-original', record[field_name])])
 
-    def from_html(self, cr, uid, model, column, element, context=None):
+    def from_html(self, cr, uid, model, field, element, context=None):
         value = element.text_content().strip()
 
         # non-localized value
@@ -417,7 +419,7 @@ class Contact(orm.AbstractModel):
     _name = 'website.qweb.field.contact'
     _inherit = ['ir.qweb.field.contact', 'website.qweb.field.many2one']
 
-    def from_html(self, cr, uid, model, column, element, context=None):
+    def from_html(self, cr, uid, model, field, element, context=None):
         return None
 
 class QwebView(orm.AbstractModel):
index ebb1753..5153f22 100644 (file)
@@ -87,10 +87,8 @@ class view(osv.osv):
         Model = self.pool[el.get('data-oe-model')]
         field = el.get('data-oe-field')
 
-        column = Model._all_columns[field].column
-        converter = self.pool['website.qweb'].get_converter_for(
-            el.get('data-oe-type'))
-        value = converter.from_html(cr, uid, Model, column, el)
+        converter = self.pool['website.qweb'].get_converter_for(el.get('data-oe-type'))
+        value = converter.from_html(cr, uid, Model, Model._fields[field], el)
 
         if value is not None:
             # TODO: batch writes?
index 25dc303..f7f9045 100644 (file)
@@ -535,7 +535,7 @@ class website(osv.osv):
 
         ids = Model.search(cr, uid,
                            [('id', '=', id)], context=context)
-        if not ids and 'website_published' in Model._all_columns:
+        if not ids and 'website_published' in Model._fields:
             ids = Model.search(cr, openerp.SUPERUSER_ID,
                                [('id', '=', id), ('website_published', '=', True)], context=context)
         if not ids:
index a1177a3..9ae8168 100644 (file)
@@ -157,12 +157,11 @@ class TestConvertBack(common.TransactionCase):
         element = html.fromstring(
             rendered, parser=html.HTMLParser(encoding='utf-8'))
 
-        column = Model._all_columns[field].column
         converter = self.registry('website.qweb').get_converter_for(
             element.get('data-oe-type'))
 
         value_back = converter.from_html(
-            self.cr, self.uid, model, column, element)
+            self.cr, self.uid, model, Model._fields[field], element)
 
         if isinstance(expected, str):
             expected = expected.decode('utf-8')
@@ -240,12 +239,11 @@ class TestConvertBack(common.TransactionCase):
         # emulate edition
         element.text = "New content"
 
-        column = Model._all_columns[field].column
         converter = self.registry('website.qweb').get_converter_for(
             element.get('data-oe-type'))
 
         value_back = converter.from_html(
-            self.cr, self.uid, model, column, element)
+            self.cr, self.uid, model, Model._fields[field], element)
 
         self.assertIsNone(
             value_back, "the m2o converter should return None to avoid spurious"
index 18912a9..db1a6f9 100644 (file)
@@ -62,7 +62,7 @@ class contactus(http.Controller):
         for field_name, field_value in kwargs.items():
             if hasattr(field_value, 'filename'):
                 post_file.append(field_value)
-            elif field_name in request.registry['crm.lead']._all_columns and field_name not in _BLACKLIST:
+            elif field_name in request.registry['crm.lead']._fields and field_name not in _BLACKLIST:
                 values[field_name] = field_value
             elif field_name not in _TECHNICAL:  # allow to add some free fields or blacklisted field like ID
                 post_description.append("%s: %s" % (field_name, field_value))
index 6116dd3..157b8ef 100644 (file)
@@ -13,10 +13,10 @@ class WebsiteEmailDesigner(http.Controller):
     def index(self, model, res_id, template_model=None, **kw):
         if not model or not model in request.registry or not res_id:
             return request.redirect('/')
-        model_cols = request.registry[model]._all_columns
-        if 'body' not in model_cols and 'body_html' not in model_cols or \
-           'email' not in model_cols and 'email_from' not in model_cols or \
-           'name' not in model_cols and 'subject' not in model_cols:
+        model_fields = request.registry[model]._fields
+        if 'body' not in model_fields and 'body_html' not in model_fields or \
+           'email' not in model_fields and 'email_from' not in model_fields or \
+           'name' not in model_fields and 'subject' not in model_fields:
             return request.redirect('/')
         res_id = int(res_id)
         obj_ids = request.registry[model].exists(request.cr, request.uid, [res_id], context=request.context)
@@ -25,13 +25,13 @@ class WebsiteEmailDesigner(http.Controller):
         # try to find fields to display / edit -> as t-field is static, we have to limit
         # the available fields to a given subset
         email_from_field = 'email'
-        if 'email_from' in model_cols:
+        if 'email_from' in model_fields:
             email_from_field = 'email_from'
         subject_field = 'name'
-        if 'subject' in model_cols:
+        if 'subject' in model_fields:
             subject_field = 'subject'
         body_field = 'body'
-        if 'body_html' in model_cols:
+        if 'body_html' in model_fields:
             body_field = 'body_html'
 
         cr, uid, context = request.cr, request.uid, request.context
index 518d371..168537f 100644 (file)
@@ -616,16 +616,16 @@ class ir_actions_server(osv.osv):
         # analyze path
         while path:
             step = path.pop(0)
-            column_info = self.pool[current_model_name]._all_columns.get(step)
-            if not column_info:
+            field = self.pool[current_model_name]._fields.get(step)
+            if not field:
                 return (False, None, 'Part of the expression (%s) is not recognized as a column in the model %s.' % (step, current_model_name))
-            column_type = column_info.column._type
-            if column_type not in ['many2one', 'int']:
-                return (False, None, 'Part of the expression (%s) is not a valid column type (is %s, should be a many2one or an int)' % (step, column_type))
-            if column_type == 'int' and path:
+            ftype = field.type
+            if ftype not in ['many2one', 'int']:
+                return (False, None, 'Part of the expression (%s) is not a valid column type (is %s, should be a many2one or an int)' % (step, ftype))
+            if ftype == 'int' and path:
                 return (False, None, 'Part of the expression (%s) is an integer field that is only allowed at the end of an expression' % (step))
-            if column_type == 'many2one':
-                current_model_name = column_info.column._obj
+            if ftype == 'many2one':
+                current_model_name = field.comodel_name
         return (True, current_model_name, None)
 
     def _check_write_expression(self, cr, uid, ids, context=None):
index 494111d..77c0c83 100644 (file)
@@ -8,12 +8,8 @@ import time
 import psycopg2
 import pytz
 
-from openerp.osv import orm
-from openerp.tools.translate import _
-from openerp.tools.misc import DEFAULT_SERVER_DATE_FORMAT,\
-                               DEFAULT_SERVER_DATETIME_FORMAT,\
-                               ustr
-from openerp.tools import html_sanitize
+from openerp import models, api, _
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, ustr
 
 REFERENCING_FIELDS = set([None, 'id', '.id'])
 def only_ref_fields(record):
@@ -37,33 +33,12 @@ class ImportWarning(Warning):
 
 class ConversionNotFound(ValueError): pass
 
-class ColumnWrapper(object):
-    def __init__(self, column, cr, uid, pool, fromtype, context=None):
-        self._converter = None
-        self._column = column
-        if column._obj:
-            self._pool = pool
-            self._converter_args = {
-                'cr': cr,
-                'uid': uid,
-                'model': pool[column._obj],
-                'fromtype': fromtype,
-                'context': context
-            }
-    @property
-    def converter(self):
-        if not self._converter:
-            self._converter = self._pool['ir.fields.converter'].for_model(
-                **self._converter_args)
-        return self._converter
-
-    def __getattr__(self, item):
-        return getattr(self._column, item)
-
-class ir_fields_converter(orm.Model):
+
+class ir_fields_converter(models.Model):
     _name = 'ir.fields.converter'
 
-    def for_model(self, cr, uid, model, fromtype=str, context=None):
+    @api.model
+    def for_model(self, model, fromtype=str):
         """ Returns a converter object for the model. A converter is a
         callable taking a record-ish (a dictionary representing an openerp
         record with values of typetag ``fromtype``) and returning a converted
@@ -73,17 +48,19 @@ class ir_fields_converter(orm.Model):
         :returns: a converter callable
         :rtype: (record: dict, logger: (field, error) -> None) -> dict
         """
-        columns = dict(
-            (k, ColumnWrapper(v.column, cr, uid, self.pool, fromtype, context))
-            for k, v in model._all_columns.iteritems())
-        converters = dict(
-            (k, self.to_field(cr, uid, model, column, fromtype, context))
-            for k, column in columns.iteritems())
+        # make sure model is new api
+        model = self.env[model._name]
+
+        converters = {
+            name: self.to_field(model, field, fromtype)
+            for name, field in model._fields.iteritems()
+        }
 
         def fn(record, log):
             converted = {}
             for field, value in record.iteritems():
-                if field in (None, 'id', '.id'): continue
+                if field in (None, 'id', '.id'):
+                    continue
                 if not value:
                     converted[field] = False
                     continue
@@ -97,20 +74,21 @@ class ir_fields_converter(orm.Model):
                         log(field, w)
                 except ValueError, e:
                     log(field, e)
-
             return converted
+
         return fn
 
-    def to_field(self, cr, uid, model, column, fromtype=str, context=None):
-        """ Fetches a converter for the provided column object, from the
+    @api.model
+    def to_field(self, model, field, fromtype=str):
+        """ Fetches a converter for the provided field object, from the
         specified type.
 
         A converter is simply a callable taking a value of type ``fromtype``
         (or a composite of ``fromtype``, e.g. list or dict) and returning a
-        value acceptable for a write() on the column ``column``.
+        value acceptable for a write() on the field ``field``.
 
         By default, tries to get a method on itself with a name matching the
-        pattern ``_$fromtype_to_$column._type`` and returns it.
+        pattern ``_$fromtype_to_$field.type`` and returns it.
 
         Converter callables can either return a value and a list of warnings
         to their caller or raise ``ValueError``, which will be interpreted as a
@@ -132,42 +110,43 @@ class ir_fields_converter(orm.Model):
         it returns. The handling of a warning at the upper levels is the same
         as ``ValueError`` above.
 
-        :param column: column object to generate a value for
-        :type column: :class:`fields._column`
-        :param fromtype: type to convert to something fitting for ``column``
+        :param field: field object to generate a value for
+        :type field: :class:`openerp.fields.Field`
+        :param fromtype: type to convert to something fitting for ``field``
         :type fromtype: type | str
         :param context: openerp request context
-        :return: a function (fromtype -> column.write_type), if a converter is found
+        :return: a function (fromtype -> field.write_type), if a converter is found
         :rtype: Callable | None
         """
         assert isinstance(fromtype, (type, str))
         # FIXME: return None
         typename = fromtype.__name__ if isinstance(fromtype, type) else fromtype
-        converter = getattr(
-            self, '_%s_to_%s' % (typename, column._type), None)
-        if not converter: return None
-
-        return functools.partial(
-            converter, cr, uid, model, column, context=context)
+        converter = getattr(self, '_%s_to_%s' % (typename, field.type), None)
+        if not converter:
+            return None
+        return functools.partial(converter, model, field)
 
-    def _str_to_boolean(self, cr, uid, model, column, value, context=None):
+    @api.model
+    def _str_to_boolean(self, model, field, value):
         # all translatables used for booleans
         true, yes, false, no = _(u"true"), _(u"yes"), _(u"false"), _(u"no")
         # potentially broken casefolding? What about locales?
         trues = set(word.lower() for word in itertools.chain(
             [u'1', u"true", u"yes"], # don't use potentially translated values
-            self._get_translations(cr, uid, ['code'], u"true", context=context),
-            self._get_translations(cr, uid, ['code'], u"yes", context=context),
+            self._get_translations(['code'], u"true"),
+            self._get_translations(['code'], u"yes"),
         ))
-        if value.lower() in trues: return True, []
+        if value.lower() in trues:
+            return True, []
 
         # potentially broken casefolding? What about locales?
         falses = set(word.lower() for word in itertools.chain(
             [u'', u"0", u"false", u"no"],
-            self._get_translations(cr, uid, ['code'], u"false", context=context),
-            self._get_translations(cr, uid, ['code'], u"no", context=context),
+            self._get_translations(['code'], u"false"),
+            self._get_translations(['code'], u"no"),
         ))
-        if value.lower() in falses: return False, []
+        if value.lower() in falses:
+            return False, []
 
         return True, [ImportWarning(
             _(u"Unknown value '%s' for boolean field '%%(field)s', assuming '%s'")
@@ -175,7 +154,8 @@ class ir_fields_converter(orm.Model):
                 'moreinfo': _(u"Use '1' for yes and '0' for no")
             })]
 
-    def _str_to_integer(self, cr, uid, model, column, value, context=None):
+    @api.model
+    def _str_to_integer(self, model, field, value):
         try:
             return int(value), []
         except ValueError:
@@ -183,7 +163,8 @@ class ir_fields_converter(orm.Model):
                 _(u"'%s' does not seem to be an integer for field '%%(field)s'")
                 % value)
 
-    def _str_to_float(self, cr, uid, model, column, value, context=None):
+    @api.model
+    def _str_to_float(self, model, field, value):
         try:
             return float(value), []
         except ValueError:
@@ -191,11 +172,14 @@ class ir_fields_converter(orm.Model):
                 _(u"'%s' does not seem to be a number for field '%%(field)s'")
                 % value)
 
-    def _str_id(self, cr, uid, model, column, value, context=None):
+    @api.model
+    def _str_id(self, model, field, value):
         return value, []
+
     _str_to_reference = _str_to_char = _str_to_text = _str_to_binary = _str_to_html = _str_id
 
-    def _str_to_date(self, cr, uid, model, column, value, context=None):
+    @api.model
+    def _str_to_date(self, model, field, value):
         try:
             time.strptime(value, DEFAULT_SERVER_DATE_FORMAT)
             return value, []
@@ -205,28 +189,28 @@ class ir_fields_converter(orm.Model):
                     'moreinfo': _(u"Use the format '%s'") % u"2012-12-31"
                 })
 
-    def _input_tz(self, cr, uid, context):
+    @api.model
+    def _input_tz(self):
         # if there's a tz in context, try to use that
-        if context.get('tz'):
+        if self._context.get('tz'):
             try:
-                return pytz.timezone(context['tz'])
+                return pytz.timezone(self._context['tz'])
             except pytz.UnknownTimeZoneError:
                 pass
 
         # if the current user has a tz set, try to use that
-        user = self.pool['res.users'].read(
-            cr, uid, [uid], ['tz'], context=context)[0]
-        if user['tz']:
+        user = self.env.user
+        if user.tz:
             try:
-                return pytz.timezone(user['tz'])
+                return pytz.timezone(user.tz)
             except pytz.UnknownTimeZoneError:
                 pass
 
         # fallback if no tz in context or on user: UTC
         return pytz.UTC
 
-    def _str_to_datetime(self, cr, uid, model, column, value, context=None):
-        if context is None: context = {}
+    @api.model
+    def _str_to_datetime(self, model, field, value):
         try:
             parsed_value = datetime.datetime.strptime(
                 value, DEFAULT_SERVER_DATETIME_FORMAT)
@@ -236,40 +220,37 @@ class ir_fields_converter(orm.Model):
                     'moreinfo': _(u"Use the format '%s'") % u"2012-12-31 23:59:59"
                 })
 
-        input_tz = self._input_tz(cr, uid, context)# Apply input tz to the parsed naive datetime
+        input_tz = self._input_tz()# Apply input tz to the parsed naive datetime
         dt = input_tz.localize(parsed_value, is_dst=False)
         # And convert to UTC before reformatting for writing
         return dt.astimezone(pytz.UTC).strftime(DEFAULT_SERVER_DATETIME_FORMAT), []
 
-    def _get_translations(self, cr, uid, types, src, context):
+    @api.model
+    def _get_translations(self, types, src):
         types = tuple(types)
         # Cache translations so they don't have to be reloaded from scratch on
         # every row of the file
-        tnx_cache = cr.cache.setdefault(self._name, {})
+        tnx_cache = self._cr.cache.setdefault(self._name, {})
         if tnx_cache.setdefault(types, {}) and src in tnx_cache[types]:
             return tnx_cache[types][src]
 
-        Translations = self.pool['ir.translation']
-        tnx_ids = Translations.search(
-            cr, uid, [('type', 'in', types), ('src', '=', src)], context=context)
-        tnx = Translations.read(cr, uid, tnx_ids, ['value'], context=context)
-        result = tnx_cache[types][src] = [t['value'] for t in tnx if t['value'] is not False]
+        Translations = self.env['ir.translation']
+        tnx = Translations.search([('type', 'in', types), ('src', '=', src)])
+        result = tnx_cache[types][src] = [t.value for t in tnx if t.value is not False]
         return result
 
-    def _str_to_selection(self, cr, uid, model, column, value, context=None):
+    @api.model
+    def _str_to_selection(self, model, field, value):
+        # get untranslated values
+        env = self.with_context(lang=None).env
+        selection = field.get_description(env)['selection']
 
-        selection = column.selection
-        if not isinstance(selection, (tuple, list)):
-            # FIXME: Don't pass context to avoid translations?
-            #        Or just copy context & remove lang?
-            selection = selection(model, cr, uid, context=None)
         for item, label in selection:
             label = ustr(label)
-            labels = self._get_translations(
-                cr, uid, ('selection', 'model', 'code'), label, context=context)
-            labels.append(label)
+            labels = [label] + self._get_translations(('selection', 'model', 'code'), label)
             if value == unicode(item) or value in labels:
                 return item, []
+
         raise ValueError(
             _(u"Value '%s' not found in selection field '%%(field)s'") % (
                 value), {
@@ -277,13 +258,13 @@ class ir_fields_converter(orm.Model):
                              if _label or item]
             })
 
-
-    def db_id_for(self, cr, uid, model, column, subfield, value, context=None):
+    @api.model
+    def db_id_for(self, model, field, subfield, value):
         """ Finds a database id for the reference ``value`` in the referencing
-        subfield ``subfield`` of the provided column of the provided model.
+        subfield ``subfield`` of the provided field of the provided model.
 
-        :param model: model to which the column belongs
-        :param column: relational column for which references are provided
+        :param model: model to which the field belongs
+        :param field: relational field for which references are provided
         :param subfield: a relational subfield allowing building of refs to
                          existing records: ``None`` for a name_get/name_search,
                          ``id`` for an external id and ``.id`` for a database
@@ -295,7 +276,6 @@ class ir_fields_converter(orm.Model):
                  warnings
         :rtype: (ID|None, unicode, list)
         """
-        if context is None: context = {}
         id = None
         warnings = []
         action = {'type': 'ir.actions.act_window', 'target': 'new',
@@ -303,19 +283,18 @@ class ir_fields_converter(orm.Model):
                   'views': [(False, 'tree'), (False, 'form')],
                   'help': _(u"See all possible values")}
         if subfield is None:
-            action['res_model'] = column._obj
+            action['res_model'] = field.comodel_name
         elif subfield in ('id', '.id'):
             action['res_model'] = 'ir.model.data'
-            action['domain'] = [('model', '=', column._obj)]
+            action['domain'] = [('model', '=', field.comodel_name)]
 
-        RelatedModel = self.pool[column._obj]
+        RelatedModel = self.env[field.comodel_name]
         if subfield == '.id':
             field_type = _(u"database id")
             try: tentative_id = int(value)
             except ValueError: tentative_id = value
             try:
-                if RelatedModel.search(cr, uid, [('id', '=', tentative_id)],
-                                       context=context):
+                if RelatedModel.search([('id', '=', tentative_id)]):
                     id = tentative_id
             except psycopg2.DataError:
                 # type error
@@ -325,18 +304,16 @@ class ir_fields_converter(orm.Model):
         elif subfield == 'id':
             field_type = _(u"external id")
             if '.' in value:
-                module, xid = value.split('.', 1)
+                xmlid = value
             else:
-                module, xid = context.get('_import_current_module', ''), value
-            ModelData = self.pool['ir.model.data']
+                xmlid = "%s.%s" % (self._context.get('_import_current_module', ''), value)
             try:
-                _model, id = ModelData.get_object_reference(
-                    cr, uid, module, xid)
-            except ValueError: pass # leave id is None
+                id = self.env.ref(xmlid).id
+            except ValueError:
+                pass # leave id is None
         elif subfield is None:
             field_type = _(u"name")
-            ids = RelatedModel.name_search(
-                cr, uid, name=value, operator='=', context=context)
+            ids = RelatedModel.name_search(name=value, operator='=')
             if ids:
                 if len(ids) > 1:
                     warnings.append(ImportWarning(
@@ -376,31 +353,32 @@ class ir_fields_converter(orm.Model):
         [subfield] = fieldset
         return subfield, []
 
-    def _str_to_many2one(self, cr, uid, model, column, values, context=None):
+    @api.model
+    def _str_to_many2one(self, model, field, values):
         # Should only be one record, unpack
         [record] = values
 
         subfield, w1 = self._referencing_subfield(record)
 
         reference = record[subfield]
-        id, subfield_type, w2 = self.db_id_for(
-            cr, uid, model, column, subfield, reference, context=context)
+        id, _, w2 = self.db_id_for(model, field, subfield, reference)
         return id, w1 + w2
 
-    def _str_to_many2many(self, cr, uid, model, column, value, context=None):
+    @api.model
+    def _str_to_many2many(self, model, field, value):
         [record] = value
 
         subfield, warnings = self._referencing_subfield(record)
 
         ids = []
         for reference in record[subfield].split(','):
-            id, subfield_type, ws = self.db_id_for(
-                cr, uid, model, column, subfield, reference, context=context)
+            id, _, ws = self.db_id_for(model, field, subfield, reference)
             ids.append(id)
             warnings.extend(ws)
         return [REPLACE_WITH(ids)], warnings
 
-    def _str_to_one2many(self, cr, uid, model, column, records, context=None):
+    @api.model
+    def _str_to_one2many(self, model, field, records):
         commands = []
         warnings = []
 
@@ -418,6 +396,9 @@ class ir_fields_converter(orm.Model):
             if not isinstance(e, Warning):
                 raise e
             warnings.append(e)
+
+        convert = self.for_model(self.env[field.comodel_name])
+
         for record in records:
             id = None
             refs = only_ref_fields(record)
@@ -426,11 +407,10 @@ class ir_fields_converter(orm.Model):
                 subfield, w1 = self._referencing_subfield(refs)
                 warnings.extend(w1)
                 reference = record[subfield]
-                id, subfield_type, w2 = self.db_id_for(
-                    cr, uid, model, column, subfield, reference, context=context)
+                id, _, w2 = self.db_id_for(model, field, subfield, reference)
                 warnings.extend(w2)
 
-            writable = column.converter(exclude_ref_fields(record), log)
+            writable = convert(exclude_ref_fields(record), log)
             if id:
                 commands.append(LINK_TO(id))
                 commands.append(UPDATE(id, writable))
index 6a54e6f..39667b0 100644 (file)
@@ -470,9 +470,9 @@ class QWeb(orm.AbstractModel):
         record, field_name = template_attributes["field"].rsplit('.', 1)
         record = self.eval_object(record, qwebcontext)
 
-        column = record._all_columns[field_name].column
+        field = record._fields[field_name]
         options = json.loads(template_attributes.get('field-options') or '{}')
-        field_type = get_field_type(column, options)
+        field_type = get_field_type(field, options)
 
         converter = self.get_converter_for(field_type)
 
@@ -538,16 +538,16 @@ class FieldConverter(osv.AbstractModel):
         * ``model``, the name of the record's model
         * ``id`` the id of the record to which the field belongs
         * ``field`` the name of the converted field
-        * ``type`` the logical field type (widget, may not match the column's
-          ``type``, may not be any _column subclass name)
+        * ``type`` the logical field type (widget, may not match the field's
+          ``type``, may not be any Field subclass name)
         * ``translate``, a boolean flag (``0`` or ``1``) denoting whether the
-          column is translatable
+          field is translatable
         * ``expression``, the original expression
 
         :returns: iterable of (attribute name, attribute value) pairs.
         """
-        column = record._all_columns[field_name].column
-        field_type = get_field_type(column, options)
+        field = record._fields[field_name]
+        field_type = get_field_type(field, options)
         return [
             ('data-oe-model', record._name),
             ('data-oe-id', record.id),
@@ -556,21 +556,22 @@ class FieldConverter(osv.AbstractModel):
             ('data-oe-expression', t_att['field']),
         ]
 
-    def value_to_html(self, cr, uid, value, column, options=None, context=None):
-        """ value_to_html(cr, uid, value, column, options=None, context=None)
+    def value_to_html(self, cr, uid, value, field, options=None, context=None):
+        """ value_to_html(cr, uid, value, field, options=None, context=None)
 
         Converts a single value to its HTML version/output
         """
         if not value: return ''
         return value
 
-    def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None):
-        """ record_to_html(cr, uid, field_name, record, column, options=None, context=None)
+    def record_to_html(self, cr, uid, field_name, record, options=None, context=None):
+        """ record_to_html(cr, uid, field_name, record, options=None, context=None)
 
         Converts the specified field of the browse_record ``record`` to HTML
         """
+        field = record._fields[field_name]
         return self.value_to_html(
-            cr, uid, record[field_name], column, options=options, context=context)
+            cr, uid, record[field_name], field, options=options, context=context)
 
     def to_html(self, cr, uid, field_name, record, options,
                 source_element, t_att, g_att, qweb_context, context=None):
@@ -584,10 +585,7 @@ class FieldConverter(osv.AbstractModel):
         field's own ``_type``.
         """
         try:
-            content = self.record_to_html(
-                cr, uid, field_name, record,
-                record._all_columns[field_name].column,
-                options, context=context)
+            content = self.record_to_html(cr, uid, field_name, record, options, context=context)
             if options.get('html-escape', True):
                 content = escape(content)
             elif hasattr(content, '__html__'):
@@ -648,14 +646,14 @@ class FloatConverter(osv.AbstractModel):
     _name = 'ir.qweb.field.float'
     _inherit = 'ir.qweb.field'
 
-    def precision(self, cr, uid, column, options=None, context=None):
-        _, precision = column.digits or (None, None)
+    def precision(self, cr, uid, field, options=None, context=None):
+        _, precision = field.digits or (None, None)
         return precision
 
-    def value_to_html(self, cr, uid, value, column, options=None, context=None):
+    def value_to_html(self, cr, uid, value, field, options=None, context=None):
         if context is None:
             context = {}
-        precision = self.precision(cr, uid, column, options=options, context=context)
+        precision = self.precision(cr, uid, field, options=options, context=context)
         fmt = '%f' if precision is None else '%.{precision}f'
 
         lang_code = context.get('lang') or 'en_US'
@@ -674,7 +672,7 @@ class DateConverter(osv.AbstractModel):
     _name = 'ir.qweb.field.date'
     _inherit = 'ir.qweb.field'
 
-    def value_to_html(self, cr, uid, value, column, options=None, context=None):
+    def value_to_html(self, cr, uid, value, field, options=None, context=None):
         if not value or len(value)<10: return ''
         lang = self.user_lang(cr, uid, context=context)
         locale = babel.Locale.parse(lang.code)
@@ -697,7 +695,7 @@ class DateTimeConverter(osv.AbstractModel):
     _name = 'ir.qweb.field.datetime'
     _inherit = 'ir.qweb.field'
 
-    def value_to_html(self, cr, uid, value, column, options=None, context=None):
+    def value_to_html(self, cr, uid, value, field, options=None, context=None):
         if not value: return ''
         lang = self.user_lang(cr, uid, context=context)
         locale = babel.Locale.parse(lang.code)
@@ -723,7 +721,7 @@ class TextConverter(osv.AbstractModel):
     _name = 'ir.qweb.field.text'
     _inherit = 'ir.qweb.field'
 
-    def value_to_html(self, cr, uid, value, column, options=None, context=None):
+    def value_to_html(self, cr, uid, value, field, options=None, context=None):
         """
         Escapes the value and converts newlines to br. This is bullshit.
         """
@@ -735,19 +733,19 @@ class SelectionConverter(osv.AbstractModel):
     _name = 'ir.qweb.field.selection'
     _inherit = 'ir.qweb.field'
 
-    def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None):
+    def record_to_html(self, cr, uid, field_name, record, options=None, context=None):
         value = record[field_name]
         if not value: return ''
-        selection = dict(fields.selection.reify(
-            cr, uid, record._model, column, context=context))
+        field = record._fields[field_name]
+        selection = dict(field.get_description(record.env)['selection'])
         return self.value_to_html(
-            cr, uid, selection[value], column, options=options)
+            cr, uid, selection[value], field, options=options)
 
 class ManyToOneConverter(osv.AbstractModel):
     _name = 'ir.qweb.field.many2one'
     _inherit = 'ir.qweb.field'
 
-    def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None):
+    def record_to_html(self, cr, uid, field_name, record, options=None, context=None):
         [read] = record.read([field_name])
         if not read[field_name]: return ''
         _, value = read[field_name]
@@ -757,7 +755,7 @@ class HTMLConverter(osv.AbstractModel):
     _name = 'ir.qweb.field.html'
     _inherit = 'ir.qweb.field'
 
-    def value_to_html(self, cr, uid, value, column, options=None, context=None):
+    def value_to_html(self, cr, uid, value, field, options=None, context=None):
         return HTMLSafe(value or '')
 
 class ImageConverter(osv.AbstractModel):
@@ -772,7 +770,7 @@ class ImageConverter(osv.AbstractModel):
     _name = 'ir.qweb.field.image'
     _inherit = 'ir.qweb.field'
 
-    def value_to_html(self, cr, uid, value, column, options=None, context=None):
+    def value_to_html(self, cr, uid, value, field, options=None, context=None):
         try:
             image = Image.open(cStringIO.StringIO(value.decode('base64')))
             image.verify()
@@ -805,7 +803,7 @@ class MonetaryConverter(osv.AbstractModel):
             cr, uid, field_name, record, options,
             source_element, t_att, g_att, qweb_context, context=context)
 
-    def record_to_html(self, cr, uid, field_name, record, column, options, context=None):
+    def record_to_html(self, cr, uid, field_name, record, options, context=None):
         if context is None:
             context = {}
         Currency = self.pool['res.currency']
@@ -876,7 +874,7 @@ class DurationConverter(osv.AbstractModel):
     _name = 'ir.qweb.field.duration'
     _inherit = 'ir.qweb.field'
 
-    def value_to_html(self, cr, uid, value, column, options=None, context=None):
+    def value_to_html(self, cr, uid, value, field, options=None, context=None):
         units = dict(TIMEDELTA_UNITS)
         if value < 0:
             raise ValueError(_("Durations can't be negative"))
@@ -903,7 +901,7 @@ class RelativeDatetimeConverter(osv.AbstractModel):
     _name = 'ir.qweb.field.relative'
     _inherit = 'ir.qweb.field'
 
-    def value_to_html(self, cr, uid, value, column, options=None, context=None):
+    def value_to_html(self, cr, uid, value, field, options=None, context=None):
         parse_format = openerp.tools.DEFAULT_SERVER_DATETIME_FORMAT
         locale = babel.Locale.parse(
             self.user_lang(cr, uid, context=context).code)
@@ -911,8 +909,8 @@ class RelativeDatetimeConverter(osv.AbstractModel):
         if isinstance(value, basestring):
             value = datetime.datetime.strptime(value, parse_format)
 
-        # value should be a naive datetime in UTC. So is fields.datetime.now()
-        reference = datetime.datetime.strptime(column.now(), parse_format)
+        # value should be a naive datetime in UTC. So is fields.Datetime.now()
+        reference = datetime.datetime.strptime(field.now(), parse_format)
 
         return babel.dates.format_timedelta(
             value - reference, add_direction=True, locale=locale)
@@ -921,33 +919,32 @@ class Contact(orm.AbstractModel):
     _name = 'ir.qweb.field.contact'
     _inherit = 'ir.qweb.field.many2one'
 
-    def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None):
+    def record_to_html(self, cr, uid, field_name, record, options=None, context=None):
         if context is None:
             context = {}
 
         if options is None:
             options = {}
         opf = options.get('fields') or ["name", "address", "phone", "mobile", "fax", "email"]
-        if not getattr(record, field_name):
-            return None
 
-        id = getattr(record, field_name).id
-        context.update(show_address=True)
-        field_browse = self.pool[column._obj].browse(cr, openerp.SUPERUSER_ID, id, context=context)
-        value = field_browse.name_get()[0][1]
+        value_rec = record[field_name]
+        if not value_rec:
+            return None
+        value_rec = value_rec.sudo().with_context(show_address=True)
+        value = value_rec.display_name
 
         val = {
             'name': value.split("\n")[0],
             'address': escape("\n".join(value.split("\n")[1:])),
-            'phone': field_browse.phone,
-            'mobile': field_browse.mobile,
-            'fax': field_browse.fax,
-            'city': field_browse.city,
-            'country_id': field_browse.country_id.display_name,
-            'website': field_browse.website,
-            'email': field_browse.email,
+            'phone': value_rec.phone,
+            'mobile': value_rec.mobile,
+            'fax': value_rec.fax,
+            'city': value_rec.city,
+            'country_id': value_rec.country_id.display_name,
+            'website': value_rec.website,
+            'email': value_rec.email,
             'fields': opf,
-            'object': field_browse,
+            'object': value_rec,
             'options': options
         }
 
@@ -959,7 +956,7 @@ class QwebView(orm.AbstractModel):
     _name = 'ir.qweb.field.qweb'
     _inherit = 'ir.qweb.field.many2one'
 
-    def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None):
+    def record_to_html(self, cr, uid, field_name, record, options=None, context=None):
         if not getattr(record, field_name):
             return None
 
@@ -1045,10 +1042,9 @@ def nl2br(string, options=None):
         string = escape(string)
     return HTMLSafe(string.replace('\n', '<br>\n'))
 
-def get_field_type(column, options):
-    """ Gets a t-field's effective type from the field's column and its options
-    """
-    return options.get('widget', column._type)
+def get_field_type(field, options):
+    """ Gets a t-field's effective type from the field definition and its options """
+    return options.get('widget', field.type)
 
 class AssetError(Exception):
     pass
index 9e0dd5e..a16933b 100644 (file)
@@ -399,15 +399,15 @@ class ir_translation(osv.osv):
         langs = [lg.code for lg in self.pool.get('res.lang').browse(cr, uid, langs_ids, context=context)]
         main_lang = 'en_US'
         translatable_fields = []
-        for f, info in trans_model._all_columns.items():
-            if info.column.translate:
-                if info.parent_model:
-                    parent_id = trans_model.read(cr, uid, [id], [info.parent_column], context=context)[0][info.parent_column][0]
-                    translatable_fields.append({ 'name': f, 'id': parent_id, 'model': info.parent_model })
+        for k, f in trans_model._fields.items():
+            if f.translate:
+                if f.inherited:
+                    parent_id = trans_model.read(cr, uid, [id], [f.related[0]], context=context)[0][f.related[0]][0]
+                    translatable_fields.append({'name': k, 'id': parent_id, 'model': f.base_field.model})
                     domain.insert(0, '|')
-                    domain.extend(['&', ('res_id', '=', parent_id), ('name', '=', "%s,%s" % (info.parent_model, f))])
+                    domain.extend(['&', ('res_id', '=', parent_id), ('name', '=', "%s,%s" % (f.base_field.model, k))])
                 else:
-                    translatable_fields.append({ 'name': f, 'id': id, 'model': model })
+                    translatable_fields.append({'name': k, 'id': id, 'model': model })
         if len(langs):
             fields = [f.get('name') for f in translatable_fields]
             record = trans_model.read(cr, uid, [id], fields, context={ 'lang': main_lang })[0]
@@ -432,9 +432,9 @@ class ir_translation(osv.osv):
             'domain': domain,
         }
         if field:
-            info = trans_model._all_columns[field]
+            f = trans_model._fields[field]
             action['context'] = {
-                'search_default_name': "%s,%s" % (info.parent_model or model, field)
+                'search_default_name': "%s,%s" % (f.base_field.model, field)
             }
         return action
 
index 738ae3a..751326d 100644 (file)
@@ -811,11 +811,11 @@ class view(osv.osv):
                 if not node.get(action) and not Model.check_access_rights(cr, user, operation, raise_exception=False):
                     node.set(action, 'false')
         if node.tag in ('kanban'):
-            group_by_field = node.get('default_group_by')
-            if group_by_field and Model._all_columns.get(group_by_field):
-                group_by_column = Model._all_columns[group_by_field].column
-                if group_by_column._type == 'many2one':
-                    group_by_model = Model.pool.get(group_by_column._obj)
+            group_by_name = node.get('default_group_by')
+            if group_by_name in Model._fields:
+                group_by_field = Model._fields[group_by_name]
+                if group_by_field.type == 'many2one':
+                    group_by_model = Model.pool[group_by_field.comodel_name]
                     for action, operation in (('group_create', 'create'), ('group_delete', 'unlink'), ('group_edit', 'write')):
                         if not node.get(action) and not group_by_model.check_access_rights(cr, user, operation, raise_exception=False):
                             node.set(action, 'false')
index 075612c..070edca 100644 (file)
@@ -423,7 +423,7 @@ class ir_values(osv.osv):
             if action_model_name not in self.pool:
                 continue    # unknow model? skip it
             action_model = self.pool[action_model_name]
-            fields = [field for field in action_model._all_columns if field not in EXCLUDED_FIELDS]
+            fields = [field for field in action_model._fields if field not in EXCLUDED_FIELDS]
             # FIXME: needs cleanup
             try:
                 action_def = action_model.read(cr, uid, int(action_id), fields, context)
index 4f21ea0..0f4f475 100644 (file)
@@ -416,16 +416,16 @@ class res_partner(osv.Model, format_address):
     def _update_fields_values(self, cr, uid, partner, fields, context=None):
         """ Returns dict of write() values for synchronizing ``fields`` """
         values = {}
-        for field in fields:
-            column = self._all_columns[field].column
-            if column._type == 'one2many':
+        for fname in fields:
+            field = self._fields[fname]
+            if field.type == 'one2many':
                 raise AssertionError('One2Many fields cannot be synchronized as part of `commercial_fields` or `address fields`')
-            if column._type == 'many2one':
-                values[field] = partner[field].id if partner[field] else False
-            elif column._type == 'many2many':
-                values[field] = [(6,0,[r.id for r in partner[field] or []])]
+            if field.type == 'many2one':
+                values[fname] = partner[fname].id if partner[fname] else False
+            elif field.type == 'many2many':
+                values[fname] = [(6,0,[r.id for r in partner[fname] or []])]
             else:
-                values[field] = partner[field]
+                values[fname] = partner[fname]
         return values
 
     def _address_fields(self, cr, uid, context=None):
index e0c9667..9a7c780 100644 (file)
@@ -377,7 +377,7 @@ class res_users(osv.osv):
     def context_get(self, cr, uid, context=None):
         user = self.browse(cr, SUPERUSER_ID, uid, context)
         result = {}
-        for k in self._all_columns.keys():
+        for k in self._fields:
             if k.startswith('context_'):
                 context_key = k[8:]
             elif k in ['lang', 'tz']:
index a6f32a7..c7ab7f0 100644 (file)
@@ -60,7 +60,8 @@
         self.pool._init = False
 
         # Force partner_categ.copy() to copy children
-        self.pool['res.partner.category']._columns['child_ids'].copy = True
+        self._fields['child_ids'].copy = True
+        self._columns['child_ids'].copy = True
 -
     "1.0 Setup test partner categories: parent root"
 -
 -
     !python {model: res.partner.category}: |
         self.pool._init = True
-        self.pool['res.partner.category']._columns['child_ids'].copy = False
+        self._fields['child_ids'].copy = False
+        self._columns['child_ids'].copy = False
 
 -
     "Float precision tests: verify that float rounding methods are working correctly via res.currency"
index 2be394e..1f52032 100644 (file)
@@ -93,17 +93,17 @@ class TestAPI(common.TransactionCase):
         self.assertIsRecordset(user.groups_id, 'res.groups')
 
         partners = self.env['res.partner'].search([])
-        for name, cinfo in partners._all_columns.iteritems():
-            if cinfo.column._type == 'many2one':
+        for name, field in partners._fields.iteritems():
+            if field.type == 'many2one':
                 for p in partners:
-                    self.assertIsRecord(p[name], cinfo.column._obj)
-            elif cinfo.column._type == 'reference':
+                    self.assertIsRecord(p[name], field.comodel_name)
+            elif field.type == 'reference':
                 for p in partners:
                     if p[name]:
-                        self.assertIsRecord(p[name], cinfo.column._obj)
-            elif cinfo.column._type in ('one2many', 'many2many'):
+                        self.assertIsRecord(p[name], field.comodel_name)
+            elif field.type in ('one2many', 'many2many'):
                 for p in partners:
-                    self.assertIsRecordset(p[name], cinfo.column._obj)
+                    self.assertIsRecordset(p[name], field.comodel_name)
 
     @mute_logger('openerp.models')
     def test_07_null(self):
index 300fea5..aef7bad 100644 (file)
@@ -17,15 +17,14 @@ class TestExport(common.TransactionCase):
     def setUp(self):
         super(TestExport, self).setUp()
         self.Model = self.registry(self._model)
-        self.columns = self.Model._all_columns
 
-    def get_column(self, name):
-        return self.Model._all_columns[name].column
+    def get_field(self, name):
+        return self.Model._fields[name]
 
     def get_converter(self, name, type=None):
-        column = self.get_column(name)
+        field = self.get_field(name)
 
-        for postfix in type, column._type, '':
+        for postfix in type, field.type, '':
             fs = ['ir', 'qweb', 'field']
             if postfix is None: continue
             if postfix: fs.append(postfix)
@@ -36,7 +35,7 @@ class TestExport(common.TransactionCase):
             except KeyError: pass
 
         return lambda value, options=None, context=None: e(model.value_to_html(
-            self.cr, self.uid, value, column, options=options, context=context))
+            self.cr, self.uid, value, field, options=options, context=context))
 
 class TestBasicExport(TestExport):
     _model = 'test_converter.test_model'
@@ -222,11 +221,8 @@ class TestMany2OneExport(TestBasicExport):
         })
 
         def converter(record):
-            column = self.get_column('many2one')
             model = self.registry('ir.qweb.field.many2one')
-
-            return e(model.record_to_html(
-                self.cr, self.uid, 'many2one', record, column))
+            return e(model.record_to_html(self.cr, self.uid, 'many2one', record))
 
         value = converter(self.Model.browse(self.cr, self.uid, id0))
         self.assertEqual(value, "Foo")
@@ -236,7 +232,7 @@ class TestMany2OneExport(TestBasicExport):
 
 class TestBinaryExport(TestBasicExport):
     def test_image(self):
-        column = self.get_column('binary')
+        field = self.get_field('binary')
         converter = self.registry('ir.qweb.field.image')
 
         with open(os.path.join(directory, 'test_vectors', 'image'), 'rb') as f:
@@ -244,7 +240,7 @@ class TestBinaryExport(TestBasicExport):
 
         encoded_content = content.encode('base64')
         value = e(converter.value_to_html(
-            self.cr, self.uid, encoded_content, column))
+            self.cr, self.uid, encoded_content, field))
         self.assertEqual(
             value, '<img src="data:image/jpeg;base64,%s">' % (
                 encoded_content
@@ -255,14 +251,14 @@ class TestBinaryExport(TestBasicExport):
 
         with self.assertRaises(ValueError):
             e(converter.value_to_html(
-                self.cr, self.uid, 'binary', content.encode('base64'), column))
+                self.cr, self.uid, 'binary', content.encode('base64'), field))
 
         with open(os.path.join(directory, 'test_vectors', 'pptx'), 'rb') as f:
             content = f.read()
 
         with self.assertRaises(ValueError):
             e(converter.value_to_html(
-                self.cr, self.uid, 'binary', content.encode('base64'), column))
+                self.cr, self.uid, 'binary', content.encode('base64'), field))
 
 class TestSelectionExport(TestBasicExport):
     def test_selection(self):
@@ -271,18 +267,14 @@ class TestSelectionExport(TestBasicExport):
             'selection_str': 'C',
         })])
 
-        column_name = 'selection'
-        column = self.get_column(column_name)
         converter = self.registry('ir.qweb.field.selection')
 
-        value = converter.record_to_html(
-            self.cr, self.uid, column_name, record, column)
+        field_name = 'selection'
+        value = converter.record_to_html(self.cr, self.uid, field_name, record)
         self.assertEqual(value, "réponse B")
 
-        column_name = 'selection_str'
-        column = self.get_column(column_name)
-        value = converter.record_to_html(
-            self.cr, self.uid, column_name, record, column)
+        field_name = 'selection_str'
+        value = converter.record_to_html(self.cr, self.uid, field_name, record)
         self.assertEqual(value, "Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?")
 
 class TestHTMLExport(TestBasicExport):
index 7c46361..2d5e0c2 100644 (file)
@@ -202,9 +202,12 @@ class Field(object):
 
         :param related: sequence of field names
 
-        The value of some attributes from related fields are automatically taken
-        from the source field, when it makes sense. Examples are the attributes
-        `string` or `selection` on selection fields.
+        Some field attributes are automatically copied from the source field if
+        they are not redefined: `string`, `help`, `readonly`, `required` (only
+        if all fields in the sequence are required), `groups`, `digits`, `size`,
+        `translate`, `sanitize`, `selection`, `comodel_name`, `domain`,
+        `context`. All semantic-free attributes are copied from the source
+        field.
 
         By default, the values of related fields are not stored to the database.
         Add the attribute ``store=True`` to make it stored, just like computed
@@ -459,6 +462,11 @@ class Field(object):
             if not getattr(self, attr):
                 setattr(self, attr, getattr(field, prop))
 
+        for attr in field._free_attrs:
+            if attr not in self._free_attrs:
+                self._free_attrs.append(attr)
+                setattr(self, attr, getattr(field, attr))
+
         # special case for required: check if all fields are required
         if not self.store and not self.required:
             self.required = all(field.required for field in fields)
@@ -497,6 +505,11 @@ class Field(object):
     _related_readonly = property(attrgetter('readonly'))
     _related_groups = property(attrgetter('groups'))
 
+    @property
+    def base_field(self):
+        """ Return the base field of an inherited field, or `self`. """
+        return self.related_field if self.inherited else self
+
     #
     # Setup of non-related fields
     #
@@ -935,6 +948,11 @@ class Boolean(Field):
 
 class Integer(Field):
     type = 'integer'
+    group_operator = None       # operator for aggregating values
+
+    _related_group_operator = property(attrgetter('group_operator'))
+
+    _column_group_operator = property(attrgetter('group_operator'))
 
     def convert_to_cache(self, value, record, validate=True):
         if isinstance(value, dict):
@@ -963,6 +981,7 @@ class Float(Field):
     type = 'float'
     _digits = None              # digits argument passed to class initializer
     digits = None               # digits as computed by setup()
+    group_operator = None       # operator for aggregating values
 
     def __init__(self, string=None, digits=None, **kwargs):
         super(Float, self).__init__(string=string, _digits=digits, **kwargs)
@@ -981,11 +1000,13 @@ class Float(Field):
         self._setup_digits(env)
 
     _related_digits = property(attrgetter('digits'))
+    _related_group_operator = property(attrgetter('group_operator'))
 
     _description_digits = property(attrgetter('digits'))
 
     _column_digits = property(lambda self: not callable(self._digits) and self._digits)
     _column_digits_compute = property(lambda self: callable(self._digits) and self._digits)
+    _column_group_operator = property(attrgetter('group_operator'))
 
     def convert_to_cache(self, value, record, validate=True):
         # apply rounding here, otherwise value in cache may be wrong!
index 48274ea..de234af 100644 (file)
@@ -334,6 +334,7 @@ class BaseModel(object):
     # This is similar to _inherit_fields but:
     # 1. includes self fields,
     # 2. uses column_info instead of a triple.
+    # Warning: _all_columns is deprecated, use _fields instead
     _all_columns = {}
 
     _table = None
@@ -1141,22 +1142,23 @@ class BaseModel(object):
         * "id" is the External ID for the record
         * ".id" is the Database ID for the record
         """
-        columns = dict((k, v.column) for k, v in self._all_columns.iteritems())
-        # Fake columns to avoid special cases in extractor
-        columns[None] = fields.char('rec_name')
-        columns['id'] = fields.char('External ID')
-        columns['.id'] = fields.integer('Database ID')
+        from openerp.fields import Char, Integer
+        fields = dict(self._fields)
+        # Fake fields to avoid special cases in extractor
+        fields[None] = Char('rec_name')
+        fields['id'] = Char('External ID')
+        fields['.id'] = Integer('Database ID')
 
         # m2o fields can't be on multiple lines so exclude them from the
         # is_relational field rows filter, but special-case it later on to
         # be handled with relational fields (as it can have subfields)
-        is_relational = lambda field: columns[field]._type in ('one2many', 'many2many', 'many2one')
+        is_relational = lambda field: fields[field].relational
         get_o2m_values = itemgetter_tuple(
             [index for index, field in enumerate(fields_)
-                  if columns[field[0]]._type == 'one2many'])
+                   if fields[field[0]].type == 'one2many'])
         get_nono2m_values = itemgetter_tuple(
             [index for index, field in enumerate(fields_)
-                  if columns[field[0]]._type != 'one2many'])
+                   if fields[field[0]].type != 'one2many'])
         # Checks if the provided row has any non-empty non-relational field
         def only_o2m_values(row, f=get_nono2m_values, g=get_o2m_values):
             return any(g(row)) and not any(f(row))
@@ -1180,12 +1182,11 @@ class BaseModel(object):
             for relfield in set(
                     field[0] for field in fields_
                              if is_relational(field[0])):
-                column = columns[relfield]
                 # FIXME: how to not use _obj without relying on fields_get?
-                Model = self.pool[column._obj]
+                Model = self.pool[fields[relfield].comodel_name]
 
                 # get only cells for this sub-field, should be strictly
-                # non-empty, field path [None] is for name_get column
+                # non-empty, field path [None] is for name_get field
                 indices, subfields = zip(*((index, field[1:] or [None])
                                            for index, field in enumerate(fields_)
                                            if field[0] == relfield))
@@ -1215,13 +1216,13 @@ class BaseModel(object):
         """
         if context is None: context = {}
         Converter = self.pool['ir.fields.converter']
-        columns = dict((k, v.column) for k, v in self._all_columns.iteritems())
         Translation = self.pool['ir.translation']
+        fields = dict(self._fields)
         field_names = dict(
             (f, (Translation._get_source(cr, uid, self._name + ',' + f, 'field',
                                          context.get('lang'))
-                 or column.string))
-            for f, column in columns.iteritems())
+                 or field.string))
+            for f, field in fields.iteritems())
 
         convert = Converter.for_model(cr, uid, self, context=context)
 
@@ -1947,7 +1948,7 @@ class BaseModel(object):
             order_field = order_split[0]
             if order_field in groupby_fields:
 
-                if self._all_columns[order_field.split(':')[0]].column._type == 'many2one':
+                if self._fields[order_field.split(':')[0]].type == 'many2one':
                     order_clause = self._generate_order_by(order_part, query).replace('ORDER BY ', '')
                     if order_clause:
                         orderby_terms.append(order_clause)
@@ -1969,7 +1970,7 @@ class BaseModel(object):
             field name, type, time informations, qualified name, ...
         """
         split = gb.split(':')
-        field_type = self._all_columns[split[0]].column._type
+        field_type = self._fields[split[0]].type
         gb_function = split[1] if len(split) == 2 else None
         temporal = field_type in ('date', 'datetime')
         tz_convert = field_type == 'datetime' and context.get('tz') in pytz.all_timezones
@@ -2116,7 +2117,7 @@ class BaseModel(object):
             assert gb in fields, "Fields in 'groupby' must appear in the list of fields to read (perhaps it's missing in the list view?)"
             groupby_def = self._columns.get(gb) or (self._inherit_fields.get(gb) and self._inherit_fields.get(gb)[2])
             assert groupby_def and groupby_def._classic_write, "Fields in 'groupby' must be regular database-persisted fields (no function or related fields), or function fields with store=True"
-            if not (gb in self._all_columns):
+            if not (gb in self._fields):
                 # Don't allow arbitrary values, as this would be a SQL injection vector!
                 raise except_orm(_('Invalid group_by'),
                                  _('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(gb,))
@@ -2125,11 +2126,12 @@ class BaseModel(object):
             f for f in fields
             if f not in ('id', 'sequence')
             if f not in groupby_fields
-            if f in self._all_columns
-            if self._all_columns[f].column._type in ('integer', 'float')
-            if getattr(self._all_columns[f].column, '_classic_write')]
+            if f in self._fields
+            if self._fields[f].type in ('integer', 'float')
+            if getattr(self._fields[f].base_field.column, '_classic_write')
+        ]
 
-        field_formatter = lambda f: (self._all_columns[f].column.group_operator or 'sum', self._inherits_join_calc(f, query), f)
+        field_formatter = lambda f: (self._fields[f].group_operator or 'sum', self._inherits_join_calc(f, query), f)
         select_terms = ["%s(%s) AS %s" % field_formatter(f) for f in aggregated_fields]
 
         for gb in annotated_groupbys:
@@ -3506,7 +3508,7 @@ class BaseModel(object):
         if isinstance(ids, (int, long)):
             ids = [ids]
 
-        result_store = self._store_get_values(cr, uid, ids, self._all_columns.keys(), context)
+        result_store = self._store_get_values(cr, uid, ids, self._fields.keys(), context)
 
         # for recomputing new-style fields
         recs = self.browse(cr, uid, ids, context)
@@ -3748,9 +3750,9 @@ class BaseModel(object):
         direct = []
         totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
         for field in vals:
-            field_column = self._all_columns.get(field) and self._all_columns.get(field).column
-            if field_column and field_column.deprecated:
-                _logger.warning('Field %s.%s is deprecated: %s', self._name, field, field_column.deprecated)
+            ffield = self._fields.get(field)
+            if ffield and ffield.deprecated:
+                _logger.warning('Field %s.%s is deprecated: %s', self._name, field, ffield.deprecated)
             if field in self._columns:
                 if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
                     if (not totranslate) or not self._columns[field].translate:
@@ -4336,7 +4338,7 @@ class BaseModel(object):
         domain = domain[:]
         # if the object has a field named 'active', filter out all inactive
         # records unless they were explicitely asked for
-        if 'active' in self._all_columns and (active_test and context.get('active_test', True)):
+        if 'active' in self._fields and active_test and context.get('active_test', True):
             if domain:
                 # the item[0] trick below works for domain items and '&'/'|'/'!'
                 # operators too
@@ -4598,6 +4600,8 @@ class BaseModel(object):
 
         # build a black list of fields that should not be copied
         blacklist = set(MAGIC_COLUMNS + ['parent_left', 'parent_right'])
+        whitelist = set(name for name, field in self._fields.iteritems() if not field.inherited)
+
         def blacklist_given_fields(obj):
             # blacklist the fields that are given by inheritance
             for other, field_to_other in obj._inherits.items():
@@ -4605,19 +4609,19 @@ class BaseModel(object):
                 if field_to_other in default:
                     # all the fields of 'other' are given by the record: default[field_to_other],
                     # except the ones redefined in self
-                    blacklist.update(set(self.pool[other]._all_columns) - set(self._columns))
+                    blacklist.update(set(self.pool[other]._fields) - whitelist)
                 else:
                     blacklist_given_fields(self.pool[other])
             # blacklist deprecated fields
-            for name, field in obj._columns.items():
+            for name, field in obj._fields.iteritems():
                 if field.deprecated:
                     blacklist.add(name)
 
         blacklist_given_fields(self)
 
 
-        fields_to_copy = dict((f,fi) for f, fi in self._all_columns.iteritems()
-                                     if fi.column.copy
+        fields_to_copy = dict((f,fi) for f, fi in self._fields.iteritems()
+                                     if fi.copy
                                      if f not in default
                                      if f not in blacklist)
 
@@ -4628,19 +4632,18 @@ class BaseModel(object):
             raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name))
 
         res = dict(default)
-        for f, colinfo in fields_to_copy.iteritems():
-            field = colinfo.column
-            if field._type == 'many2one':
+        for f, field in fields_to_copy.iteritems():
+            if field.type == 'many2one':
                 res[f] = data[f] and data[f][0]
-            elif field._type == 'one2many':
-                other = self.pool[field._obj]
+            elif field.type == 'one2many':
+                other = self.pool[field.comodel_name]
                 # duplicate following the order of the ids because we'll rely on
                 # it later for copying translations in copy_translation()!
                 lines = [other.copy_data(cr, uid, line_id, context=context) for line_id in sorted(data[f])]
                 # the lines are duplicated using the wrong (old) parent, but then
                 # are reassigned to the correct one thanks to the (0, 0, ...)
                 res[f] = [(0, 0, line) for line in lines if line]
-            elif field._type == 'many2many':
+            elif field.type == 'many2many':
                 res[f] = [(6, 0, data[f])]
             else:
                 res[f] = data[f]
@@ -4658,16 +4661,14 @@ class BaseModel(object):
         seen_map[self._name].append(old_id)
 
         trans_obj = self.pool.get('ir.translation')
-        # TODO it seems fields_get can be replaced by _all_columns (no need for translation)
-        fields = self.fields_get(cr, uid, context=context)
 
-        for field_name, field_def in fields.items():
+        for field_name, field in self._fields.iteritems():
             # removing the lang to compare untranslated values
             context_wo_lang = dict(context, lang=None)
             old_record, new_record = self.browse(cr, uid, [old_id, new_id], context=context_wo_lang)
             # we must recursively copy the translations for o2o and o2m
-            if field_def['type'] == 'one2many':
-                target_obj = self.pool[field_def['relation']]
+            if field.type == 'one2many':
+                target_obj = self.pool[field.comodel_name]
                 # here we rely on the order of the ids to match the translations
                 # as foreseen in copy_data()
                 old_children = sorted(r.id for r in old_record[field_name])
@@ -4675,7 +4676,7 @@ class BaseModel(object):
                 for (old_child, new_child) in zip(old_children, new_children):
                     target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
             # and for translatable fields we keep them for copy
-            elif field_def.get('translate'):
+            elif getattr(field, 'translate', False):
                 if field_name in self._columns:
                     trans_name = self._name + "," + field_name
                     target_id = new_id
@@ -4799,13 +4800,14 @@ class BaseModel(object):
         :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
         """
 
-        field = self._all_columns.get(field_name)
-        field = field.column if field else None
-        if not field or field._type != 'many2many' or field._obj != self._name:
+        field = self._fields.get(field_name)
+        if not (field and field.type == 'many2many' and
+                field.comodel_name == self._name and field.store):
             # field must be a many2many on itself
             raise ValueError('invalid field_name: %r' % (field_name,))
 
-        query = 'SELECT distinct "%s" FROM "%s" WHERE "%s" IN %%s' % (field._id2, field._rel, field._id1)
+        query = 'SELECT distinct "%s" FROM "%s" WHERE "%s" IN %%s' % \
+                    (field.column2, field.relation, field.column1)
         ids_parent = ids[:]
         while ids_parent:
             ids_parent2 = []
@@ -4985,7 +4987,7 @@ class BaseModel(object):
                 result, record_ids = [], list(command[2])
 
         # read the records and apply the updates
-        other_model = self.pool[self._all_columns[field_name].column._obj]
+        other_model = self.pool[self._fields[field_name].comodel_name]
         for record in other_model.read(cr, uid, record_ids, fields=fields, context=context):
             record.update(updates.get(record['id'], {}))
             result.append(record)
index d42544e..8cf86ed 100644 (file)
@@ -1106,7 +1106,7 @@ class expression(object):
         # final sanity checks - should never fail
         assert operator in (TERM_OPERATORS + ('inselect', 'not inselect')), \
             "Invalid operator %r in domain term %r" % (operator, leaf)
-        assert leaf in (TRUE_LEAF, FALSE_LEAF) or left in model._all_columns \
+        assert leaf in (TRUE_LEAF, FALSE_LEAF) or left in model._fields \
             or left in MAGIC_COLUMNS, "Invalid field %r in domain term %r" % (left, leaf)
         assert not isinstance(right, BaseModel), \
             "Invalid value %r in domain term %r" % (right, leaf)
index 14e1018..e8d4166 100644 (file)
@@ -178,6 +178,7 @@ class _column(object):
             ('groups', self.groups),
             ('change_default', self.change_default),
             ('deprecated', self.deprecated),
+            ('group_operator', self.group_operator),
             ('size', self.size),
             ('ondelete', self.ondelete),
             ('translate', self.translate),
@@ -746,31 +747,32 @@ class one2many(_column):
             elif act[0] == 2:
                 obj.unlink(cr, user, [act[1]], context=context)
             elif act[0] == 3:
-                reverse_rel = obj._all_columns.get(self._fields_id)
-                assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
+                inverse_field = obj._fields.get(self._fields_id)
+                assert inverse_field, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
                 # if the model has on delete cascade, just delete the row
-                if reverse_rel.column.ondelete == "cascade":
+                if inverse_field.ondelete == "cascade":
                     obj.unlink(cr, user, [act[1]], context=context)
                 else:
                     cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
             elif act[0] == 4:
                 # table of the field (parent_model in case of inherit)
-                field_model = self._fields_id in obj.pool[self._obj]._columns and self._obj or obj.pool[self._obj]._all_columns[self._fields_id].parent_model
+                field = obj.pool[self._obj]._fields[self._fields_id]
+                field_model = field.base_field.model_name
                 field_table = obj.pool[field_model]._table
                 cr.execute("select 1 from {0} where id=%s and {1}=%s".format(field_table, self._fields_id), (act[1], id))
                 if not cr.fetchone():
                     # Must use write() to recompute parent_store structure if needed and check access rules
                     obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
             elif act[0] == 5:
-                reverse_rel = obj._all_columns.get(self._fields_id)
-                assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
+                inverse_field = obj._fields.get(self._fields_id)
+                assert inverse_field, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
                 # if the o2m has a static domain we must respect it when unlinking
                 domain = self._domain(obj) if callable(self._domain) else self._domain
                 extra_domain = domain or []
                 ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
                 # If the model has cascade deletion, we delete the rows because it is the intended behavior,
                 # otherwise we only nullify the reverse foreign key column.
-                if reverse_rel.column.ondelete == "cascade":
+                if inverse_field.ondelete == "cascade":
                     obj.unlink(cr, user, ids_to_unlink, context=context)
                 else:
                     obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
@@ -1611,12 +1613,12 @@ class property(function):
 
         res = {id: {} for id in ids}
         for prop_name in prop_names:
-            column = obj._all_columns[prop_name].column
+            field = obj._fields[prop_name]
             values = ir_property.get_multi(cr, uid, prop_name, obj._name, ids, context=context)
-            if column._type == 'many2one':
+            if field.type == 'many2one':
                 # name_get the non-null values as SUPERUSER_ID
                 vals = sum(set(filter(None, values.itervalues())),
-                           obj.pool[column._obj].browse(cr, uid, [], context=context))
+                           obj.pool[field.comodel_name].browse(cr, uid, [], context=context))
                 vals_name = dict(vals.sudo().name_get()) if vals else {}
                 for id, value in values.iteritems():
                     ng = False
index 7f56322..cd8e1b7 100644 (file)
@@ -727,8 +727,8 @@ form: module.record_id""" % (xml_id,)
             f_ref = field.get("ref",'').encode('utf-8')
             f_search = field.get("search",'').encode('utf-8')
             f_model = field.get("model",'').encode('utf-8')
-            if not f_model and model._all_columns.get(f_name):
-                f_model = model._all_columns[f_name].column._obj
+            if not f_model and f_name in model._fields:
+                f_model = model._fields[f_name].comodel_name
             f_use = field.get("use",'').encode('utf-8') or 'id'
             f_val = False
 
@@ -739,26 +739,24 @@ form: module.record_id""" % (xml_id,)
                 # browse the objects searched
                 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
                 # column definitions of the "local" object
-                _cols = self.pool[rec_model]._all_columns
+                _fields = self.pool[rec_model]._fields
                 # if the current field is many2many
-                if (f_name in _cols) and _cols[f_name].column._type=='many2many':
+                if (f_name in _fields) and _fields[f_name].type == 'many2many':
                     f_val = [(6, 0, map(lambda x: x[f_use], s))]
                 elif len(s):
                     # otherwise (we are probably in a many2one field),
                     # take the first element of the search
                     f_val = s[0][f_use]
             elif f_ref:
-                if f_name in model._all_columns \
-                          and model._all_columns[f_name].column._type == 'reference':
+                if f_name in model._fields and model._fields[f_name].type == 'reference':
                     val = self.model_id_get(cr, f_ref)
                     f_val = val[0] + ',' + str(val[1])
                 else:
                     f_val = self.id_get(cr, f_ref)
             else:
                 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
-                if f_name in model._all_columns:
-                    import openerp.osv as osv
-                    if isinstance(model._all_columns[f_name].column, osv.fields.integer):
+                if f_name in model._fields:
+                    if model._fields[f_name].type == 'integer':
                         f_val = int(f_val)
             res[f_name] = f_val
 
index eaca889..33e19e2 100644 (file)
@@ -482,15 +482,15 @@ class YamlInterpreter(object):
             record_dict[field_name] = field_value
         return record_dict
 
-    def process_ref(self, node, column=None):
+    def process_ref(self, node, field=None):
         assert node.search or node.id, '!ref node should have a `search` attribute or `id` attribute'
         if node.search:
             if node.model:
                 model_name = node.model
-            elif column:
-                model_name = column._obj
+            elif field:
+                model_name = field.comodel_name
             else:
-                raise YamlImportException('You need to give a model for the search, or a column to infer it.')
+                raise YamlImportException('You need to give a model for the search, or a field to infer it.')
             model = self.get_model(model_name)
             q = eval(node.search, self.eval_context)
             ids = model.search(self.cr, self.uid, q)
@@ -510,34 +510,32 @@ class YamlInterpreter(object):
 
     def _eval_field(self, model, field_name, expression, view_info=False, parent={}, default=True):
         # TODO this should be refactored as something like model.get_field() in bin/osv
-        if field_name in model._columns:
-            column = model._columns[field_name]
-        elif field_name in model._inherit_fields:
-            column = model._inherit_fields[field_name][2]
-        else:
+        if field_name not in model._fields:
             raise KeyError("Object '%s' does not contain field '%s'" % (model, field_name))
+        field = model._fields[field_name]
+
         if is_ref(expression):
-            elements = self.process_ref(expression, column)
-            if column._type in ("many2many", "one2many"):
+            elements = self.process_ref(expression, field)
+            if field.type in ("many2many", "one2many"):
                 value = [(6, 0, elements)]
             else: # many2one
                 if isinstance(elements, (list,tuple)):
                     value = self._get_first_result(elements)
                 else:
                     value = elements
-        elif column._type == "many2one":
+        elif field.type == "many2one":
             value = self.get_id(expression)
-        elif column._type == "one2many":
-            other_model = self.get_model(column._obj)
+        elif field.type == "one2many":
+            other_model = self.get_model(field.comodel_name)
             value = [(0, 0, self._create_record(other_model, fields, view_info, parent, default=default)) for fields in expression]
-        elif column._type == "many2many":
+        elif field.type == "many2many":
             ids = [self.get_id(xml_id) for xml_id in expression]
             value = [(6, 0, ids)]
-        elif column._type == "date" and is_string(expression):
+        elif field.type == "date" and is_string(expression):
             # enforce ISO format for string date values, to be locale-agnostic during tests
             time.strptime(expression, misc.DEFAULT_SERVER_DATE_FORMAT)
             value = expression
-        elif column._type == "datetime" and is_string(expression):
+        elif field.type == "datetime" and is_string(expression):
             # enforce ISO format for string datetime values, to be locale-agnostic during tests
             time.strptime(expression, misc.DEFAULT_SERVER_DATETIME_FORMAT)
             value = expression
@@ -546,7 +544,7 @@ class YamlInterpreter(object):
                 value = self.process_eval(expression)
             else:
                 value = expression
-            # raise YamlImportException('Unsupported column "%s" or value %s:%s' % (field_name, type(expression), expression))
+            # raise YamlImportException('Unsupported field "%s" or value %s:%s' % (field_name, type(expression), expression))
         return value
 
     def process_context(self, node):