raise KeyError(error_msg)
# if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
- if col._prefetch:
+ if col._prefetch and not col.groups:
# gen the list of "local" (ie not inherited) fields which are classic or many2one
- fields_to_fetch = filter(lambda x: x[1]._classic_write, self._table._columns.items())
+ field_filter = lambda x: x[1]._classic_write and x[1]._prefetch and not x[1].groups
+ fields_to_fetch = filter(field_filter, self._table._columns.items())
# gen the list of inherited fields
inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items())
# complete the field list with the inherited fields which are classic or many2one
- fields_to_fetch += filter(lambda x: x[1]._classic_write, inherits)
+ fields_to_fetch += filter(field_filter, inherits)
# otherwise we fetch only that field
else:
fields_to_fetch = [(name, col)]
+
ids = filter(lambda id: name not in self._data[id], self._data.keys())
# read the results
field_names = map(lambda x: x[0], fields_to_fetch)
- field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
+ try:
+ field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
+ except (openerp.exceptions.AccessError, except_orm):
+ if len(ids) == 1:
+ raise
+ # prefetching attempt failed, perhaps we're violating ACL restrictions involuntarily
+ _logger.info('Prefetching attempt for fields %s on %s failed for ids %s, re-trying just for id %s', field_names, self._model._name, ids, self._id)
+ ids = [self._id]
+ field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
# TODO: improve this, very slow for reports
if self._fields_process:
self._module = module_name
# Remember which models to instanciate for this module.
- self.module_to_models.setdefault(self._module, []).append(self)
+ if not self._custom:
+ self.module_to_models.setdefault(self._module, []).append(self)
# Definition of log access columns, automatically added to models if
_name = None
_columns = {}
_constraints = []
+ _custom = False
_defaults = {}
_rec_name = None
_parent_name = 'parent_id'
# managed by the metaclass.
module_model_list = MetaModel.module_to_models.setdefault(cls._module, [])
if cls not in module_model_list:
- module_model_list.append(cls)
+ if not cls._custom:
+ module_model_list.append(cls)
# Since we don't return an instance here, the __init__
# method won't be called.
raise except_orm('Error',
('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
self.pool._store_function.setdefault(object, [])
- self.pool._store_function[object].append((self._name, store_field, fnct, tuple(fields2) if fields2 else None, order, length))
- self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
+ t = (self._name, store_field, fnct, tuple(fields2) if fields2 else None, order, length)
+ if not t in self.pool._store_function[object]:
+ self.pool._store_function[object].append((self._name, store_field, fnct, tuple(fields2) if fields2 else None, order, length))
+ self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
for (key, _, msg) in self._sql_constraints:
self.pool._sql_error[self._table+'_'+key] = msg
'required': bool(field['required']),
'readonly': bool(field['readonly']),
'domain': eval(field['domain']) if field['domain'] else None,
- 'size': field['size'],
+ 'size': field['size'] or None,
'ondelete': field['on_delete'],
'translate': (field['translate']),
'manual': True,
# Validate rec_name
if self._rec_name is not None:
- assert self._rec_name in self._columns.keys() + ['id'], "Invalid rec_name %s for model %s" % (self._rec_name, self._name)
+ assert self._rec_name in self._all_columns.keys() + ['id'], "Invalid rec_name %s for model %s" % (self._rec_name, self._name)
else:
self._rec_name = 'name'
noupdate=noupdate, res_id=id, context=context))
cr.execute('RELEASE SAVEPOINT model_load_save')
except psycopg2.Warning, e:
- _logger.exception('Failed to import record %s', record)
messages.append(dict(info, type='warning', message=str(e)))
cr.execute('ROLLBACK TO SAVEPOINT model_load_save')
except psycopg2.Error, e:
- _logger.exception('Failed to import record %s', record)
messages.append(dict(
info, type='error',
**PGERROR_TO_OE[e.pgcode](self, fg, info, e)))
are applied
"""
- sql_inherit = self.pool.get('ir.ui.view').get_inheriting_views_arch(cr, user, inherit_id, self._name)
+ sql_inherit = self.pool.get('ir.ui.view').get_inheriting_views_arch(cr, user, inherit_id, self._name, context=context)
for (view_arch, view_id) in sql_inherit:
source = apply_inheritance_specs(source, view_arch, view_id)
source = apply_view_inheritance(cr, user, source, view_id)
"""
Record the creation of a constraint for this model, to make it possible
to delete it later when the module is uninstalled. Type can be either
- 'f' or 'u' depending on the constraing being a foreign key or not.
+ 'f' or 'u' depending on the constraint being a foreign key or not.
"""
+ if not self._module:
+ # no need to save constraints for custom models as they're not part
+ # of any module
+ return
assert type in ('f', 'u')
cr.execute("""
SELECT 1 FROM ir_model_constraint, ir_module_module
if field_name not in self._all_columns:
return True
field = self._all_columns[field_name].column
- if field.groups:
+ if user != SUPERUSER_ID and field.groups:
return self.user_has_groups(cr, user, groups=field.groups, context=context)
else:
return True
upd1 += ",%s,(now() at time zone 'UTC'),%s,(now() at time zone 'UTC')"
upd2.extend((user, user))
cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2))
- self.check_access_rule(cr, user, [id_new], 'create', context=context)
upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority)
if self._parent_store and not context.get('defer_parent_store_computation'):
self.name_get(cr, user, [id_new], context=context)[0][1] + \
"' " + _("created.")
self.log(cr, user, id_new, message, True, context=context)
+ self.check_access_rule(cr, user, [id_new], 'create', context=context)
self._workflow_trigger(cr, user, [id_new], 'trg_create', context=context)
return id_new
if ((not f[trigger_fields_]) or set(fields).intersection(f[trigger_fields_]))]
mapping = {}
+ fresults = {}
for function in to_compute:
- # use admin user for accessing objects having rules defined on store fields
- target_ids = [id for id in function[id_mapping_fnct_](self, cr, SUPERUSER_ID, ids, context) if id]
+ fid = id(function[id_mapping_fnct_])
+ if not fid in fresults:
+ # use admin user for accessing objects having rules defined on store fields
+ fresults[fid] = [id2 for id2 in function[id_mapping_fnct_](self, cr, SUPERUSER_ID, ids, context) if id2]
+ target_ids = fresults[fid]
# the compound key must consider the priority and model name
key = (function[priority_], function[model_name_])
functions_ids_maps = {}
# function_ids_maps =
# { (function_1_tuple, function_2_tuple) : [target_id1, target_id2, ..] }
- for id, functions in id_map.iteritems():
- functions_ids_maps.setdefault(tuple(functions), []).append(id)
+ for fid, functions in id_map.iteritems():
+ functions_ids_maps.setdefault(tuple(functions), []).append(fid)
for functions, ids in functions_ids_maps.iteritems():
call_map.setdefault((priority,model),[]).append((priority, model, ids,
[f[func_field_to_compute_] for f in functions]))
order_field = order_split[0].strip()
order_direction = order_split[1].strip() if len(order_split) == 2 else ''
inner_clause = None
- if order_field == 'id':
- order_by_elements.append('"%s"."id" %s' % (self._table, order_direction))
+ if order_field == 'id' or (self._log_access and order_field in LOG_ACCESS_COLUMNS.keys()):
+ order_by_elements.append('"%s"."%s" %s' % (self._table, order_field, order_direction))
elif order_field in self._columns:
order_column = self._columns[order_field]
if order_column._classic_read:
inner_clause = self._generate_m2o_order_by(order_field, query)
else:
continue # ignore non-readable or "non-joinable" fields
+ else:
+ raise ValueError( _("Sorting field %s not found on model %s") %( order_field, self._name))
if inner_clause:
if isinstance(inner_clause, list):
for clause in inner_clause:
else:
default['state'] = self._defaults['state']
- context_wo_lang = context.copy()
- if 'lang' in context:
- del context_wo_lang['lang']
- data = self.read(cr, uid, [id,], context=context_wo_lang)
+ data = self.read(cr, uid, [id,], context=context)
if data:
data = data[0]
else:
# TODO it seems fields_get can be replaced by _all_columns (no need for translation)
fields = self.fields_get(cr, uid, context=context)
- translation_records = []
for field_name, field_def in fields.items():
+ # 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.get(field_def['relation'])
- old_record, new_record = self.read(cr, uid, [old_id, new_id], [field_name], context=context)
# here we rely on the order of the ids to match the translations
# as foreseen in copy_data()
- old_children = sorted(old_record[field_name])
- new_children = sorted(new_record[field_name])
+ old_children = sorted(r.id for r in old_record[field_name])
+ new_children = sorted(r.id for r in new_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'):
- trans_name = ''
if field_name in self._columns:
trans_name = self._name + "," + field_name
+ target_id = new_id
+ source_id = old_id
elif field_name in self._inherit_fields:
trans_name = self._inherit_fields[field_name][0] + "," + field_name
- if trans_name:
- trans_ids = trans_obj.search(cr, uid, [
- ('name', '=', trans_name),
- ('res_id', '=', old_id)
- ])
- translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context))
+ # get the id of the parent record to set the translation
+ inherit_field_name = self._inherit_fields[field_name][1]
+ target_id = new_record[inherit_field_name].id
+ source_id = old_record[inherit_field_name].id
+ else:
+ continue
- for record in translation_records:
- del record['id']
- record['res_id'] = new_id
- trans_obj.create(cr, uid, record, context=context)
+ trans_ids = trans_obj.search(cr, uid, [
+ ('name', '=', trans_name),
+ ('res_id', '=', source_id)
+ ])
+ user_lang = context.get('lang')
+ for record in trans_obj.read(cr, uid, trans_ids, context=context):
+ del record['id']
+ # remove source to avoid triggering _set_src
+ del record['source']
+ record.update({'res_id': target_id})
+ if user_lang and user_lang == record['lang']:
+ # 'source' to force the call to _set_src
+ # 'value' needed if value is changed in copy(), want to see the new_value
+ record['source'] = old_record[field_name]
+ record['value'] = new_record[field_name]
+ trans_obj.create(cr, uid, record, context=context)
def copy(self, cr, uid, id, default=None, context=None):
:param parent: optional parent field name (default: ``self._parent_name = parent_id``)
:return: **True** if the operation can proceed safely, or **False** if an infinite loop is detected.
"""
-
if not parent:
parent = self._parent_name
- ids_parent = ids[:]
- query = 'SELECT distinct "%s" FROM "%s" WHERE id IN %%s' % (parent, self._table)
- while ids_parent:
- ids_parent2 = []
- for i in range(0, len(ids), cr.IN_MAX):
- sub_ids_parent = ids_parent[i:i+cr.IN_MAX]
- cr.execute(query, (tuple(sub_ids_parent),))
- ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall())))
- ids_parent = ids_parent2
- for i in ids_parent:
- if i in ids:
+
+ # must ignore 'active' flag, ir.rules, etc. => direct SQL query
+ query = 'SELECT "%s" FROM "%s" WHERE id = %%s' % (parent, self._table)
+ for id in ids:
+ current_id = id
+ while current_id is not None:
+ cr.execute(query, (current_id,))
+ result = cr.fetchone()
+ current_id = result[0] if result else None
+ if current_id == id:
return False
return True
'message': message,
'field': field_name,
}
+def convert_pgerror_23505(model, fields, info, e):
+ m = re.match(r'^duplicate key (?P<field>\w+) violates unique constraint',
+ str(e))
+ field_name = m.group('field')
+ if not m or field_name not in fields:
+ return {'message': unicode(e)}
+ message = _(u"The value for the field '%s' already exists.") % field_name
+ field = fields.get(field_name)
+ if field:
+ message = _(u"%s This might be '%s' in the current model, or a field "
+ u"of the same name in an o2m.") % (message, field['string'])
+ return {
+ 'message': message,
+ 'field': field_name,
+ }
PGERROR_TO_OE = collections.defaultdict(
# shape of mapped converters
lambda: (lambda model, fvg, info, pgerror: {'message': unicode(pgerror)}), {
# not_null_violation
'23502': convert_pgerror_23502,
+ # unique constraint error
+ '23505': convert_pgerror_23505,
})
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: