[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:
parent
0f52e22906
commit
f2e4a10e1a
|
@ -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]
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 []
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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']:
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue