# 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)
# 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]
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):
# 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
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]
_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'
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')
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
# 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
# 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
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
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)
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)
# 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:
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
""" 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):
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
# 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 []
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',
},
'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'})
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 = {}
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)
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)
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)
'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)
#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"]]):
# 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')
""" 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
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):
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)
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))
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):
_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()
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
('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
_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)
_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)
# 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
_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)
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))
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)
_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()
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
_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):
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?
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:
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')
# 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"
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))
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)
# 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
# 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):
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):
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
: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
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
it returns. The handling of a warning at the upper levels is the same
as ``ValueError`` above.
- :param column: column object to generate a value for
- :type column: :class:`fields._column`
- :param fromtype: type to convert to something fitting for ``column``
+ :param field: field object to generate a value for
+ :type field: :class:`openerp.fields.Field`
+ :param fromtype: type to convert to something fitting for ``field``
:type fromtype: type | str
:param context: openerp request context
- :return: a function (fromtype -> column.write_type), if a converter is found
+ :return: a function (fromtype -> field.write_type), if a converter is found
:rtype: Callable | None
"""
assert isinstance(fromtype, (type, str))
# FIXME: return None
typename = fromtype.__name__ if isinstance(fromtype, type) else fromtype
- converter = getattr(
- self, '_%s_to_%s' % (typename, column._type), None)
- if not converter: return None
-
- return functools.partial(
- converter, cr, uid, model, column, context=context)
+ converter = getattr(self, '_%s_to_%s' % (typename, field.type), None)
+ if not converter:
+ return None
+ return functools.partial(converter, model, field)
- def _str_to_boolean(self, cr, uid, model, column, value, context=None):
+ @api.model
+ def _str_to_boolean(self, model, field, value):
# all translatables used for booleans
true, yes, false, no = _(u"true"), _(u"yes"), _(u"false"), _(u"no")
# potentially broken casefolding? What about locales?
trues = set(word.lower() for word in itertools.chain(
[u'1', u"true", u"yes"], # don't use potentially translated values
- self._get_translations(cr, uid, ['code'], u"true", context=context),
- self._get_translations(cr, uid, ['code'], u"yes", context=context),
+ self._get_translations(['code'], u"true"),
+ self._get_translations(['code'], u"yes"),
))
- if value.lower() in trues: return True, []
+ if value.lower() in trues:
+ return True, []
# potentially broken casefolding? What about locales?
falses = set(word.lower() for word in itertools.chain(
[u'', u"0", u"false", u"no"],
- self._get_translations(cr, uid, ['code'], u"false", context=context),
- self._get_translations(cr, uid, ['code'], u"no", context=context),
+ self._get_translations(['code'], u"false"),
+ self._get_translations(['code'], u"no"),
))
- if value.lower() in falses: return False, []
+ if value.lower() in falses:
+ return False, []
return True, [ImportWarning(
_(u"Unknown value '%s' for boolean field '%%(field)s', assuming '%s'")
'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:
_(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:
_(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, []
'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)
'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), {
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
warnings
:rtype: (ID|None, unicode, list)
"""
- if context is None: context = {}
id = None
warnings = []
action = {'type': 'ir.actions.act_window', 'target': 'new',
'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
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(
[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 = []
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)
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))
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)
* ``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),
('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):
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__'):
_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'
_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)
_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)
_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.
"""
_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]
_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):
_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()
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']
_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"))
_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)
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)
_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
}
_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
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
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]
'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
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')
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)
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):
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']:
self.pool._init = False
# Force partner_categ.copy() to copy children
- self.pool['res.partner.category']._columns['child_ids'].copy = True
+ self._fields['child_ids'].copy = True
+ self._columns['child_ids'].copy = True
-
"1.0 Setup test partner categories: parent root"
-
-
!python {model: res.partner.category}: |
self.pool._init = True
- self.pool['res.partner.category']._columns['child_ids'].copy = False
+ self._fields['child_ids'].copy = False
+ self._columns['child_ids'].copy = False
-
"Float precision tests: verify that float rounding methods are working correctly via res.currency"
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):
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)
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'
})
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")
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:
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
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):
'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):
: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
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)
_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
#
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):
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)
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!
# 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
* "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))
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))
"""
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)
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)
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
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,))
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:
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)
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:
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
# 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():
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)
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]
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])
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
: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 = []
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)
# 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)
('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),
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)
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
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
# 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
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)
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
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):