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

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')]
def tracking_get_values(self, cr, uid, vals, context=None):
for key, field in self.tracking_fields():
column = self._all_columns[field].column
value = vals.get(field) or (request and request.httprequest.cookies.get(key)) # params.get should be always in session by the dispatch from ir_http
if column._type in ['many2one'] and isinstance(value, basestring): # if we receive a string for a many2one, we search / create the id
for key, fname in self.tracking_fields():
field = self._fields[fname]
value = vals.get(fname) or (request and request.httprequest.cookies.get(key)) # params.get should be always in session by the dispatch from ir_http
if field.type == 'many2one' and isinstance(value, basestring):
# if we receive a string for a many2one, we search/create the id
if value:
Model = self.pool[column._obj]
Model = self.pool[field.comodel_name]
rel_id = Model.name_search(cr, uid, value, context=context)
if rel_id:
rel_id = rel_id[0][0]
else:
rel_id = Model.create(cr, uid, {'name': value}, context=context)
vals[field] = rel_id
# Here the code for others cases that many2one
vals[fname] = rel_id
else:
vals[field] = value
# Here the code for others cases that many2one
vals[fname] = value
return vals
def _get_default_track(self, cr, uid, field, context=None):

View File

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

View File

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

View File

@ -8,10 +8,10 @@ class TestFloatExport(common.TransactionCase):
def get_converter(self, name):
converter = self.registry('ir.qweb.field.float')
column = self.Model._all_columns[name].column
field = self.Model._fields[name]
return lambda value, options=None: converter.value_to_html(
self.cr, self.uid, value, column, options=options, context=None)
self.cr, self.uid, value, field, options=options, context=None)
def test_basic_float(self):
converter = self.get_converter('float')

View File

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

View File

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

View File

@ -73,7 +73,7 @@ class mail_mail(osv.Model):
def default_get(self, cr, uid, fields, context=None):
# protection for `default_type` values leaking from menu action context (e.g. for invoices)
# To remove when automatic context propagation is removed in web client
if context and context.get('default_type') and context.get('default_type') not in self._all_columns['type'].column.selection:
if context and context.get('default_type') and context.get('default_type') not in self._fields['type'].selection:
context = dict(context, default_type=None)
return super(mail_mail, self).default_get(cr, uid, fields, context=context)

View File

@ -460,12 +460,12 @@ class mail_thread(osv.AbstractModel):
def _get_tracked_fields(self, cr, uid, updated_fields, context=None):
""" Return a structure of tracked fields for the current model.
:param list updated_fields: modified field names
:return list: a list of (field_name, column_info obj), containing
:return dict: a dict mapping field name to description, containing
always tracked fields and modified on_change fields
"""
tracked_fields = []
for name, column_info in self._all_columns.items():
visibility = getattr(column_info.column, 'track_visibility', False)
for name, field in self._fields.items():
visibility = getattr(field, 'track_visibility', False)
if visibility == 'always' or (visibility == 'onchange' and name in updated_fields) or name in self._track:
tracked_fields.append(name)
@ -507,17 +507,22 @@ class mail_thread(osv.AbstractModel):
# generate tracked_values data structure: {'col_name': {col_info, new_value, old_value}}
for col_name, col_info in tracked_fields.items():
field = self._fields[col_name]
initial_value = initial[col_name]
record_value = getattr(browse_record, col_name)
if record_value == initial_value and getattr(self._all_columns[col_name].column, 'track_visibility', None) == 'always':
tracked_values[col_name] = dict(col_info=col_info['string'],
new_value=convert_for_display(record_value, col_info))
if record_value == initial_value and getattr(field, 'track_visibility', None) == 'always':
tracked_values[col_name] = dict(
col_info=col_info['string'],
new_value=convert_for_display(record_value, col_info),
)
elif record_value != initial_value and (record_value or initial_value): # because browse null != False
if getattr(self._all_columns[col_name].column, 'track_visibility', None) in ['always', 'onchange']:
tracked_values[col_name] = dict(col_info=col_info['string'],
old_value=convert_for_display(initial_value, col_info),
new_value=convert_for_display(record_value, col_info))
if getattr(field, 'track_visibility', None) in ['always', 'onchange']:
tracked_values[col_name] = dict(
col_info=col_info['string'],
old_value=convert_for_display(initial_value, col_info),
new_value=convert_for_display(record_value, col_info),
)
if col_name in tracked_fields:
changes.add(col_name)
if not changes:
@ -681,11 +686,11 @@ class mail_thread(osv.AbstractModel):
res = {}
for record in self.browse(cr, SUPERUSER_ID, ids, context=context):
recipient_ids, email_to, email_cc = set(), False, False
if 'partner_id' in self._all_columns and record.partner_id:
if 'partner_id' in self._fields and record.partner_id:
recipient_ids.add(record.partner_id.id)
elif 'email_from' in self._all_columns and record.email_from:
elif 'email_from' in self._fields and record.email_from:
email_to = record.email_from
elif 'email' in self._all_columns:
elif 'email' in self._fields:
email_to = record.email
res[record.id] = {'partner_ids': list(recipient_ids), 'email_to': email_to, 'email_cc': email_cc}
return res
@ -1398,11 +1403,11 @@ class mail_thread(osv.AbstractModel):
""" Returns suggested recipients for ids. Those are a list of
tuple (partner_id, partner_name, reason), to be managed by Chatter. """
result = dict((res_id, []) for res_id in ids)
if self._all_columns.get('user_id'):
if 'user_id' in self._fields:
for obj in self.browse(cr, SUPERUSER_ID, ids, context=context): # SUPERUSER because of a read on res.users that would crash otherwise
if not obj.user_id or not obj.user_id.partner_id:
continue
self._message_add_suggested_recipient(cr, uid, result, obj, partner=obj.user_id.partner_id, reason=self._all_columns['user_id'].column.string, context=context)
self._message_add_suggested_recipient(cr, uid, result, obj, partner=obj.user_id.partner_id, reason=self._fields['user_id'].string, context=context)
return result
def _find_partner_from_emails(self, cr, uid, id, emails, model=None, context=None, check_followers=True):
@ -1775,8 +1780,8 @@ class mail_thread(osv.AbstractModel):
if auto_follow_fields is None:
auto_follow_fields = ['user_id']
user_field_lst = []
for name, column_info in self._all_columns.items():
if name in auto_follow_fields and name in updated_fields and getattr(column_info.column, 'track_visibility', False) and column_info.column._obj == 'res.users':
for name, field in self._fields.items():
if name in auto_follow_fields and name in updated_fields and getattr(field, 'track_visibility', False) and field.comodel_name == 'res.users':
user_field_lst.append(name)
return user_field_lst
@ -1916,7 +1921,7 @@ class mail_thread(osv.AbstractModel):
# TDE HACK: originally by MAT from portal/mail_mail.py but not working until the inheritance graph bug is not solved in trunk
# TDE FIXME: relocate in portal when it won't be necessary to reload the hr.employee model in an additional bridge module
if self.pool['res.groups']._all_columns.get('is_portal'):
if 'is_portal' in self.pool['res.groups']._fields:
user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context)
if any(group.is_portal for group in user.groups_id):
return []

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})
# Data: alter mail_group model for testing purposes (test on classic, selection and many2one fields)
self.mail_group._track = {
cls = type(self.mail_group)
self.assertNotIn('_track', cls.__dict__)
cls._track = {
'public': {
'mail.mt_private': lambda self, cr, uid, obj, ctx=None: obj.public == 'private',
},
@ -839,12 +841,16 @@ class test_mail(TestMail):
'mail.mt_group_public': lambda self, cr, uid, obj, ctx=None: True,
},
}
public_col = self.mail_group._columns.get('public')
name_col = self.mail_group._columns.get('name')
group_public_col = self.mail_group._columns.get('group_public_id')
public_col.track_visibility = 'onchange'
name_col.track_visibility = 'always'
group_public_col.track_visibility = 'onchange'
visibility = {'public': 'onchange', 'name': 'always', 'group_public_id': 'onchange'}
for key in visibility:
self.assertFalse(hasattr(getattr(cls, key), 'track_visibility'))
getattr(cls, key).track_visibility = visibility[key]
@self.addCleanup
def cleanup():
delattr(cls, '_track')
for key in visibility:
del getattr(cls, key).track_visibility
# Test: change name -> always tracked, not related to a subtype
self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'public': 'public'})
@ -903,9 +909,3 @@ class test_mail(TestMail):
self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'description': 'Dummy'})
self.group_pigs.refresh()
self.assertEqual(len(self.group_pigs.message_ids), 6, 'tracked: No message should have been produced')
# Data: removed changes
public_col.track_visibility = None
name_col.track_visibility = None
group_public_col.track_visibility = None
self.mail_group._track = {}

View File

@ -32,7 +32,7 @@ class publisher_warranty_contract(AbstractModel):
nbr_active_users = user_count([("login_date", ">=", limit_date_str)])
nbr_share_users = 0
nbr_active_share_users = 0
if "share" in Users._all_columns:
if "share" in Users._fields:
nbr_share_users = user_count([("share", "=", True)])
nbr_active_share_users = user_count([("share", "=", True), ("login_date", ">=", limit_date_str)])
user = Users.browse(cr, uid, uid)

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

View File

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

View File

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

View File

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

View File

@ -94,7 +94,7 @@ class PaymentAcquirer(osv.Model):
""" If the field has 'required_if_provider="<provider>"' attribute, then it
required if record.provider is <provider>. """
for acquirer in self.browse(cr, uid, ids, context=context):
if any(c for c, f in self._all_columns.items() if getattr(f.column, 'required_if_provider', None) == acquirer.provider and not acquirer[c]):
if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in self._fields.items()):
return False
return True

View File

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

View File

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

View File

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

View File

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

View File

@ -535,7 +535,7 @@ class website(osv.osv):
ids = Model.search(cr, uid,
[('id', '=', id)], context=context)
if not ids and 'website_published' in Model._all_columns:
if not ids and 'website_published' in Model._fields:
ids = Model.search(cr, openerp.SUPERUSER_ID,
[('id', '=', id), ('website_published', '=', True)], context=context)
if not ids:

View File

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

View File

@ -62,7 +62,7 @@ class contactus(http.Controller):
for field_name, field_value in kwargs.items():
if hasattr(field_value, 'filename'):
post_file.append(field_value)
elif field_name in request.registry['crm.lead']._all_columns and field_name not in _BLACKLIST:
elif field_name in request.registry['crm.lead']._fields and field_name not in _BLACKLIST:
values[field_name] = field_value
elif field_name not in _TECHNICAL: # allow to add some free fields or blacklisted field like ID
post_description.append("%s: %s" % (field_name, field_value))

View File

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

View File

@ -616,16 +616,16 @@ class ir_actions_server(osv.osv):
# analyze path
while path:
step = path.pop(0)
column_info = self.pool[current_model_name]._all_columns.get(step)
if not column_info:
field = self.pool[current_model_name]._fields.get(step)
if not field:
return (False, None, 'Part of the expression (%s) is not recognized as a column in the model %s.' % (step, current_model_name))
column_type = column_info.column._type
if column_type not in ['many2one', 'int']:
return (False, None, 'Part of the expression (%s) is not a valid column type (is %s, should be a many2one or an int)' % (step, column_type))
if column_type == 'int' and path:
ftype = field.type
if ftype not in ['many2one', 'int']:
return (False, None, 'Part of the expression (%s) is not a valid column type (is %s, should be a many2one or an int)' % (step, ftype))
if ftype == 'int' and path:
return (False, None, 'Part of the expression (%s) is an integer field that is only allowed at the end of an expression' % (step))
if column_type == 'many2one':
current_model_name = column_info.column._obj
if ftype == 'many2one':
current_model_name = field.comodel_name
return (True, current_model_name, None)
def _check_write_expression(self, cr, uid, ids, context=None):

View File

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

View File

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

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

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

View File

@ -423,7 +423,7 @@ class ir_values(osv.osv):
if action_model_name not in self.pool:
continue # unknow model? skip it
action_model = self.pool[action_model_name]
fields = [field for field in action_model._all_columns if field not in EXCLUDED_FIELDS]
fields = [field for field in action_model._fields if field not in EXCLUDED_FIELDS]
# FIXME: needs cleanup
try:
action_def = action_model.read(cr, uid, int(action_id), fields, context)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1106,7 +1106,7 @@ class expression(object):
# final sanity checks - should never fail
assert operator in (TERM_OPERATORS + ('inselect', 'not inselect')), \
"Invalid operator %r in domain term %r" % (operator, leaf)
assert leaf in (TRUE_LEAF, FALSE_LEAF) or left in model._all_columns \
assert leaf in (TRUE_LEAF, FALSE_LEAF) or left in model._fields \
or left in MAGIC_COLUMNS, "Invalid field %r in domain term %r" % (left, leaf)
assert not isinstance(right, BaseModel), \
"Invalid value %r in domain term %r" % (right, leaf)

View File

@ -178,6 +178,7 @@ class _column(object):
('groups', self.groups),
('change_default', self.change_default),
('deprecated', self.deprecated),
('group_operator', self.group_operator),
('size', self.size),
('ondelete', self.ondelete),
('translate', self.translate),
@ -746,31 +747,32 @@ class one2many(_column):
elif act[0] == 2:
obj.unlink(cr, user, [act[1]], context=context)
elif act[0] == 3:
reverse_rel = obj._all_columns.get(self._fields_id)
assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
inverse_field = obj._fields.get(self._fields_id)
assert inverse_field, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
# if the model has on delete cascade, just delete the row
if reverse_rel.column.ondelete == "cascade":
if inverse_field.ondelete == "cascade":
obj.unlink(cr, user, [act[1]], context=context)
else:
cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
elif act[0] == 4:
# table of the field (parent_model in case of inherit)
field_model = self._fields_id in obj.pool[self._obj]._columns and self._obj or obj.pool[self._obj]._all_columns[self._fields_id].parent_model
field = obj.pool[self._obj]._fields[self._fields_id]
field_model = field.base_field.model_name
field_table = obj.pool[field_model]._table
cr.execute("select 1 from {0} where id=%s and {1}=%s".format(field_table, self._fields_id), (act[1], id))
if not cr.fetchone():
# Must use write() to recompute parent_store structure if needed and check access rules
obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
elif act[0] == 5:
reverse_rel = obj._all_columns.get(self._fields_id)
assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
inverse_field = obj._fields.get(self._fields_id)
assert inverse_field, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
# if the o2m has a static domain we must respect it when unlinking
domain = self._domain(obj) if callable(self._domain) else self._domain
extra_domain = domain or []
ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
# If the model has cascade deletion, we delete the rows because it is the intended behavior,
# otherwise we only nullify the reverse foreign key column.
if reverse_rel.column.ondelete == "cascade":
if inverse_field.ondelete == "cascade":
obj.unlink(cr, user, ids_to_unlink, context=context)
else:
obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
@ -1611,12 +1613,12 @@ class property(function):
res = {id: {} for id in ids}
for prop_name in prop_names:
column = obj._all_columns[prop_name].column
field = obj._fields[prop_name]
values = ir_property.get_multi(cr, uid, prop_name, obj._name, ids, context=context)
if column._type == 'many2one':
if field.type == 'many2one':
# name_get the non-null values as SUPERUSER_ID
vals = sum(set(filter(None, values.itervalues())),
obj.pool[column._obj].browse(cr, uid, [], context=context))
obj.pool[field.comodel_name].browse(cr, uid, [], context=context))
vals_name = dict(vals.sudo().name_get()) if vals else {}
for id, value in values.iteritems():
ng = False

View File

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

View File

@ -482,15 +482,15 @@ class YamlInterpreter(object):
record_dict[field_name] = field_value
return record_dict
def process_ref(self, node, column=None):
def process_ref(self, node, field=None):
assert node.search or node.id, '!ref node should have a `search` attribute or `id` attribute'
if node.search:
if node.model:
model_name = node.model
elif column:
model_name = column._obj
elif field:
model_name = field.comodel_name
else:
raise YamlImportException('You need to give a model for the search, or a column to infer it.')
raise YamlImportException('You need to give a model for the search, or a field to infer it.')
model = self.get_model(model_name)
q = eval(node.search, self.eval_context)
ids = model.search(self.cr, self.uid, q)
@ -510,34 +510,32 @@ class YamlInterpreter(object):
def _eval_field(self, model, field_name, expression, view_info=False, parent={}, default=True):
# TODO this should be refactored as something like model.get_field() in bin/osv
if field_name in model._columns:
column = model._columns[field_name]
elif field_name in model._inherit_fields:
column = model._inherit_fields[field_name][2]
else:
if field_name not in model._fields:
raise KeyError("Object '%s' does not contain field '%s'" % (model, field_name))
field = model._fields[field_name]
if is_ref(expression):
elements = self.process_ref(expression, column)
if column._type in ("many2many", "one2many"):
elements = self.process_ref(expression, field)
if field.type in ("many2many", "one2many"):
value = [(6, 0, elements)]
else: # many2one
if isinstance(elements, (list,tuple)):
value = self._get_first_result(elements)
else:
value = elements
elif column._type == "many2one":
elif field.type == "many2one":
value = self.get_id(expression)
elif column._type == "one2many":
other_model = self.get_model(column._obj)
elif field.type == "one2many":
other_model = self.get_model(field.comodel_name)
value = [(0, 0, self._create_record(other_model, fields, view_info, parent, default=default)) for fields in expression]
elif column._type == "many2many":
elif field.type == "many2many":
ids = [self.get_id(xml_id) for xml_id in expression]
value = [(6, 0, ids)]
elif column._type == "date" and is_string(expression):
elif field.type == "date" and is_string(expression):
# enforce ISO format for string date values, to be locale-agnostic during tests
time.strptime(expression, misc.DEFAULT_SERVER_DATE_FORMAT)
value = expression
elif column._type == "datetime" and is_string(expression):
elif field.type == "datetime" and is_string(expression):
# enforce ISO format for string datetime values, to be locale-agnostic during tests
time.strptime(expression, misc.DEFAULT_SERVER_DATETIME_FORMAT)
value = expression
@ -546,7 +544,7 @@ class YamlInterpreter(object):
value = self.process_eval(expression)
else:
value = expression
# raise YamlImportException('Unsupported column "%s" or value %s:%s' % (field_name, type(expression), expression))
# raise YamlImportException('Unsupported field "%s" or value %s:%s' % (field_name, type(expression), expression))
return value
def process_context(self, node):