[IMP] use model._fields instead of model._all_columns to cover all fields

The old-api model._all_columns contains information about model._columns and
inherited columns.  This dictionary is missing new-api computed non-stored
fields, and the new field objects provide a more readable api...

This commit contains the following changes:

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

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

 - add attribute 'group_operator' on integer and float fields

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

 - base, decimal_precision, website: adapt qweb rendering methods to use fields
   instead of columns
This commit is contained in:
Raphael Collet 2014-11-03 16:00:50 +01:00
parent 0f52e22906
commit f2e4a10e1a
41 changed files with 466 additions and 474 deletions

View File

@ -131,9 +131,9 @@ class base_action_rule(osv.osv):
# modify records # modify records
values = {} 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) 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 values['user_id'] = action.act_user_id.id
if values: if values:
model.write(cr, uid, record_ids, values, context=context) 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 # determine when action should occur for the records
date_field = action.trg_date_id.name 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 get_record_dt = lambda record: record[date_field] or record.create_date
else: else:
get_record_dt = lambda record: record[date_field] get_record_dt = lambda record: record[date_field]

View File

@ -80,21 +80,22 @@ class crm_tracking_mixin(osv.AbstractModel):
return [('utm_campaign', 'campaign_id'), ('utm_source', 'source_id'), ('utm_medium', 'medium_id')] return [('utm_campaign', 'campaign_id'), ('utm_source', 'source_id'), ('utm_medium', 'medium_id')]
def tracking_get_values(self, cr, uid, vals, context=None): def tracking_get_values(self, cr, uid, vals, context=None):
for key, field in self.tracking_fields(): for key, fname in self.tracking_fields():
column = self._all_columns[field].column field = self._fields[fname]
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 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 column._type in ['many2one'] and isinstance(value, basestring): # if we receive a string for a many2one, we search / create the id if field.type == 'many2one' and isinstance(value, basestring):
# if we receive a string for a many2one, we search/create the id
if value: if value:
Model = self.pool[column._obj] Model = self.pool[field.comodel_name]
rel_id = Model.name_search(cr, uid, value, context=context) rel_id = Model.name_search(cr, uid, value, context=context)
if rel_id: if rel_id:
rel_id = rel_id[0][0] rel_id = rel_id[0][0]
else: else:
rel_id = Model.create(cr, uid, {'name': value}, context=context) rel_id = Model.create(cr, uid, {'name': value}, context=context)
vals[field] = rel_id vals[fname] = rel_id
# Here the code for others cases that many2one
else: else:
vals[field] = value # Here the code for others cases that many2one
vals[fname] = value
return vals return vals
def _get_default_track(self, cr, uid, field, context=None): def _get_default_track(self, cr, uid, field, context=None):

View File

@ -485,15 +485,14 @@ class crm_lead(format_address, osv.osv):
# Process the fields' values # Process the fields' values
data = {} data = {}
for field_name in fields: for field_name in fields:
field_info = self._all_columns.get(field_name) field = self._fields.get(field_name)
if field_info is None: if field is None:
continue continue
field = field_info.column if field.type in ('many2many', 'one2many'):
if field._type in ('many2many', 'one2many'):
continue continue
elif field._type == 'many2one': elif field.type == 'many2one':
data[field_name] = _get_first_not_null_id(field_name) # !! 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 data[field_name] = _concat_all(field_name) #not lost
else: else:
data[field_name] = _get_first_not_null(field_name) #not lost 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)) body.append("%s\n" % (title))
for field_name in fields: for field_name in fields:
field_info = self._all_columns.get(field_name) field = self._fields.get(field_name)
if field_info is None: if field is None:
continue continue
field = field_info.column
value = '' value = ''
if field._type == 'selection': if field.type == 'selection':
if hasattr(field.selection, '__call__'): if callable(field.selection):
key = field.selection(self, cr, uid, context=context) key = field.selection(self, cr, uid, context=context)
else: else:
key = field.selection key = field.selection
value = dict(key).get(lead[field_name], lead[field_name]) value = dict(key).get(lead[field_name], lead[field_name])
elif field._type == 'many2one': elif field.type == 'many2one':
if lead[field_name]: if lead[field_name]:
value = lead[field_name].name_get()[0][1] value = lead[field_name].name_get()[0][1]
elif field._type == 'many2many': elif field.type == 'many2many':
if lead[field_name]: if lead[field_name]:
for val in lead[field_name]: for val in lead[field_name]:
field_value = val.name_get()[0][1] field_value = val.name_get()[0][1]

View File

@ -85,14 +85,14 @@ class DecimalPrecisionFloat(orm.AbstractModel):
_inherit = 'ir.qweb.field.float' _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') dp = options and options.get('decimal_precision')
if dp: if dp:
return self.pool['decimal.precision'].precision_get( return self.pool['decimal.precision'].precision_get(
cr, uid, dp) cr, uid, dp)
return super(DecimalPrecisionFloat, self).precision( return super(DecimalPrecisionFloat, self).precision(
cr, uid, column, options=options, context=context) cr, uid, field, options=options, context=context)
class DecimalPrecisionTestModel(orm.Model): class DecimalPrecisionTestModel(orm.Model):
_name = 'decimal.precision.test' _name = 'decimal.precision.test'

View File

@ -8,10 +8,10 @@ class TestFloatExport(common.TransactionCase):
def get_converter(self, name): def get_converter(self, name):
converter = self.registry('ir.qweb.field.float') 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( 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): def test_basic_float(self):
converter = self.get_converter('float') converter = self.get_converter('float')

View File

@ -384,18 +384,18 @@ class EDIMixin(object):
results = [] results = []
for record in records: for record in records:
edi_dict = self.edi_metadata(cr, uid, [record], context=context)[0] edi_dict = self.edi_metadata(cr, uid, [record], context=context)[0]
for field in fields_to_export: for field_name in fields_to_export:
column = self._all_columns[field].column field = self._fields[field_name]
value = getattr(record, field) value = getattr(record, field_name)
if not value and value not in ('', 0): if not value and value not in ('', 0):
continue continue
elif column._type == 'many2one': elif field.type == 'many2one':
value = self.edi_m2o(cr, uid, value, context=context) 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) value = self.edi_m2m(cr, uid, value, context=context)
elif column._type == 'one2many': elif field.type == 'one2many':
value = self.edi_o2m(cr, uid, value, edi_struct=edi_struct.get(field, {}), context=context) value = self.edi_o2m(cr, uid, value, edi_struct=edi_struct.get(field_name, {}), context=context)
edi_dict[field] = value edi_dict[field_name] = value
results.append(edi_dict) results.append(edi_dict)
return results return results
@ -558,25 +558,24 @@ class EDIMixin(object):
# skip metadata and empty fields # skip metadata and empty fields
if field_name.startswith('__') or field_value is None or field_value is False: if field_name.startswith('__') or field_value is None or field_value is False:
continue continue
field_info = self._all_columns.get(field_name) field = self._fields.get(field_name)
if not field_info: if not field:
_logger.warning('Ignoring unknown field `%s` when importing `%s` EDI document.', field_name, self._name) _logger.warning('Ignoring unknown field `%s` when importing `%s` EDI document.', field_name, self._name)
continue continue
field = field_info.column
# skip function/related fields # 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)) _logger.warning("Unexpected function field value is found in '%s' EDI document: '%s'." % (self._name, field_name))
continue continue
relation_model = field._obj relation_model = field.comodel_name
if field._type == 'many2one': if field.type == 'many2one':
record_values[field_name] = self.edi_import_relation(cr, uid, relation_model, record_values[field_name] = self.edi_import_relation(cr, uid, relation_model,
field_value[1], field_value[0], field_value[1], field_value[0],
context=context) 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], record_values[field_name] = [self.edi_import_relation(cr, uid, relation_model, m2m_value[1],
m2m_value[0], context=context) m2m_value[0], context=context)
for m2m_value in field_value] 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 # must wait until parent report is imported, as the parent relationship
# is often required in o2m child records # is often required in o2m child records
o2m_todo[field_name] = field_value o2m_todo[field_name] = field_value
@ -591,11 +590,12 @@ class EDIMixin(object):
# process o2m values, connecting them to their parent on-the-fly # process o2m values, connecting them to their parent on-the-fly
for o2m_field, o2m_value in o2m_todo.iteritems(): for o2m_field, o2m_value in o2m_todo.iteritems():
field = self._all_columns[o2m_field].column field = self._fields[o2m_field]
dest_model = self.pool[field._obj] dest_model = self.pool[field.comodel_name]
dest_field = field.inverse_name
for o2m_line in o2m_value: for o2m_line in o2m_value:
# link to parent record: expects an (ext_id, name) pair # 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) dest_model.edi_import(cr, uid, o2m_line, context=context)
# process the attachments, if any # process the attachments, if any

View File

@ -347,8 +347,8 @@ class hr_employee(osv.osv):
if auto_follow_fields is None: if auto_follow_fields is None:
auto_follow_fields = ['user_id'] auto_follow_fields = ['user_id']
user_field_lst = [] user_field_lst = []
for name, column_info in self._all_columns.items(): for name, field in self._fields.items():
if name in auto_follow_fields and name in updated_fields and column_info.column._obj == 'res.users': if name in auto_follow_fields and name in updated_fields and field.comodel_name == 'res.users':
user_field_lst.append(name) user_field_lst.append(name)
return user_field_lst return user_field_lst

View File

@ -73,7 +73,7 @@ class mail_mail(osv.Model):
def default_get(self, cr, uid, fields, context=None): def default_get(self, cr, uid, fields, context=None):
# protection for `default_type` values leaking from menu action context (e.g. for invoices) # 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 # 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) context = dict(context, default_type=None)
return super(mail_mail, self).default_get(cr, uid, fields, context=context) return super(mail_mail, self).default_get(cr, uid, fields, context=context)

View File

@ -460,12 +460,12 @@ class mail_thread(osv.AbstractModel):
def _get_tracked_fields(self, cr, uid, updated_fields, context=None): def _get_tracked_fields(self, cr, uid, updated_fields, context=None):
""" Return a structure of tracked fields for the current model. """ Return a structure of tracked fields for the current model.
:param list updated_fields: modified field names :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 always tracked fields and modified on_change fields
""" """
tracked_fields = [] tracked_fields = []
for name, column_info in self._all_columns.items(): for name, field in self._fields.items():
visibility = getattr(column_info.column, 'track_visibility', False) visibility = getattr(field, 'track_visibility', False)
if visibility == 'always' or (visibility == 'onchange' and name in updated_fields) or name in self._track: if visibility == 'always' or (visibility == 'onchange' and name in updated_fields) or name in self._track:
tracked_fields.append(name) 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}} # generate tracked_values data structure: {'col_name': {col_info, new_value, old_value}}
for col_name, col_info in tracked_fields.items(): for col_name, col_info in tracked_fields.items():
field = self._fields[col_name]
initial_value = initial[col_name] initial_value = initial[col_name]
record_value = getattr(browse_record, 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': if record_value == initial_value and getattr(field, 'track_visibility', None) == 'always':
tracked_values[col_name] = dict(col_info=col_info['string'], tracked_values[col_name] = dict(
new_value=convert_for_display(record_value, col_info)) 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 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']: if getattr(field, 'track_visibility', None) in ['always', 'onchange']:
tracked_values[col_name] = dict(col_info=col_info['string'], tracked_values[col_name] = dict(
old_value=convert_for_display(initial_value, col_info), col_info=col_info['string'],
new_value=convert_for_display(record_value, col_info)) old_value=convert_for_display(initial_value, col_info),
new_value=convert_for_display(record_value, col_info),
)
if col_name in tracked_fields: if col_name in tracked_fields:
changes.add(col_name) changes.add(col_name)
if not changes: if not changes:
@ -681,11 +686,11 @@ class mail_thread(osv.AbstractModel):
res = {} res = {}
for record in self.browse(cr, SUPERUSER_ID, ids, context=context): for record in self.browse(cr, SUPERUSER_ID, ids, context=context):
recipient_ids, email_to, email_cc = set(), False, False 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) 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 email_to = record.email_from
elif 'email' in self._all_columns: elif 'email' in self._fields:
email_to = record.email email_to = record.email
res[record.id] = {'partner_ids': list(recipient_ids), 'email_to': email_to, 'email_cc': email_cc} res[record.id] = {'partner_ids': list(recipient_ids), 'email_to': email_to, 'email_cc': email_cc}
return res return res
@ -1398,11 +1403,11 @@ class mail_thread(osv.AbstractModel):
""" Returns suggested recipients for ids. Those are a list of """ Returns suggested recipients for ids. Those are a list of
tuple (partner_id, partner_name, reason), to be managed by Chatter. """ tuple (partner_id, partner_name, reason), to be managed by Chatter. """
result = dict((res_id, []) for res_id in ids) 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 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: if not obj.user_id or not obj.user_id.partner_id:
continue 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 return result
def _find_partner_from_emails(self, cr, uid, id, emails, model=None, context=None, check_followers=True): 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: if auto_follow_fields is None:
auto_follow_fields = ['user_id'] auto_follow_fields = ['user_id']
user_field_lst = [] user_field_lst = []
for name, column_info in self._all_columns.items(): for name, field in self._fields.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': 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) user_field_lst.append(name)
return user_field_lst 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 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 # 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) user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context)
if any(group.is_portal for group in user.groups_id): if any(group.is_portal for group in user.groups_id):
return [] return []

View File

@ -827,7 +827,9 @@ class test_mail(TestMail):
self.ir_model_data.create(cr, uid, {'name': 'mt_group_public', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_group_public_id}) 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) # 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': { 'public': {
'mail.mt_private': lambda self, cr, uid, obj, ctx=None: obj.public == 'private', '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, 'mail.mt_group_public': lambda self, cr, uid, obj, ctx=None: True,
}, },
} }
public_col = self.mail_group._columns.get('public') visibility = {'public': 'onchange', 'name': 'always', 'group_public_id': 'onchange'}
name_col = self.mail_group._columns.get('name') for key in visibility:
group_public_col = self.mail_group._columns.get('group_public_id') self.assertFalse(hasattr(getattr(cls, key), 'track_visibility'))
public_col.track_visibility = 'onchange' getattr(cls, key).track_visibility = visibility[key]
name_col.track_visibility = 'always'
group_public_col.track_visibility = 'onchange' @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 # 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'}) 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.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'description': 'Dummy'})
self.group_pigs.refresh() self.group_pigs.refresh()
self.assertEqual(len(self.group_pigs.message_ids), 6, 'tracked: No message should have been produced') 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 = {}

View File

@ -32,7 +32,7 @@ class publisher_warranty_contract(AbstractModel):
nbr_active_users = user_count([("login_date", ">=", limit_date_str)]) nbr_active_users = user_count([("login_date", ">=", limit_date_str)])
nbr_share_users = 0 nbr_share_users = 0
nbr_active_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_share_users = user_count([("share", "=", True)])
nbr_active_share_users = user_count([("share", "=", True), ("login_date", ">=", limit_date_str)]) nbr_active_share_users = user_count([("share", "=", True), ("login_date", ">=", limit_date_str)])
user = Users.browse(cr, uid, uid) user = Users.browse(cr, uid, uid)

View File

@ -31,14 +31,15 @@ class MassMailController(http.Controller):
request.registry[mailing.mailing_model].write(cr, SUPERUSER_ID, record_ids, {'opt_out': True}, context=context) request.registry[mailing.mailing_model].write(cr, SUPERUSER_ID, record_ids, {'opt_out': True}, context=context)
else: else:
email_fname = None 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' email_fname = 'email_from'
elif 'email' in request.registry[mailing.mailing_model]._all_columns: elif 'email' in model._fields:
email_fname = 'email' email_fname = 'email'
if email_fname: if email_fname:
record_ids = request.registry[mailing.mailing_model].search(cr, SUPERUSER_ID, [('id', '=', res_id), (email_fname, 'ilike', email)], context=context) record_ids = 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: if 'opt_out' in model._fields:
request.registry[mailing.mailing_model].write(cr, SUPERUSER_ID, record_ids, {'opt_out': True}, context=context) model.write(cr, SUPERUSER_ID, record_ids, {'opt_out': True}, context=context)
return 'OK' return 'OK'
@http.route(['/website_mass_mailing/is_subscriber'], type='json', auth="public", website=True) @http.route(['/website_mass_mailing/is_subscriber'], type='json', auth="public", website=True)

View File

@ -77,7 +77,7 @@ class MailThread(osv.AbstractModel):
Mail Returned to Sender) is received for an existing thread. The default Mail Returned to Sender) is received for an existing thread. The default
behavior is to check is an integer ``message_bounce`` column exists. behavior is to check is an integer ``message_bounce`` column exists.
If it is the case, its content is incremented. """ 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): for obj in self.browse(cr, uid, ids, context=context):
self.write(cr, uid, [obj.id], {'message_bounce': obj.message_bounce + 1}, context=context) self.write(cr, uid, [obj.id], {'message_bounce': obj.message_bounce + 1}, context=context)

View File

@ -276,8 +276,8 @@ class MassMailing(osv.Model):
'tooltip': ustr((date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y')), 'tooltip': ustr((date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y')),
} for i in range(0, self._period_number)] } for i in range(0, self._period_number)]
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context) 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]) field = obj._fields.get(groupby_field.split(':')[0])
pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field_col_info.column._type == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field.type == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
for group in group_obj: for group in group_obj:
group_begin_date = datetime.strptime(group['__domain'][0][2], pattern).date() group_begin_date = datetime.strptime(group['__domain'][0][2], pattern).date()
timedelta = relativedelta.relativedelta(group_begin_date, date_begin) timedelta = relativedelta.relativedelta(group_begin_date, date_begin)

View File

@ -52,8 +52,8 @@ class pad_common(osv.osv_memory):
#get attr on the field model #get attr on the field model
model = self.pool[context["model"]] model = self.pool[context["model"]]
field = model._all_columns[context['field_name']] field = model._fields[context['field_name']]
real_field = field.column.pad_content_field real_field = field.pad_content_field
#get content of the real field #get content of the real field
for record in model.browse(cr, uid, [context["object_id"]]): 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 # Set the pad content in vals
def _set_pad_value(self, cr, uid, vals, context=None): def _set_pad_value(self, cr, uid, vals, context=None):
for k,v in vals.items(): for k,v in vals.items():
field = self._all_columns[k].column field = self._fields[k]
if hasattr(field,'pad_content_field'): if hasattr(field,'pad_content_field'):
vals[field.pad_content_field] = self.pad_get_content(cr, uid, v, context=context) vals[field.pad_content_field] = self.pad_get_content(cr, uid, v, context=context)
def copy(self, cr, uid, id, default=None, context=None): def copy(self, cr, uid, id, default=None, context=None):
if not default: if not default:
default = {} default = {}
for k, v in self._all_columns.iteritems(): for k, field in self._fields.iteritems():
field = v.column
if hasattr(field,'pad_content_field'): if hasattr(field,'pad_content_field'):
pad = self.pad_generate_url(cr, uid, context) pad = self.pad_generate_url(cr, uid, context)
default[k] = pad.get('url') default[k] = pad.get('url')

View File

@ -94,7 +94,7 @@ class PaymentAcquirer(osv.Model):
""" If the field has 'required_if_provider="<provider>"' attribute, then it """ If the field has 'required_if_provider="<provider>"' attribute, then it
required if record.provider is <provider>. """ required if record.provider is <provider>. """
for acquirer in self.browse(cr, uid, ids, context=context): 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 False
return True return True

View File

@ -384,38 +384,37 @@ class share_wizard(osv.TransientModel):
models = [x[1].model for x in relation_fields] models = [x[1].model for x in relation_fields]
model_obj = self.pool.get('ir.model') model_obj = self.pool.get('ir.model')
model_osv = self.pool[model.model] model_osv = self.pool[model.model]
for colinfo in model_osv._all_columns.itervalues(): for field in model_osv._fields.itervalues():
coldef = colinfo.column ftype = field.type
coltype = coldef._type
relation_field = None relation_field = None
if coltype in ttypes and colinfo.column._obj not in models: if ftype in ttypes and field.comodel_name not in models:
relation_model_id = model_obj.search(cr, UID_ROOT, [('model','=',coldef._obj)])[0] 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_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 #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) # don't record reverse path if it's not a real m2o (that happens, but rarely)
dest_model_ci = relation_osv._all_columns dest_fields = relation_osv._fields
reverse_rel = coldef._fields_id reverse_rel = field.inverse_name
if reverse_rel in dest_model_ci and dest_model_ci[reverse_rel].column._type == 'many2one': 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 relation_field = ('%s.%s'%(reverse_rel, suffix)) if suffix else reverse_rel
local_rel_fields.append((relation_field, relation_model_browse)) local_rel_fields.append((relation_field, relation_model_browse))
for parent in relation_osv._inherits: for parent in relation_osv._inherits:
if parent not in models: if parent not in models:
parent_model = self.pool[parent] 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, parent_model_browse = model_obj.browse(cr, UID_ROOT,
model_obj.search(cr, UID_ROOT, [('model','=',parent)]))[0] 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 # inverse relationship is available in the parent
local_rel_fields.append((relation_field, parent_model_browse)) local_rel_fields.append((relation_field, parent_model_browse))
else: else:
# TODO: can we setup a proper rule to restrict inherited models # TODO: can we setup a proper rule to restrict inherited models
# in case the parent does not contain the reverse m2o? # in case the parent does not contain the reverse m2o?
local_rel_fields.append((None, parent_model_browse)) 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, 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 return local_rel_fields
def _get_relationship_classes(self, cr, uid, model, context=None): def _get_relationship_classes(self, cr, uid, model, context=None):

View File

@ -367,7 +367,7 @@ class Website(openerp.addons.web.controllers.main.Home):
obj = _object.browse(request.cr, request.uid, _id) obj = _object.browse(request.cr, request.uid, _id)
values = {} values = {}
if 'website_published' in _object._all_columns: if 'website_published' in _object._fields:
values['website_published'] = not obj.website_published values['website_published'] = not obj.website_published
_object.write(request.cr, request.uid, [_id], _object.write(request.cr, request.uid, [_id],
values, context=request.context) values, context=request.context)

View File

@ -76,12 +76,12 @@ class Field(orm.AbstractModel):
def attributes(self, cr, uid, field_name, record, options, def attributes(self, cr, uid, field_name, record, options,
source_element, g_att, t_att, qweb_context, context=None): source_element, g_att, t_att, qweb_context, context=None):
if options is None: options = {} if options is None: options = {}
column = record._model._all_columns[field_name].column field = record._model._fields[field_name]
attrs = [('data-oe-translate', 1 if column.translate else 0)] attrs = [('data-oe-translate', 1 if getattr(field, 'translate', False) else 0)]
placeholder = options.get('placeholder') \ placeholder = options.get('placeholder') \
or source_element.get('placeholder') \ or source_element.get('placeholder') \
or getattr(column, 'placeholder', None) or getattr(field, 'placeholder', None)
if placeholder: if placeholder:
attrs.append(('placeholder', placeholder)) attrs.append(('placeholder', placeholder))
@ -95,7 +95,7 @@ class Field(orm.AbstractModel):
def value_from_string(self, value): def value_from_string(self, value):
return 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()) return self.value_from_string(element.text_content().strip())
def qweb_object(self): def qweb_object(self):
@ -111,7 +111,7 @@ class Float(orm.AbstractModel):
_name = 'website.qweb.field.float' _name = 'website.qweb.field.float'
_inherit = ['website.qweb.field', 'ir.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) lang = self.user_lang(cr, uid, context=context)
value = element.text_content().strip() value = element.text_content().strip()
@ -142,7 +142,7 @@ class Date(orm.AbstractModel):
qweb_context, context=None) qweb_context, context=None)
return itertools.chain(attrs, [('data-oe-original', record[field_name])]) 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() value = element.text_content().strip()
if not value: return False if not value: return False
@ -173,7 +173,7 @@ class DateTime(orm.AbstractModel):
('data-oe-original', value) ('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 = {} if context is None: context = {}
value = element.text_content().strip() value = element.text_content().strip()
if not value: return False if not value: return False
@ -204,16 +204,17 @@ class Text(orm.AbstractModel):
_name = 'website.qweb.field.text' _name = 'website.qweb.field.text'
_inherit = ['website.qweb.field', 'ir.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) return html_to_text(element)
class Selection(orm.AbstractModel): class Selection(orm.AbstractModel):
_name = 'website.qweb.field.selection' _name = 'website.qweb.field.selection'
_inherit = ['website.qweb.field', 'ir.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() 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: for k, v in selection:
if isinstance(v, str): if isinstance(v, str):
v = ustr(v) v = ustr(v)
@ -227,11 +228,11 @@ class ManyToOne(orm.AbstractModel):
_name = 'website.qweb.field.many2one' _name = 'website.qweb.field.many2one'
_inherit = ['website.qweb.field', 'ir.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 # FIXME: layering violations all the things
Model = self.pool[element.get('data-oe-model')] Model = self.pool[element.get('data-oe-model')]
M2O = self.pool[column._obj] M2O = self.pool[field.comodel_name]
field = element.get('data-oe-field') field_name = element.get('data-oe-field')
id = int(element.get('data-oe-id')) id = int(element.get('data-oe-id'))
# FIXME: weird things are going to happen for char-type _rec_name # FIXME: weird things are going to happen for char-type _rec_name
value = html_to_text(element) value = html_to_text(element)
@ -239,16 +240,16 @@ class ManyToOne(orm.AbstractModel):
# if anything blows up, just ignore it and bail # if anything blows up, just ignore it and bail
try: try:
# get parent record # get parent record
[obj] = Model.read(cr, uid, [id], [field]) [obj] = Model.read(cr, uid, [id], [field_name])
# get m2o record id # get m2o record id
(m2o_id, _) = obj[field] (m2o_id, _) = obj[field_name]
# assume _rec_name and write directly to it # assume _rec_name and write directly to it
M2O.write(cr, uid, [m2o_id], { M2O.write(cr, uid, [m2o_id], {
M2O._rec_name: value M2O._rec_name: value
}, context=context) }, context=context)
except: except:
logger.exception("Could not save %r to m2o field %s of model %s", 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 # not necessary, but might as well be explicit about it
return None return None
@ -257,7 +258,7 @@ class HTML(orm.AbstractModel):
_name = 'website.qweb.field.html' _name = 'website.qweb.field.html'
_inherit = ['website.qweb.field', 'ir.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 = [] content = []
if element.text: content.append(element.text) if element.text: content.append(element.text)
content.extend(html.tostring(child) content.extend(html.tostring(child)
@ -286,7 +287,7 @@ class Image(orm.AbstractModel):
cr, uid, field_name, record, options, cr, uid, field_name, record, options,
source_element, t_att, g_att, qweb_context, context=context) 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 = {} if options is None: options = {}
aclasses = ['img', 'img-responsive'] + options.get('class', '').split() aclasses = ['img', 'img-responsive'] + options.get('class', '').split()
classes = ' '.join(itertools.imap(escape, aclasses)) classes = ' '.join(itertools.imap(escape, aclasses))
@ -301,7 +302,8 @@ class Image(orm.AbstractModel):
return ir_qweb.HTMLSafe(img) return ir_qweb.HTMLSafe(img)
local_url_re = re.compile(r'^/(?P<module>[^]]+)/static/(?P<rest>.+)$') local_url_re = re.compile(r'^/(?P<module>[^]]+)/static/(?P<rest>.+)$')
def from_html(self, cr, uid, model, column, element, context=None):
def from_html(self, cr, uid, model, field, element, context=None):
url = element.find('img').get('src') url = element.find('img').get('src')
url_object = urlparse.urlsplit(url) url_object = urlparse.urlsplit(url)
@ -373,7 +375,7 @@ class Monetary(orm.AbstractModel):
_name = 'website.qweb.field.monetary' _name = 'website.qweb.field.monetary'
_inherit = ['website.qweb.field', 'ir.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) lang = self.user_lang(cr, uid, context=context)
value = element.find('span').text.strip() value = element.find('span').text.strip()
@ -396,7 +398,7 @@ class Duration(orm.AbstractModel):
qweb_context, context=None) qweb_context, context=None)
return itertools.chain(attrs, [('data-oe-original', record[field_name])]) 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() value = element.text_content().strip()
# non-localized value # non-localized value
@ -417,7 +419,7 @@ class Contact(orm.AbstractModel):
_name = 'website.qweb.field.contact' _name = 'website.qweb.field.contact'
_inherit = ['ir.qweb.field.contact', 'website.qweb.field.many2one'] _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 return None
class QwebView(orm.AbstractModel): class QwebView(orm.AbstractModel):

View File

@ -87,10 +87,8 @@ class view(osv.osv):
Model = self.pool[el.get('data-oe-model')] Model = self.pool[el.get('data-oe-model')]
field = el.get('data-oe-field') 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'))
converter = self.pool['website.qweb'].get_converter_for( value = converter.from_html(cr, uid, Model, Model._fields[field], el)
el.get('data-oe-type'))
value = converter.from_html(cr, uid, Model, column, el)
if value is not None: if value is not None:
# TODO: batch writes? # TODO: batch writes?

View File

@ -535,7 +535,7 @@ class website(osv.osv):
ids = Model.search(cr, uid, ids = Model.search(cr, uid,
[('id', '=', id)], context=context) [('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, ids = Model.search(cr, openerp.SUPERUSER_ID,
[('id', '=', id), ('website_published', '=', True)], context=context) [('id', '=', id), ('website_published', '=', True)], context=context)
if not ids: if not ids:

View File

@ -157,12 +157,11 @@ class TestConvertBack(common.TransactionCase):
element = html.fromstring( element = html.fromstring(
rendered, parser=html.HTMLParser(encoding='utf-8')) rendered, parser=html.HTMLParser(encoding='utf-8'))
column = Model._all_columns[field].column
converter = self.registry('website.qweb').get_converter_for( converter = self.registry('website.qweb').get_converter_for(
element.get('data-oe-type')) element.get('data-oe-type'))
value_back = converter.from_html( 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): if isinstance(expected, str):
expected = expected.decode('utf-8') expected = expected.decode('utf-8')
@ -240,12 +239,11 @@ class TestConvertBack(common.TransactionCase):
# emulate edition # emulate edition
element.text = "New content" element.text = "New content"
column = Model._all_columns[field].column
converter = self.registry('website.qweb').get_converter_for( converter = self.registry('website.qweb').get_converter_for(
element.get('data-oe-type')) element.get('data-oe-type'))
value_back = converter.from_html( value_back = converter.from_html(
self.cr, self.uid, model, column, element) self.cr, self.uid, model, Model._fields[field], element)
self.assertIsNone( self.assertIsNone(
value_back, "the m2o converter should return None to avoid spurious" value_back, "the m2o converter should return None to avoid spurious"

View File

@ -62,7 +62,7 @@ class contactus(http.Controller):
for field_name, field_value in kwargs.items(): for field_name, field_value in kwargs.items():
if hasattr(field_value, 'filename'): if hasattr(field_value, 'filename'):
post_file.append(field_value) 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 values[field_name] = field_value
elif field_name not in _TECHNICAL: # allow to add some free fields or blacklisted field like ID 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)) post_description.append("%s: %s" % (field_name, field_value))

View File

@ -13,10 +13,10 @@ class WebsiteEmailDesigner(http.Controller):
def index(self, model, res_id, template_model=None, **kw): def index(self, model, res_id, template_model=None, **kw):
if not model or not model in request.registry or not res_id: if not model or not model in request.registry or not res_id:
return request.redirect('/') return request.redirect('/')
model_cols = request.registry[model]._all_columns model_fields = request.registry[model]._fields
if 'body' not in model_cols and 'body_html' not in model_cols or \ if 'body' not in model_fields and 'body_html' not in model_fields or \
'email' not in model_cols and 'email_from' not in model_cols or \ 'email' not in model_fields and 'email_from' not in model_fields or \
'name' not in model_cols and 'subject' not in model_cols: 'name' not in model_fields and 'subject' not in model_fields:
return request.redirect('/') return request.redirect('/')
res_id = int(res_id) res_id = int(res_id)
obj_ids = request.registry[model].exists(request.cr, request.uid, [res_id], context=request.context) 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 # try to find fields to display / edit -> as t-field is static, we have to limit
# the available fields to a given subset # the available fields to a given subset
email_from_field = 'email' email_from_field = 'email'
if 'email_from' in model_cols: if 'email_from' in model_fields:
email_from_field = 'email_from' email_from_field = 'email_from'
subject_field = 'name' subject_field = 'name'
if 'subject' in model_cols: if 'subject' in model_fields:
subject_field = 'subject' subject_field = 'subject'
body_field = 'body' body_field = 'body'
if 'body_html' in model_cols: if 'body_html' in model_fields:
body_field = 'body_html' body_field = 'body_html'
cr, uid, context = request.cr, request.uid, request.context cr, uid, context = request.cr, request.uid, request.context

View File

@ -616,16 +616,16 @@ class ir_actions_server(osv.osv):
# analyze path # analyze path
while path: while path:
step = path.pop(0) step = path.pop(0)
column_info = self.pool[current_model_name]._all_columns.get(step) field = self.pool[current_model_name]._fields.get(step)
if not column_info: 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)) 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 ftype = field.type
if column_type not in ['many2one', 'int']: 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, column_type)) 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 column_type == 'int' and path: 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)) 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': if ftype == 'many2one':
current_model_name = column_info.column._obj current_model_name = field.comodel_name
return (True, current_model_name, None) return (True, current_model_name, None)
def _check_write_expression(self, cr, uid, ids, context=None): def _check_write_expression(self, cr, uid, ids, context=None):

View File

@ -8,12 +8,8 @@ import time
import psycopg2 import psycopg2
import pytz import pytz
from openerp.osv import orm from openerp import models, api, _
from openerp.tools.translate import _ from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, ustr
from openerp.tools.misc import DEFAULT_SERVER_DATE_FORMAT,\
DEFAULT_SERVER_DATETIME_FORMAT,\
ustr
from openerp.tools import html_sanitize
REFERENCING_FIELDS = set([None, 'id', '.id']) REFERENCING_FIELDS = set([None, 'id', '.id'])
def only_ref_fields(record): def only_ref_fields(record):
@ -37,33 +33,12 @@ class ImportWarning(Warning):
class ConversionNotFound(ValueError): pass 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): class ir_fields_converter(models.Model):
return getattr(self._column, item)
class ir_fields_converter(orm.Model):
_name = 'ir.fields.converter' _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 """ Returns a converter object for the model. A converter is a
callable taking a record-ish (a dictionary representing an openerp callable taking a record-ish (a dictionary representing an openerp
record with values of typetag ``fromtype``) and returning a converted record with values of typetag ``fromtype``) and returning a converted
@ -73,17 +48,19 @@ class ir_fields_converter(orm.Model):
:returns: a converter callable :returns: a converter callable
:rtype: (record: dict, logger: (field, error) -> None) -> dict :rtype: (record: dict, logger: (field, error) -> None) -> dict
""" """
columns = dict( # make sure model is new api
(k, ColumnWrapper(v.column, cr, uid, self.pool, fromtype, context)) model = self.env[model._name]
for k, v in model._all_columns.iteritems())
converters = dict( converters = {
(k, self.to_field(cr, uid, model, column, fromtype, context)) name: self.to_field(model, field, fromtype)
for k, column in columns.iteritems()) for name, field in model._fields.iteritems()
}
def fn(record, log): def fn(record, log):
converted = {} converted = {}
for field, value in record.iteritems(): for field, value in record.iteritems():
if field in (None, 'id', '.id'): continue if field in (None, 'id', '.id'):
continue
if not value: if not value:
converted[field] = False converted[field] = False
continue continue
@ -97,20 +74,21 @@ class ir_fields_converter(orm.Model):
log(field, w) log(field, w)
except ValueError, e: except ValueError, e:
log(field, e) log(field, e)
return converted return converted
return fn return fn
def to_field(self, cr, uid, model, column, fromtype=str, context=None): @api.model
""" Fetches a converter for the provided column object, from the def to_field(self, model, field, fromtype=str):
""" Fetches a converter for the provided field object, from the
specified type. specified type.
A converter is simply a callable taking a value of type ``fromtype`` 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 (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 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 Converter callables can either return a value and a list of warnings
to their caller or raise ``ValueError``, which will be interpreted as a 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 it returns. The handling of a warning at the upper levels is the same
as ``ValueError`` above. as ``ValueError`` above.
:param column: column object to generate a value for :param field: field object to generate a value for
:type column: :class:`fields._column` :type field: :class:`openerp.fields.Field`
:param fromtype: type to convert to something fitting for ``column`` :param fromtype: type to convert to something fitting for ``field``
:type fromtype: type | str :type fromtype: type | str
:param context: openerp request context :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 :rtype: Callable | None
""" """
assert isinstance(fromtype, (type, str)) assert isinstance(fromtype, (type, str))
# FIXME: return None # FIXME: return None
typename = fromtype.__name__ if isinstance(fromtype, type) else fromtype typename = fromtype.__name__ if isinstance(fromtype, type) else fromtype
converter = getattr( converter = getattr(self, '_%s_to_%s' % (typename, field.type), None)
self, '_%s_to_%s' % (typename, column._type), None) if not converter:
if not converter: return None return None
return functools.partial(converter, model, field)
return functools.partial( @api.model
converter, cr, uid, model, column, context=context) def _str_to_boolean(self, model, field, value):
def _str_to_boolean(self, cr, uid, model, column, value, context=None):
# all translatables used for booleans # all translatables used for booleans
true, yes, false, no = _(u"true"), _(u"yes"), _(u"false"), _(u"no") true, yes, false, no = _(u"true"), _(u"yes"), _(u"false"), _(u"no")
# potentially broken casefolding? What about locales? # potentially broken casefolding? What about locales?
trues = set(word.lower() for word in itertools.chain( trues = set(word.lower() for word in itertools.chain(
[u'1', u"true", u"yes"], # don't use potentially translated values [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(['code'], u"true"),
self._get_translations(cr, uid, ['code'], u"yes", context=context), 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? # potentially broken casefolding? What about locales?
falses = set(word.lower() for word in itertools.chain( falses = set(word.lower() for word in itertools.chain(
[u'', u"0", u"false", u"no"], [u'', u"0", u"false", u"no"],
self._get_translations(cr, uid, ['code'], u"false", context=context), self._get_translations(['code'], u"false"),
self._get_translations(cr, uid, ['code'], u"no", context=context), self._get_translations(['code'], u"no"),
)) ))
if value.lower() in falses: return False, [] if value.lower() in falses:
return False, []
return True, [ImportWarning( return True, [ImportWarning(
_(u"Unknown value '%s' for boolean field '%%(field)s', assuming '%s'") _(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") '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: try:
return int(value), [] return int(value), []
except ValueError: 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'") _(u"'%s' does not seem to be an integer for field '%%(field)s'")
% value) % value)
def _str_to_float(self, cr, uid, model, column, value, context=None): @api.model
def _str_to_float(self, model, field, value):
try: try:
return float(value), [] return float(value), []
except ValueError: 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'") _(u"'%s' does not seem to be a number for field '%%(field)s'")
% value) % value)
def _str_id(self, cr, uid, model, column, value, context=None): @api.model
def _str_id(self, model, field, value):
return value, [] return value, []
_str_to_reference = _str_to_char = _str_to_text = _str_to_binary = _str_to_html = _str_id _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: try:
time.strptime(value, DEFAULT_SERVER_DATE_FORMAT) time.strptime(value, DEFAULT_SERVER_DATE_FORMAT)
return value, [] return value, []
@ -205,28 +189,28 @@ class ir_fields_converter(orm.Model):
'moreinfo': _(u"Use the format '%s'") % u"2012-12-31" '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 there's a tz in context, try to use that
if context.get('tz'): if self._context.get('tz'):
try: try:
return pytz.timezone(context['tz']) return pytz.timezone(self._context['tz'])
except pytz.UnknownTimeZoneError: except pytz.UnknownTimeZoneError:
pass pass
# if the current user has a tz set, try to use that # if the current user has a tz set, try to use that
user = self.pool['res.users'].read( user = self.env.user
cr, uid, [uid], ['tz'], context=context)[0] if user.tz:
if user['tz']:
try: try:
return pytz.timezone(user['tz']) return pytz.timezone(user.tz)
except pytz.UnknownTimeZoneError: except pytz.UnknownTimeZoneError:
pass pass
# fallback if no tz in context or on user: UTC # fallback if no tz in context or on user: UTC
return pytz.UTC return pytz.UTC
def _str_to_datetime(self, cr, uid, model, column, value, context=None): @api.model
if context is None: context = {} def _str_to_datetime(self, model, field, value):
try: try:
parsed_value = datetime.datetime.strptime( parsed_value = datetime.datetime.strptime(
value, DEFAULT_SERVER_DATETIME_FORMAT) 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" '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) dt = input_tz.localize(parsed_value, is_dst=False)
# And convert to UTC before reformatting for writing # And convert to UTC before reformatting for writing
return dt.astimezone(pytz.UTC).strftime(DEFAULT_SERVER_DATETIME_FORMAT), [] 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) types = tuple(types)
# Cache translations so they don't have to be reloaded from scratch on # Cache translations so they don't have to be reloaded from scratch on
# every row of the file # 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]: if tnx_cache.setdefault(types, {}) and src in tnx_cache[types]:
return tnx_cache[types][src] return tnx_cache[types][src]
Translations = self.pool['ir.translation'] Translations = self.env['ir.translation']
tnx_ids = Translations.search( tnx = Translations.search([('type', 'in', types), ('src', '=', src)])
cr, uid, [('type', 'in', types), ('src', '=', src)], context=context) result = tnx_cache[types][src] = [t.value for t in tnx if t.value is not False]
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]
return result 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: for item, label in selection:
label = ustr(label) label = ustr(label)
labels = self._get_translations( labels = [label] + self._get_translations(('selection', 'model', 'code'), label)
cr, uid, ('selection', 'model', 'code'), label, context=context)
labels.append(label)
if value == unicode(item) or value in labels: if value == unicode(item) or value in labels:
return item, [] return item, []
raise ValueError( raise ValueError(
_(u"Value '%s' not found in selection field '%%(field)s'") % ( _(u"Value '%s' not found in selection field '%%(field)s'") % (
value), { value), {
@ -277,13 +258,13 @@ class ir_fields_converter(orm.Model):
if _label or item] if _label or item]
}) })
@api.model
def db_id_for(self, cr, uid, model, column, subfield, value, context=None): def db_id_for(self, model, field, subfield, value):
""" Finds a database id for the reference ``value`` in the referencing """ 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 model: model to which the field belongs
:param column: relational column for which references are provided :param field: relational field for which references are provided
:param subfield: a relational subfield allowing building of refs to :param subfield: a relational subfield allowing building of refs to
existing records: ``None`` for a name_get/name_search, existing records: ``None`` for a name_get/name_search,
``id`` for an external id and ``.id`` for a database ``id`` for an external id and ``.id`` for a database
@ -295,7 +276,6 @@ class ir_fields_converter(orm.Model):
warnings warnings
:rtype: (ID|None, unicode, list) :rtype: (ID|None, unicode, list)
""" """
if context is None: context = {}
id = None id = None
warnings = [] warnings = []
action = {'type': 'ir.actions.act_window', 'target': 'new', action = {'type': 'ir.actions.act_window', 'target': 'new',
@ -303,19 +283,18 @@ class ir_fields_converter(orm.Model):
'views': [(False, 'tree'), (False, 'form')], 'views': [(False, 'tree'), (False, 'form')],
'help': _(u"See all possible values")} 'help': _(u"See all possible values")}
if subfield is None: if subfield is None:
action['res_model'] = column._obj action['res_model'] = field.comodel_name
elif subfield in ('id', '.id'): elif subfield in ('id', '.id'):
action['res_model'] = 'ir.model.data' 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': if subfield == '.id':
field_type = _(u"database id") field_type = _(u"database id")
try: tentative_id = int(value) try: tentative_id = int(value)
except ValueError: tentative_id = value except ValueError: tentative_id = value
try: try:
if RelatedModel.search(cr, uid, [('id', '=', tentative_id)], if RelatedModel.search([('id', '=', tentative_id)]):
context=context):
id = tentative_id id = tentative_id
except psycopg2.DataError: except psycopg2.DataError:
# type error # type error
@ -325,18 +304,16 @@ class ir_fields_converter(orm.Model):
elif subfield == 'id': elif subfield == 'id':
field_type = _(u"external id") field_type = _(u"external id")
if '.' in value: if '.' in value:
module, xid = value.split('.', 1) xmlid = value
else: else:
module, xid = context.get('_import_current_module', ''), value xmlid = "%s.%s" % (self._context.get('_import_current_module', ''), value)
ModelData = self.pool['ir.model.data']
try: try:
_model, id = ModelData.get_object_reference( id = self.env.ref(xmlid).id
cr, uid, module, xid) except ValueError:
except ValueError: pass # leave id is None pass # leave id is None
elif subfield is None: elif subfield is None:
field_type = _(u"name") field_type = _(u"name")
ids = RelatedModel.name_search( ids = RelatedModel.name_search(name=value, operator='=')
cr, uid, name=value, operator='=', context=context)
if ids: if ids:
if len(ids) > 1: if len(ids) > 1:
warnings.append(ImportWarning( warnings.append(ImportWarning(
@ -376,31 +353,32 @@ class ir_fields_converter(orm.Model):
[subfield] = fieldset [subfield] = fieldset
return subfield, [] 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 # Should only be one record, unpack
[record] = values [record] = values
subfield, w1 = self._referencing_subfield(record) subfield, w1 = self._referencing_subfield(record)
reference = record[subfield] reference = record[subfield]
id, subfield_type, w2 = self.db_id_for( id, _, w2 = self.db_id_for(model, field, subfield, reference)
cr, uid, model, column, subfield, reference, context=context)
return id, w1 + w2 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 [record] = value
subfield, warnings = self._referencing_subfield(record) subfield, warnings = self._referencing_subfield(record)
ids = [] ids = []
for reference in record[subfield].split(','): for reference in record[subfield].split(','):
id, subfield_type, ws = self.db_id_for( id, _, ws = self.db_id_for(model, field, subfield, reference)
cr, uid, model, column, subfield, reference, context=context)
ids.append(id) ids.append(id)
warnings.extend(ws) warnings.extend(ws)
return [REPLACE_WITH(ids)], warnings 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 = [] commands = []
warnings = [] warnings = []
@ -418,6 +396,9 @@ class ir_fields_converter(orm.Model):
if not isinstance(e, Warning): if not isinstance(e, Warning):
raise e raise e
warnings.append(e) warnings.append(e)
convert = self.for_model(self.env[field.comodel_name])
for record in records: for record in records:
id = None id = None
refs = only_ref_fields(record) refs = only_ref_fields(record)
@ -426,11 +407,10 @@ class ir_fields_converter(orm.Model):
subfield, w1 = self._referencing_subfield(refs) subfield, w1 = self._referencing_subfield(refs)
warnings.extend(w1) warnings.extend(w1)
reference = record[subfield] reference = record[subfield]
id, subfield_type, w2 = self.db_id_for( id, _, w2 = self.db_id_for(model, field, subfield, reference)
cr, uid, model, column, subfield, reference, context=context)
warnings.extend(w2) warnings.extend(w2)
writable = column.converter(exclude_ref_fields(record), log) writable = convert(exclude_ref_fields(record), log)
if id: if id:
commands.append(LINK_TO(id)) commands.append(LINK_TO(id))
commands.append(UPDATE(id, writable)) commands.append(UPDATE(id, writable))

View File

@ -470,9 +470,9 @@ class QWeb(orm.AbstractModel):
record, field_name = template_attributes["field"].rsplit('.', 1) record, field_name = template_attributes["field"].rsplit('.', 1)
record = self.eval_object(record, qwebcontext) 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 '{}') 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) converter = self.get_converter_for(field_type)
@ -538,16 +538,16 @@ class FieldConverter(osv.AbstractModel):
* ``model``, the name of the record's model * ``model``, the name of the record's model
* ``id`` the id of the record to which the field belongs * ``id`` the id of the record to which the field belongs
* ``field`` the name of the converted field * ``field`` the name of the converted field
* ``type`` the logical field type (widget, may not match the column's * ``type`` the logical field type (widget, may not match the field's
``type``, may not be any _column subclass name) ``type``, may not be any Field subclass name)
* ``translate``, a boolean flag (``0`` or ``1``) denoting whether the * ``translate``, a boolean flag (``0`` or ``1``) denoting whether the
column is translatable field is translatable
* ``expression``, the original expression * ``expression``, the original expression
:returns: iterable of (attribute name, attribute value) pairs. :returns: iterable of (attribute name, attribute value) pairs.
""" """
column = record._all_columns[field_name].column field = record._fields[field_name]
field_type = get_field_type(column, options) field_type = get_field_type(field, options)
return [ return [
('data-oe-model', record._name), ('data-oe-model', record._name),
('data-oe-id', record.id), ('data-oe-id', record.id),
@ -556,21 +556,22 @@ class FieldConverter(osv.AbstractModel):
('data-oe-expression', t_att['field']), ('data-oe-expression', t_att['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):
""" value_to_html(cr, uid, value, column, options=None, context=None) """ value_to_html(cr, uid, value, field, options=None, context=None)
Converts a single value to its HTML version/output Converts a single value to its HTML version/output
""" """
if not value: return '' if not value: return ''
return value return value
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):
""" record_to_html(cr, uid, field_name, record, column, 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 Converts the specified field of the browse_record ``record`` to HTML
""" """
field = record._fields[field_name]
return self.value_to_html( 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, def to_html(self, cr, uid, field_name, record, options,
source_element, t_att, g_att, qweb_context, context=None): source_element, t_att, g_att, qweb_context, context=None):
@ -584,10 +585,7 @@ class FieldConverter(osv.AbstractModel):
field's own ``_type``. field's own ``_type``.
""" """
try: try:
content = self.record_to_html( content = self.record_to_html(cr, uid, field_name, record, options, context=context)
cr, uid, field_name, record,
record._all_columns[field_name].column,
options, context=context)
if options.get('html-escape', True): if options.get('html-escape', True):
content = escape(content) content = escape(content)
elif hasattr(content, '__html__'): elif hasattr(content, '__html__'):
@ -648,14 +646,14 @@ class FloatConverter(osv.AbstractModel):
_name = 'ir.qweb.field.float' _name = 'ir.qweb.field.float'
_inherit = 'ir.qweb.field' _inherit = 'ir.qweb.field'
def precision(self, cr, uid, column, options=None, context=None): def precision(self, cr, uid, field, options=None, context=None):
_, precision = column.digits or (None, None) _, precision = field.digits or (None, None)
return precision 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: if context is None:
context = {} 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' fmt = '%f' if precision is None else '%.{precision}f'
lang_code = context.get('lang') or 'en_US' lang_code = context.get('lang') or 'en_US'
@ -674,7 +672,7 @@ class DateConverter(osv.AbstractModel):
_name = 'ir.qweb.field.date' _name = 'ir.qweb.field.date'
_inherit = 'ir.qweb.field' _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 '' if not value or len(value)<10: return ''
lang = self.user_lang(cr, uid, context=context) lang = self.user_lang(cr, uid, context=context)
locale = babel.Locale.parse(lang.code) locale = babel.Locale.parse(lang.code)
@ -697,7 +695,7 @@ class DateTimeConverter(osv.AbstractModel):
_name = 'ir.qweb.field.datetime' _name = 'ir.qweb.field.datetime'
_inherit = 'ir.qweb.field' _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 '' if not value: return ''
lang = self.user_lang(cr, uid, context=context) lang = self.user_lang(cr, uid, context=context)
locale = babel.Locale.parse(lang.code) locale = babel.Locale.parse(lang.code)
@ -723,7 +721,7 @@ class TextConverter(osv.AbstractModel):
_name = 'ir.qweb.field.text' _name = 'ir.qweb.field.text'
_inherit = 'ir.qweb.field' _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. Escapes the value and converts newlines to br. This is bullshit.
""" """
@ -735,19 +733,19 @@ class SelectionConverter(osv.AbstractModel):
_name = 'ir.qweb.field.selection' _name = 'ir.qweb.field.selection'
_inherit = 'ir.qweb.field' _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] value = record[field_name]
if not value: return '' if not value: return ''
selection = dict(fields.selection.reify( field = record._fields[field_name]
cr, uid, record._model, column, context=context)) selection = dict(field.get_description(record.env)['selection'])
return self.value_to_html( return self.value_to_html(
cr, uid, selection[value], column, options=options) cr, uid, selection[value], field, options=options)
class ManyToOneConverter(osv.AbstractModel): class ManyToOneConverter(osv.AbstractModel):
_name = 'ir.qweb.field.many2one' _name = 'ir.qweb.field.many2one'
_inherit = 'ir.qweb.field' _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]) [read] = record.read([field_name])
if not read[field_name]: return '' if not read[field_name]: return ''
_, value = read[field_name] _, value = read[field_name]
@ -757,7 +755,7 @@ class HTMLConverter(osv.AbstractModel):
_name = 'ir.qweb.field.html' _name = 'ir.qweb.field.html'
_inherit = 'ir.qweb.field' _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 '') return HTMLSafe(value or '')
class ImageConverter(osv.AbstractModel): class ImageConverter(osv.AbstractModel):
@ -772,7 +770,7 @@ class ImageConverter(osv.AbstractModel):
_name = 'ir.qweb.field.image' _name = 'ir.qweb.field.image'
_inherit = 'ir.qweb.field' _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: try:
image = Image.open(cStringIO.StringIO(value.decode('base64'))) image = Image.open(cStringIO.StringIO(value.decode('base64')))
image.verify() image.verify()
@ -805,7 +803,7 @@ class MonetaryConverter(osv.AbstractModel):
cr, uid, field_name, record, options, cr, uid, field_name, record, options,
source_element, t_att, g_att, qweb_context, context=context) 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: if context is None:
context = {} context = {}
Currency = self.pool['res.currency'] Currency = self.pool['res.currency']
@ -876,7 +874,7 @@ class DurationConverter(osv.AbstractModel):
_name = 'ir.qweb.field.duration' _name = 'ir.qweb.field.duration'
_inherit = 'ir.qweb.field' _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) units = dict(TIMEDELTA_UNITS)
if value < 0: if value < 0:
raise ValueError(_("Durations can't be negative")) raise ValueError(_("Durations can't be negative"))
@ -903,7 +901,7 @@ class RelativeDatetimeConverter(osv.AbstractModel):
_name = 'ir.qweb.field.relative' _name = 'ir.qweb.field.relative'
_inherit = 'ir.qweb.field' _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 parse_format = openerp.tools.DEFAULT_SERVER_DATETIME_FORMAT
locale = babel.Locale.parse( locale = babel.Locale.parse(
self.user_lang(cr, uid, context=context).code) self.user_lang(cr, uid, context=context).code)
@ -911,8 +909,8 @@ class RelativeDatetimeConverter(osv.AbstractModel):
if isinstance(value, basestring): if isinstance(value, basestring):
value = datetime.datetime.strptime(value, parse_format) value = datetime.datetime.strptime(value, parse_format)
# value should be a naive datetime in UTC. So is fields.datetime.now() # value should be a naive datetime in UTC. So is fields.Datetime.now()
reference = datetime.datetime.strptime(column.now(), parse_format) reference = datetime.datetime.strptime(field.now(), parse_format)
return babel.dates.format_timedelta( return babel.dates.format_timedelta(
value - reference, add_direction=True, locale=locale) value - reference, add_direction=True, locale=locale)
@ -921,33 +919,32 @@ class Contact(orm.AbstractModel):
_name = 'ir.qweb.field.contact' _name = 'ir.qweb.field.contact'
_inherit = 'ir.qweb.field.many2one' _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: if context is None:
context = {} context = {}
if options is None: if options is None:
options = {} options = {}
opf = options.get('fields') or ["name", "address", "phone", "mobile", "fax", "email"] 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 value_rec = record[field_name]
context.update(show_address=True) if not value_rec:
field_browse = self.pool[column._obj].browse(cr, openerp.SUPERUSER_ID, id, context=context) return None
value = field_browse.name_get()[0][1] value_rec = value_rec.sudo().with_context(show_address=True)
value = value_rec.display_name
val = { val = {
'name': value.split("\n")[0], 'name': value.split("\n")[0],
'address': escape("\n".join(value.split("\n")[1:])), 'address': escape("\n".join(value.split("\n")[1:])),
'phone': field_browse.phone, 'phone': value_rec.phone,
'mobile': field_browse.mobile, 'mobile': value_rec.mobile,
'fax': field_browse.fax, 'fax': value_rec.fax,
'city': field_browse.city, 'city': value_rec.city,
'country_id': field_browse.country_id.display_name, 'country_id': value_rec.country_id.display_name,
'website': field_browse.website, 'website': value_rec.website,
'email': field_browse.email, 'email': value_rec.email,
'fields': opf, 'fields': opf,
'object': field_browse, 'object': value_rec,
'options': options 'options': options
} }
@ -959,7 +956,7 @@ class QwebView(orm.AbstractModel):
_name = 'ir.qweb.field.qweb' _name = 'ir.qweb.field.qweb'
_inherit = 'ir.qweb.field.many2one' _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): if not getattr(record, field_name):
return None return None
@ -1045,10 +1042,9 @@ def nl2br(string, options=None):
string = escape(string) string = escape(string)
return HTMLSafe(string.replace('\n', '<br>\n')) return HTMLSafe(string.replace('\n', '<br>\n'))
def get_field_type(column, options): def get_field_type(field, options):
""" Gets a t-field's effective type from the field's column and its options """ Gets a t-field's effective type from the field definition and its options """
""" return options.get('widget', field.type)
return options.get('widget', column._type)
class AssetError(Exception): class AssetError(Exception):
pass pass

View File

@ -399,15 +399,15 @@ class ir_translation(osv.osv):
langs = [lg.code for lg in self.pool.get('res.lang').browse(cr, uid, langs_ids, context=context)] langs = [lg.code for lg in self.pool.get('res.lang').browse(cr, uid, langs_ids, context=context)]
main_lang = 'en_US' main_lang = 'en_US'
translatable_fields = [] translatable_fields = []
for f, info in trans_model._all_columns.items(): for k, f in trans_model._fields.items():
if info.column.translate: if f.translate:
if info.parent_model: if f.inherited:
parent_id = trans_model.read(cr, uid, [id], [info.parent_column], context=context)[0][info.parent_column][0] parent_id = trans_model.read(cr, uid, [id], [f.related[0]], context=context)[0][f.related[0]][0]
translatable_fields.append({ 'name': f, 'id': parent_id, 'model': info.parent_model }) translatable_fields.append({'name': k, 'id': parent_id, 'model': f.base_field.model})
domain.insert(0, '|') 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: else:
translatable_fields.append({ 'name': f, 'id': id, 'model': model }) translatable_fields.append({'name': k, 'id': id, 'model': model })
if len(langs): if len(langs):
fields = [f.get('name') for f in translatable_fields] fields = [f.get('name') for f in translatable_fields]
record = trans_model.read(cr, uid, [id], fields, context={ 'lang': main_lang })[0] record = trans_model.read(cr, uid, [id], fields, context={ 'lang': main_lang })[0]
@ -432,9 +432,9 @@ class ir_translation(osv.osv):
'domain': domain, 'domain': domain,
} }
if field: if field:
info = trans_model._all_columns[field] f = trans_model._fields[field]
action['context'] = { 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 return action

View File

@ -811,11 +811,11 @@ class view(osv.osv):
if not node.get(action) and not Model.check_access_rights(cr, user, operation, raise_exception=False): if not node.get(action) and not Model.check_access_rights(cr, user, operation, raise_exception=False):
node.set(action, 'false') node.set(action, 'false')
if node.tag in ('kanban'): if node.tag in ('kanban'):
group_by_field = node.get('default_group_by') group_by_name = node.get('default_group_by')
if group_by_field and Model._all_columns.get(group_by_field): if group_by_name in Model._fields:
group_by_column = Model._all_columns[group_by_field].column group_by_field = Model._fields[group_by_name]
if group_by_column._type == 'many2one': if group_by_field.type == 'many2one':
group_by_model = Model.pool.get(group_by_column._obj) group_by_model = Model.pool[group_by_field.comodel_name]
for action, operation in (('group_create', 'create'), ('group_delete', 'unlink'), ('group_edit', 'write')): 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): if not node.get(action) and not group_by_model.check_access_rights(cr, user, operation, raise_exception=False):
node.set(action, 'false') node.set(action, 'false')

View File

@ -423,7 +423,7 @@ class ir_values(osv.osv):
if action_model_name not in self.pool: if action_model_name not in self.pool:
continue # unknow model? skip it continue # unknow model? skip it
action_model = self.pool[action_model_name] 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 # FIXME: needs cleanup
try: try:
action_def = action_model.read(cr, uid, int(action_id), fields, context) action_def = action_model.read(cr, uid, int(action_id), fields, context)

View File

@ -416,16 +416,16 @@ class res_partner(osv.Model, format_address):
def _update_fields_values(self, cr, uid, partner, fields, context=None): def _update_fields_values(self, cr, uid, partner, fields, context=None):
""" Returns dict of write() values for synchronizing ``fields`` """ """ Returns dict of write() values for synchronizing ``fields`` """
values = {} values = {}
for field in fields: for fname in fields:
column = self._all_columns[field].column field = self._fields[fname]
if column._type == 'one2many': if field.type == 'one2many':
raise AssertionError('One2Many fields cannot be synchronized as part of `commercial_fields` or `address fields`') raise AssertionError('One2Many fields cannot be synchronized as part of `commercial_fields` or `address fields`')
if column._type == 'many2one': if field.type == 'many2one':
values[field] = partner[field].id if partner[field] else False values[fname] = partner[fname].id if partner[fname] else False
elif column._type == 'many2many': elif field.type == 'many2many':
values[field] = [(6,0,[r.id for r in partner[field] or []])] values[fname] = [(6,0,[r.id for r in partner[fname] or []])]
else: else:
values[field] = partner[field] values[fname] = partner[fname]
return values return values
def _address_fields(self, cr, uid, context=None): def _address_fields(self, cr, uid, context=None):

View File

@ -377,7 +377,7 @@ class res_users(osv.osv):
def context_get(self, cr, uid, context=None): def context_get(self, cr, uid, context=None):
user = self.browse(cr, SUPERUSER_ID, uid, context) user = self.browse(cr, SUPERUSER_ID, uid, context)
result = {} result = {}
for k in self._all_columns.keys(): for k in self._fields:
if k.startswith('context_'): if k.startswith('context_'):
context_key = k[8:] context_key = k[8:]
elif k in ['lang', 'tz']: elif k in ['lang', 'tz']:

View File

@ -60,7 +60,8 @@
self.pool._init = False self.pool._init = False
# Force partner_categ.copy() to copy children # 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" "1.0 Setup test partner categories: parent root"
- -
@ -145,7 +146,8 @@
- -
!python {model: res.partner.category}: | !python {model: res.partner.category}: |
self.pool._init = True 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" "Float precision tests: verify that float rounding methods are working correctly via res.currency"

View File

@ -93,17 +93,17 @@ class TestAPI(common.TransactionCase):
self.assertIsRecordset(user.groups_id, 'res.groups') self.assertIsRecordset(user.groups_id, 'res.groups')
partners = self.env['res.partner'].search([]) partners = self.env['res.partner'].search([])
for name, cinfo in partners._all_columns.iteritems(): for name, field in partners._fields.iteritems():
if cinfo.column._type == 'many2one': if field.type == 'many2one':
for p in partners: for p in partners:
self.assertIsRecord(p[name], cinfo.column._obj) self.assertIsRecord(p[name], field.comodel_name)
elif cinfo.column._type == 'reference': elif field.type == 'reference':
for p in partners: for p in partners:
if p[name]: if p[name]:
self.assertIsRecord(p[name], cinfo.column._obj) self.assertIsRecord(p[name], field.comodel_name)
elif cinfo.column._type in ('one2many', 'many2many'): elif field.type in ('one2many', 'many2many'):
for p in partners: for p in partners:
self.assertIsRecordset(p[name], cinfo.column._obj) self.assertIsRecordset(p[name], field.comodel_name)
@mute_logger('openerp.models') @mute_logger('openerp.models')
def test_07_null(self): def test_07_null(self):

View File

@ -17,15 +17,14 @@ class TestExport(common.TransactionCase):
def setUp(self): def setUp(self):
super(TestExport, self).setUp() super(TestExport, self).setUp()
self.Model = self.registry(self._model) self.Model = self.registry(self._model)
self.columns = self.Model._all_columns
def get_column(self, name): def get_field(self, name):
return self.Model._all_columns[name].column return self.Model._fields[name]
def get_converter(self, name, type=None): 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'] fs = ['ir', 'qweb', 'field']
if postfix is None: continue if postfix is None: continue
if postfix: fs.append(postfix) if postfix: fs.append(postfix)
@ -36,7 +35,7 @@ class TestExport(common.TransactionCase):
except KeyError: pass except KeyError: pass
return lambda value, options=None, context=None: e(model.value_to_html( 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): class TestBasicExport(TestExport):
_model = 'test_converter.test_model' _model = 'test_converter.test_model'
@ -222,11 +221,8 @@ class TestMany2OneExport(TestBasicExport):
}) })
def converter(record): def converter(record):
column = self.get_column('many2one')
model = self.registry('ir.qweb.field.many2one') model = self.registry('ir.qweb.field.many2one')
return e(model.record_to_html(self.cr, self.uid, 'many2one', record))
return e(model.record_to_html(
self.cr, self.uid, 'many2one', record, column))
value = converter(self.Model.browse(self.cr, self.uid, id0)) value = converter(self.Model.browse(self.cr, self.uid, id0))
self.assertEqual(value, "Foo") self.assertEqual(value, "Foo")
@ -236,7 +232,7 @@ class TestMany2OneExport(TestBasicExport):
class TestBinaryExport(TestBasicExport): class TestBinaryExport(TestBasicExport):
def test_image(self): def test_image(self):
column = self.get_column('binary') field = self.get_field('binary')
converter = self.registry('ir.qweb.field.image') converter = self.registry('ir.qweb.field.image')
with open(os.path.join(directory, 'test_vectors', 'image'), 'rb') as f: with open(os.path.join(directory, 'test_vectors', 'image'), 'rb') as f:
@ -244,7 +240,7 @@ class TestBinaryExport(TestBasicExport):
encoded_content = content.encode('base64') encoded_content = content.encode('base64')
value = e(converter.value_to_html( value = e(converter.value_to_html(
self.cr, self.uid, encoded_content, column)) self.cr, self.uid, encoded_content, field))
self.assertEqual( self.assertEqual(
value, '<img src="data:image/jpeg;base64,%s">' % ( value, '<img src="data:image/jpeg;base64,%s">' % (
encoded_content encoded_content
@ -255,14 +251,14 @@ class TestBinaryExport(TestBasicExport):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
e(converter.value_to_html( 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: with open(os.path.join(directory, 'test_vectors', 'pptx'), 'rb') as f:
content = f.read() content = f.read()
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
e(converter.value_to_html( 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): class TestSelectionExport(TestBasicExport):
def test_selection(self): def test_selection(self):
@ -271,18 +267,14 @@ class TestSelectionExport(TestBasicExport):
'selection_str': 'C', 'selection_str': 'C',
})]) })])
column_name = 'selection'
column = self.get_column(column_name)
converter = self.registry('ir.qweb.field.selection') converter = self.registry('ir.qweb.field.selection')
value = converter.record_to_html( field_name = 'selection'
self.cr, self.uid, column_name, record, column) value = converter.record_to_html(self.cr, self.uid, field_name, record)
self.assertEqual(value, "réponse B") self.assertEqual(value, "réponse B")
column_name = 'selection_str' field_name = 'selection_str'
column = self.get_column(column_name) value = converter.record_to_html(self.cr, self.uid, field_name, record)
value = converter.record_to_html(
self.cr, self.uid, column_name, record, column)
self.assertEqual(value, "Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?") self.assertEqual(value, "Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?")
class TestHTMLExport(TestBasicExport): class TestHTMLExport(TestBasicExport):

View File

@ -202,9 +202,12 @@ class Field(object):
:param related: sequence of field names :param related: sequence of field names
The value of some attributes from related fields are automatically taken Some field attributes are automatically copied from the source field if
from the source field, when it makes sense. Examples are the attributes they are not redefined: `string`, `help`, `readonly`, `required` (only
`string` or `selection` on selection fields. 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. 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 Add the attribute ``store=True`` to make it stored, just like computed
@ -459,6 +462,11 @@ class Field(object):
if not getattr(self, attr): if not getattr(self, attr):
setattr(self, attr, getattr(field, prop)) 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 # special case for required: check if all fields are required
if not self.store and not self.required: if not self.store and not self.required:
self.required = all(field.required for field in fields) self.required = all(field.required for field in fields)
@ -497,6 +505,11 @@ class Field(object):
_related_readonly = property(attrgetter('readonly')) _related_readonly = property(attrgetter('readonly'))
_related_groups = property(attrgetter('groups')) _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 # Setup of non-related fields
# #
@ -935,6 +948,11 @@ class Boolean(Field):
class Integer(Field): class Integer(Field):
type = 'integer' 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): def convert_to_cache(self, value, record, validate=True):
if isinstance(value, dict): if isinstance(value, dict):
@ -963,6 +981,7 @@ class Float(Field):
type = 'float' type = 'float'
_digits = None # digits argument passed to class initializer _digits = None # digits argument passed to class initializer
digits = None # digits as computed by setup() digits = None # digits as computed by setup()
group_operator = None # operator for aggregating values
def __init__(self, string=None, digits=None, **kwargs): def __init__(self, string=None, digits=None, **kwargs):
super(Float, self).__init__(string=string, _digits=digits, **kwargs) super(Float, self).__init__(string=string, _digits=digits, **kwargs)
@ -981,11 +1000,13 @@ class Float(Field):
self._setup_digits(env) self._setup_digits(env)
_related_digits = property(attrgetter('digits')) _related_digits = property(attrgetter('digits'))
_related_group_operator = property(attrgetter('group_operator'))
_description_digits = property(attrgetter('digits')) _description_digits = property(attrgetter('digits'))
_column_digits = property(lambda self: not callable(self._digits) and self._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_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): def convert_to_cache(self, value, record, validate=True):
# apply rounding here, otherwise value in cache may be wrong! # apply rounding here, otherwise value in cache may be wrong!

View File

@ -334,6 +334,7 @@ class BaseModel(object):
# This is similar to _inherit_fields but: # This is similar to _inherit_fields but:
# 1. includes self fields, # 1. includes self fields,
# 2. uses column_info instead of a triple. # 2. uses column_info instead of a triple.
# Warning: _all_columns is deprecated, use _fields instead
_all_columns = {} _all_columns = {}
_table = None _table = None
@ -1141,22 +1142,23 @@ class BaseModel(object):
* "id" is the External ID for the record * "id" is the External ID for the record
* ".id" is the Database 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()) from openerp.fields import Char, Integer
# Fake columns to avoid special cases in extractor fields = dict(self._fields)
columns[None] = fields.char('rec_name') # Fake fields to avoid special cases in extractor
columns['id'] = fields.char('External ID') fields[None] = Char('rec_name')
columns['.id'] = fields.integer('Database ID') fields['id'] = Char('External ID')
fields['.id'] = Integer('Database ID')
# m2o fields can't be on multiple lines so exclude them from the # 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 # is_relational field rows filter, but special-case it later on to
# be handled with relational fields (as it can have subfields) # 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( get_o2m_values = itemgetter_tuple(
[index for index, field in enumerate(fields_) [index for index, field in enumerate(fields_)
if columns[field[0]]._type == 'one2many']) if fields[field[0]].type == 'one2many'])
get_nono2m_values = itemgetter_tuple( get_nono2m_values = itemgetter_tuple(
[index for index, field in enumerate(fields_) [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 # 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): def only_o2m_values(row, f=get_nono2m_values, g=get_o2m_values):
return any(g(row)) and not any(f(row)) return any(g(row)) and not any(f(row))
@ -1180,12 +1182,11 @@ class BaseModel(object):
for relfield in set( for relfield in set(
field[0] for field in fields_ field[0] for field in fields_
if is_relational(field[0])): if is_relational(field[0])):
column = columns[relfield]
# FIXME: how to not use _obj without relying on fields_get? # 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 # 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]) indices, subfields = zip(*((index, field[1:] or [None])
for index, field in enumerate(fields_) for index, field in enumerate(fields_)
if field[0] == relfield)) if field[0] == relfield))
@ -1215,13 +1216,13 @@ class BaseModel(object):
""" """
if context is None: context = {} if context is None: context = {}
Converter = self.pool['ir.fields.converter'] Converter = self.pool['ir.fields.converter']
columns = dict((k, v.column) for k, v in self._all_columns.iteritems())
Translation = self.pool['ir.translation'] Translation = self.pool['ir.translation']
fields = dict(self._fields)
field_names = dict( field_names = dict(
(f, (Translation._get_source(cr, uid, self._name + ',' + f, 'field', (f, (Translation._get_source(cr, uid, self._name + ',' + f, 'field',
context.get('lang')) context.get('lang'))
or column.string)) or field.string))
for f, column in columns.iteritems()) for f, field in fields.iteritems())
convert = Converter.for_model(cr, uid, self, context=context) convert = Converter.for_model(cr, uid, self, context=context)
@ -1947,7 +1948,7 @@ class BaseModel(object):
order_field = order_split[0] order_field = order_split[0]
if order_field in groupby_fields: 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 ', '') order_clause = self._generate_order_by(order_part, query).replace('ORDER BY ', '')
if order_clause: if order_clause:
orderby_terms.append(order_clause) orderby_terms.append(order_clause)
@ -1969,7 +1970,7 @@ class BaseModel(object):
field name, type, time informations, qualified name, ... field name, type, time informations, qualified name, ...
""" """
split = gb.split(':') 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 gb_function = split[1] if len(split) == 2 else None
temporal = field_type in ('date', 'datetime') temporal = field_type in ('date', 'datetime')
tz_convert = field_type == 'datetime' and context.get('tz') in pytz.all_timezones 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?)" 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]) 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" 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! # Don't allow arbitrary values, as this would be a SQL injection vector!
raise except_orm(_('Invalid group_by'), raise except_orm(_('Invalid group_by'),
_('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(gb,)) _('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 f for f in fields
if f not in ('id', 'sequence') if f not in ('id', 'sequence')
if f not in groupby_fields if f not in groupby_fields
if f in self._all_columns if f in self._fields
if self._all_columns[f].column._type in ('integer', 'float') if self._fields[f].type in ('integer', 'float')
if getattr(self._all_columns[f].column, '_classic_write')] 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] select_terms = ["%s(%s) AS %s" % field_formatter(f) for f in aggregated_fields]
for gb in annotated_groupbys: for gb in annotated_groupbys:
@ -3506,7 +3508,7 @@ class BaseModel(object):
if isinstance(ids, (int, long)): if isinstance(ids, (int, long)):
ids = [ids] 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 # for recomputing new-style fields
recs = self.browse(cr, uid, ids, context) recs = self.browse(cr, uid, ids, context)
@ -3748,9 +3750,9 @@ class BaseModel(object):
direct = [] direct = []
totranslate = context.get('lang', False) and (context['lang'] != 'en_US') totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
for field in vals: for field in vals:
field_column = self._all_columns.get(field) and self._all_columns.get(field).column ffield = self._fields.get(field)
if field_column and field_column.deprecated: if ffield and ffield.deprecated:
_logger.warning('Field %s.%s is deprecated: %s', self._name, field, field_column.deprecated) _logger.warning('Field %s.%s is deprecated: %s', self._name, field, ffield.deprecated)
if field in self._columns: if field in self._columns:
if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')): if self._columns[field]._classic_write and not (hasattr(self._columns[field], '_fnct_inv')):
if (not totranslate) or not self._columns[field].translate: if (not totranslate) or not self._columns[field].translate:
@ -4336,7 +4338,7 @@ class BaseModel(object):
domain = domain[:] domain = domain[:]
# if the object has a field named 'active', filter out all inactive # if the object has a field named 'active', filter out all inactive
# records unless they were explicitely asked for # 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: if domain:
# the item[0] trick below works for domain items and '&'/'|'/'!' # the item[0] trick below works for domain items and '&'/'|'/'!'
# operators too # operators too
@ -4598,6 +4600,8 @@ class BaseModel(object):
# build a black list of fields that should not be copied # build a black list of fields that should not be copied
blacklist = set(MAGIC_COLUMNS + ['parent_left', 'parent_right']) 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): def blacklist_given_fields(obj):
# blacklist the fields that are given by inheritance # blacklist the fields that are given by inheritance
for other, field_to_other in obj._inherits.items(): for other, field_to_other in obj._inherits.items():
@ -4605,19 +4609,19 @@ class BaseModel(object):
if field_to_other in default: if field_to_other in default:
# all the fields of 'other' are given by the record: default[field_to_other], # all the fields of 'other' are given by the record: default[field_to_other],
# except the ones redefined in self # 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: else:
blacklist_given_fields(self.pool[other]) blacklist_given_fields(self.pool[other])
# blacklist deprecated fields # blacklist deprecated fields
for name, field in obj._columns.items(): for name, field in obj._fields.iteritems():
if field.deprecated: if field.deprecated:
blacklist.add(name) blacklist.add(name)
blacklist_given_fields(self) blacklist_given_fields(self)
fields_to_copy = dict((f,fi) for f, fi in self._all_columns.iteritems() fields_to_copy = dict((f,fi) for f, fi in self._fields.iteritems()
if fi.column.copy if fi.copy
if f not in default if f not in default
if f not in blacklist) 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)) raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name))
res = dict(default) res = dict(default)
for f, colinfo in fields_to_copy.iteritems(): for f, field in fields_to_copy.iteritems():
field = colinfo.column if field.type == 'many2one':
if field._type == 'many2one':
res[f] = data[f] and data[f][0] res[f] = data[f] and data[f][0]
elif field._type == 'one2many': elif field.type == 'one2many':
other = self.pool[field._obj] other = self.pool[field.comodel_name]
# duplicate following the order of the ids because we'll rely on # duplicate following the order of the ids because we'll rely on
# it later for copying translations in copy_translation()! # 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])] 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 # the lines are duplicated using the wrong (old) parent, but then
# are reassigned to the correct one thanks to the (0, 0, ...) # are reassigned to the correct one thanks to the (0, 0, ...)
res[f] = [(0, 0, line) for line in lines if line] 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])] res[f] = [(6, 0, data[f])]
else: else:
res[f] = data[f] res[f] = data[f]
@ -4658,16 +4661,14 @@ class BaseModel(object):
seen_map[self._name].append(old_id) seen_map[self._name].append(old_id)
trans_obj = self.pool.get('ir.translation') 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 # removing the lang to compare untranslated values
context_wo_lang = dict(context, lang=None) context_wo_lang = dict(context, lang=None)
old_record, new_record = self.browse(cr, uid, [old_id, new_id], context=context_wo_lang) 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 # we must recursively copy the translations for o2o and o2m
if field_def['type'] == 'one2many': if field.type == 'one2many':
target_obj = self.pool[field_def['relation']] target_obj = self.pool[field.comodel_name]
# here we rely on the order of the ids to match the translations # here we rely on the order of the ids to match the translations
# as foreseen in copy_data() # as foreseen in copy_data()
old_children = sorted(r.id for r in old_record[field_name]) 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): for (old_child, new_child) in zip(old_children, new_children):
target_obj.copy_translations(cr, uid, old_child, new_child, context=context) target_obj.copy_translations(cr, uid, old_child, new_child, context=context)
# and for translatable fields we keep them for copy # 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: if field_name in self._columns:
trans_name = self._name + "," + field_name trans_name = self._name + "," + field_name
target_id = new_id 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. :return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
""" """
field = self._all_columns.get(field_name) field = self._fields.get(field_name)
field = field.column if field else None if not (field and field.type == 'many2many' and
if not field or field._type != 'many2many' or field._obj != self._name: field.comodel_name == self._name and field.store):
# field must be a many2many on itself # field must be a many2many on itself
raise ValueError('invalid field_name: %r' % (field_name,)) 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[:] ids_parent = ids[:]
while ids_parent: while ids_parent:
ids_parent2 = [] ids_parent2 = []
@ -4985,7 +4987,7 @@ class BaseModel(object):
result, record_ids = [], list(command[2]) result, record_ids = [], list(command[2])
# read the records and apply the updates # 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): for record in other_model.read(cr, uid, record_ids, fields=fields, context=context):
record.update(updates.get(record['id'], {})) record.update(updates.get(record['id'], {}))
result.append(record) result.append(record)

View File

@ -1106,7 +1106,7 @@ class expression(object):
# final sanity checks - should never fail # final sanity checks - should never fail
assert operator in (TERM_OPERATORS + ('inselect', 'not inselect')), \ assert operator in (TERM_OPERATORS + ('inselect', 'not inselect')), \
"Invalid operator %r in domain term %r" % (operator, leaf) "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) or left in MAGIC_COLUMNS, "Invalid field %r in domain term %r" % (left, leaf)
assert not isinstance(right, BaseModel), \ assert not isinstance(right, BaseModel), \
"Invalid value %r in domain term %r" % (right, leaf) "Invalid value %r in domain term %r" % (right, leaf)

View File

@ -178,6 +178,7 @@ class _column(object):
('groups', self.groups), ('groups', self.groups),
('change_default', self.change_default), ('change_default', self.change_default),
('deprecated', self.deprecated), ('deprecated', self.deprecated),
('group_operator', self.group_operator),
('size', self.size), ('size', self.size),
('ondelete', self.ondelete), ('ondelete', self.ondelete),
('translate', self.translate), ('translate', self.translate),
@ -746,31 +747,32 @@ class one2many(_column):
elif act[0] == 2: elif act[0] == 2:
obj.unlink(cr, user, [act[1]], context=context) obj.unlink(cr, user, [act[1]], context=context)
elif act[0] == 3: elif act[0] == 3:
reverse_rel = obj._all_columns.get(self._fields_id) inverse_field = obj._fields.get(self._fields_id)
assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o' 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 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) obj.unlink(cr, user, [act[1]], context=context)
else: else:
cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],)) cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
elif act[0] == 4: elif act[0] == 4:
# table of the field (parent_model in case of inherit) # 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 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)) 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(): if not cr.fetchone():
# Must use write() to recompute parent_store structure if needed and check access rules # 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 {}) obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
elif act[0] == 5: elif act[0] == 5:
reverse_rel = obj._all_columns.get(self._fields_id) inverse_field = obj._fields.get(self._fields_id)
assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o' 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 # if the o2m has a static domain we must respect it when unlinking
domain = self._domain(obj) if callable(self._domain) else self._domain domain = self._domain(obj) if callable(self._domain) else self._domain
extra_domain = domain or [] extra_domain = domain or []
ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context) 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, # 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. # 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) obj.unlink(cr, user, ids_to_unlink, context=context)
else: else:
obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context) 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} res = {id: {} for id in ids}
for prop_name in prop_names: 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) 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 # name_get the non-null values as SUPERUSER_ID
vals = sum(set(filter(None, values.itervalues())), 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 {} vals_name = dict(vals.sudo().name_get()) if vals else {}
for id, value in values.iteritems(): for id, value in values.iteritems():
ng = False ng = False

View File

@ -727,8 +727,8 @@ form: module.record_id""" % (xml_id,)
f_ref = field.get("ref",'').encode('utf-8') f_ref = field.get("ref",'').encode('utf-8')
f_search = field.get("search",'').encode('utf-8') f_search = field.get("search",'').encode('utf-8')
f_model = field.get("model",'').encode('utf-8') f_model = field.get("model",'').encode('utf-8')
if not f_model and model._all_columns.get(f_name): if not f_model and f_name in model._fields:
f_model = model._all_columns[f_name].column._obj f_model = model._fields[f_name].comodel_name
f_use = field.get("use",'').encode('utf-8') or 'id' f_use = field.get("use",'').encode('utf-8') or 'id'
f_val = False f_val = False
@ -739,26 +739,24 @@ form: module.record_id""" % (xml_id,)
# browse the objects searched # browse the objects searched
s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q)) s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
# column definitions of the "local" object # 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 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))] f_val = [(6, 0, map(lambda x: x[f_use], s))]
elif len(s): elif len(s):
# otherwise (we are probably in a many2one field), # otherwise (we are probably in a many2one field),
# take the first element of the search # take the first element of the search
f_val = s[0][f_use] f_val = s[0][f_use]
elif f_ref: elif f_ref:
if f_name in model._all_columns \ if f_name in model._fields and model._fields[f_name].type == 'reference':
and model._all_columns[f_name].column._type == 'reference':
val = self.model_id_get(cr, f_ref) val = self.model_id_get(cr, f_ref)
f_val = val[0] + ',' + str(val[1]) f_val = val[0] + ',' + str(val[1])
else: else:
f_val = self.id_get(cr, f_ref) f_val = self.id_get(cr, f_ref)
else: else:
f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref) f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
if f_name in model._all_columns: if f_name in model._fields:
import openerp.osv as osv if model._fields[f_name].type == 'integer':
if isinstance(model._all_columns[f_name].column, osv.fields.integer):
f_val = int(f_val) f_val = int(f_val)
res[f_name] = f_val res[f_name] = f_val

View File

@ -482,15 +482,15 @@ class YamlInterpreter(object):
record_dict[field_name] = field_value record_dict[field_name] = field_value
return record_dict 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' assert node.search or node.id, '!ref node should have a `search` attribute or `id` attribute'
if node.search: if node.search:
if node.model: if node.model:
model_name = node.model model_name = node.model
elif column: elif field:
model_name = column._obj model_name = field.comodel_name
else: 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) model = self.get_model(model_name)
q = eval(node.search, self.eval_context) q = eval(node.search, self.eval_context)
ids = model.search(self.cr, self.uid, q) 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): 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 # TODO this should be refactored as something like model.get_field() in bin/osv
if field_name in model._columns: if field_name not in model._fields:
column = model._columns[field_name]
elif field_name in model._inherit_fields:
column = model._inherit_fields[field_name][2]
else:
raise KeyError("Object '%s' does not contain field '%s'" % (model, field_name)) raise KeyError("Object '%s' does not contain field '%s'" % (model, field_name))
field = model._fields[field_name]
if is_ref(expression): if is_ref(expression):
elements = self.process_ref(expression, column) elements = self.process_ref(expression, field)
if column._type in ("many2many", "one2many"): if field.type in ("many2many", "one2many"):
value = [(6, 0, elements)] value = [(6, 0, elements)]
else: # many2one else: # many2one
if isinstance(elements, (list,tuple)): if isinstance(elements, (list,tuple)):
value = self._get_first_result(elements) value = self._get_first_result(elements)
else: else:
value = elements value = elements
elif column._type == "many2one": elif field.type == "many2one":
value = self.get_id(expression) value = self.get_id(expression)
elif column._type == "one2many": elif field.type == "one2many":
other_model = self.get_model(column._obj) 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] 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] ids = [self.get_id(xml_id) for xml_id in expression]
value = [(6, 0, ids)] 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 # enforce ISO format for string date values, to be locale-agnostic during tests
time.strptime(expression, misc.DEFAULT_SERVER_DATE_FORMAT) time.strptime(expression, misc.DEFAULT_SERVER_DATE_FORMAT)
value = expression 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 # enforce ISO format for string datetime values, to be locale-agnostic during tests
time.strptime(expression, misc.DEFAULT_SERVER_DATETIME_FORMAT) time.strptime(expression, misc.DEFAULT_SERVER_DATETIME_FORMAT)
value = expression value = expression
@ -546,7 +544,7 @@ class YamlInterpreter(object):
value = self.process_eval(expression) value = self.process_eval(expression)
else: else:
value = expression 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 return value
def process_context(self, node): def process_context(self, node):