diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index ac95da93534..2bf83f93e83 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -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] diff --git a/addons/crm/crm.py b/addons/crm/crm.py index f694f527957..983ae1b023f 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -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): diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index c5aa47fa244..866680eb766 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -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] diff --git a/addons/decimal_precision/decimal_precision.py b/addons/decimal_precision/decimal_precision.py index f06e88ddda1..945825ed3e6 100644 --- a/addons/decimal_precision/decimal_precision.py +++ b/addons/decimal_precision/decimal_precision.py @@ -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' diff --git a/addons/decimal_precision/tests/test_qweb_float.py b/addons/decimal_precision/tests/test_qweb_float.py index 93334170f37..c481dba2fd9 100644 --- a/addons/decimal_precision/tests/test_qweb_float.py +++ b/addons/decimal_precision/tests/test_qweb_float.py @@ -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') diff --git a/addons/edi/models/edi.py b/addons/edi/models/edi.py index f2ca15b90cb..fea26cd10d4 100644 --- a/addons/edi/models/edi.py +++ b/addons/edi/models/edi.py @@ -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 diff --git a/addons/hr/hr.py b/addons/hr/hr.py index efec3e6e2be..7e433e915d7 100644 --- a/addons/hr/hr.py +++ b/addons/hr/hr.py @@ -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 diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index f474a072c22..d849b4b6918 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -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) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 4356ee676ca..fa4d7cf5530 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -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 [] diff --git a/addons/mail/tests/test_mail_features.py b/addons/mail/tests/test_mail_features.py index 8726cf3c2a2..44b573f3e4a 100644 --- a/addons/mail/tests/test_mail_features.py +++ b/addons/mail/tests/test_mail_features.py @@ -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 = {} diff --git a/addons/mail/update.py b/addons/mail/update.py index 9096473350a..5644c45b027 100644 --- a/addons/mail/update.py +++ b/addons/mail/update.py @@ -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) diff --git a/addons/mass_mailing/controllers/main.py b/addons/mass_mailing/controllers/main.py index 2fcc6afd946..69317cb08c9 100644 --- a/addons/mass_mailing/controllers/main.py +++ b/addons/mass_mailing/controllers/main.py @@ -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) diff --git a/addons/mass_mailing/models/mail_thread.py b/addons/mass_mailing/models/mail_thread.py index 7bfce705043..e4732527f2d 100644 --- a/addons/mass_mailing/models/mail_thread.py +++ b/addons/mass_mailing/models/mail_thread.py @@ -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) diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index bee3e8528f9..8aed21d6382 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -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) diff --git a/addons/pad/pad.py b/addons/pad/pad.py index 68b19c3a1d6..3b84c791a41 100644 --- a/addons/pad/pad.py +++ b/addons/pad/pad.py @@ -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') diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py index d060c8fac14..aee891168b2 100644 --- a/addons/payment/models/payment_acquirer.py +++ b/addons/payment/models/payment_acquirer.py @@ -94,7 +94,7 @@ class PaymentAcquirer(osv.Model): """ If the field has 'required_if_provider=""' attribute, then it required if record.provider is . """ 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 diff --git a/addons/share/wizard/share_wizard.py b/addons/share/wizard/share_wizard.py index e4c50446ebf..6d3ba0ec31f 100644 --- a/addons/share/wizard/share_wizard.py +++ b/addons/share/wizard/share_wizard.py @@ -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): diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index c8081417e16..5c808c47b4e 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -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) diff --git a/addons/website/models/ir_qweb.py b/addons/website/models/ir_qweb.py index f0a75dc31db..3555b333a82 100644 --- a/addons/website/models/ir_qweb.py +++ b/addons/website/models/ir_qweb.py @@ -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[^]]+)/static/(?P.+)$') - 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): diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index ebb1753a189..5153f2287a5 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -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? diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 25dc303f074..f7f90459b62 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -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: diff --git a/addons/website/tests/test_converter.py b/addons/website/tests/test_converter.py index a1177a3970a..9ae81686f7f 100644 --- a/addons/website/tests/test_converter.py +++ b/addons/website/tests/test_converter.py @@ -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" diff --git a/addons/website_crm/controllers/main.py b/addons/website_crm/controllers/main.py index 18912a9d318..db1a6f98d62 100644 --- a/addons/website_crm/controllers/main.py +++ b/addons/website_crm/controllers/main.py @@ -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)) diff --git a/addons/website_mail/controllers/email_designer.py b/addons/website_mail/controllers/email_designer.py index 6116dd3cad7..157b8efa50f 100644 --- a/addons/website_mail/controllers/email_designer.py +++ b/addons/website_mail/controllers/email_designer.py @@ -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 diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index 518d3718d2e..168537f334c 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -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): diff --git a/openerp/addons/base/ir/ir_fields.py b/openerp/addons/base/ir/ir_fields.py index 494111d812c..77c0c830cba 100644 --- a/openerp/addons/base/ir/ir_fields.py +++ b/openerp/addons/base/ir/ir_fields.py @@ -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 + converter = getattr(self, '_%s_to_%s' % (typename, field.type), None) + if not converter: + return None + return functools.partial(converter, model, field) - return functools.partial( - converter, cr, uid, model, column, context=context) - - 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)) diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index 6a54e6f039c..39667b07633 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -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', '
\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 diff --git a/openerp/addons/base/ir/ir_translation.py b/openerp/addons/base/ir/ir_translation.py index 9e0dd5e3135..a16933b7f63 100644 --- a/openerp/addons/base/ir/ir_translation.py +++ b/openerp/addons/base/ir/ir_translation.py @@ -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 diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index 738ae3a660a..751326d3ba6 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -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') diff --git a/openerp/addons/base/ir/ir_values.py b/openerp/addons/base/ir/ir_values.py index 075612c6474..070edca5faf 100644 --- a/openerp/addons/base/ir/ir_values.py +++ b/openerp/addons/base/ir/ir_values.py @@ -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) diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 4f21ea00ff6..0f4f475b5fc 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -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): diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index e0c9667ebcd..9a7c780e0b3 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -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']: diff --git a/openerp/addons/base/tests/base_test.yml b/openerp/addons/base/tests/base_test.yml index a6f32a7c9d0..c7ab7f06ae9 100644 --- a/openerp/addons/base/tests/base_test.yml +++ b/openerp/addons/base/tests/base_test.yml @@ -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" - @@ -145,7 +146,8 @@ - !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" diff --git a/openerp/addons/base/tests/test_api.py b/openerp/addons/base/tests/test_api.py index 2be394e65dc..1f5203260e6 100644 --- a/openerp/addons/base/tests/test_api.py +++ b/openerp/addons/base/tests/test_api.py @@ -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): diff --git a/openerp/addons/test_converter/tests/test_html.py b/openerp/addons/test_converter/tests/test_html.py index 300fea5819d..aef7badfe3a 100644 --- a/openerp/addons/test_converter/tests/test_html.py +++ b/openerp/addons/test_converter/tests/test_html.py @@ -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, '' % ( 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): diff --git a/openerp/fields.py b/openerp/fields.py index 7c46361434a..2d5e0c2d6a4 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -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! diff --git a/openerp/models.py b/openerp/models.py index 48274ea5865..de234af48c7 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -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) diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py index d42544e14b8..8cf86edbaaa 100644 --- a/openerp/osv/expression.py +++ b/openerp/osv/expression.py @@ -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) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 14e101818e5..e8d416652bb 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -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 diff --git a/openerp/tools/convert.py b/openerp/tools/convert.py index 7f56322dc94..cd8e1b76b5b 100644 --- a/openerp/tools/convert.py +++ b/openerp/tools/convert.py @@ -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 diff --git a/openerp/tools/yaml_import.py b/openerp/tools/yaml_import.py index eaca88952a3..33e19e24dd3 100644 --- a/openerp/tools/yaml_import.py +++ b/openerp/tools/yaml_import.py @@ -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):