From e1a17b433d87d871e1e215b7dfd182faa274eb5f Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Tue, 19 Apr 2016 16:45:19 +0200 Subject: [PATCH] [FIX] audittrail: prevent loop between models having *2m between each other When setting rules on models having o2m/m2m relationship between each other e.g. - `res.partner`: o2m `sale_order_ids` to `sale.order` - `sale.order` o2m `message_follower_ids` to `res.partner` an infinite loop could occur if records of these models referenced records of the other models in their o2m relationships e.g. - `res.partner` ID 68: `sale.order` ID 9 in its `sale_order_ids` - `sale.order` ID 9: `res.partner` ID 68 in its `message_partner_ids` This revision solves this use case, by passing the already treated records in the context and checking that the records haven't yet be treated before making the recursive call. This revision makes sure to not break the API of methods `get_data_context` and `prepare_audittrail_log_line` (a new parameter had to be introduced for the above purpose) opw-670904 --- addons/audittrail/audittrail.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/addons/audittrail/audittrail.py b/addons/audittrail/audittrail.py index b18978a491c..e3c17105e13 100644 --- a/addons/audittrail/audittrail.py +++ b/addons/audittrail/audittrail.py @@ -309,7 +309,7 @@ class audittrail_objects_proxy(object_proxy): self.process_data(cr, uid_orig, pool, res_ids, model, method, old_values, new_values, field_list) return res - def get_data(self, cr, uid, pool, res_ids, model, method): + def get_data_context(self, cr, uid, pool, res_ids, model, method, context=None): """ This function simply read all the fields of the given res_ids, and also recurisvely on all records of a x2m fields read that need to be logged. Then it returns the result in @@ -328,6 +328,8 @@ class audittrail_objects_proxy(object_proxy): }, } """ + if not context: + context = {} data = {} resource_pool = pool.get(model.model) # read all the fields of the given resources in super admin mode @@ -357,12 +359,20 @@ class audittrail_objects_proxy(object_proxy): # we need to remove current resource_id from the many2many to prevent an infinit loop if resource_id in field_resource_ids: field_resource_ids.remove(resource_id) - data.update(self.get_data(cr, SUPERUSER_ID, pool, field_resource_ids, x2m_model, method)) + for treated_resource_id in context.get('audittrail_treated_records', {}).get((model.model, resource_id), {}).get(x2m_model.model, []): + if treated_resource_id in field_resource_ids: + field_resource_ids.remove(treated_resource_id) + audittrail_context = dict(context) + audittrail_context.setdefault('audittrail_treated_records', {}).setdefault((model.model, resource_id), {}).setdefault(x2m_model.model, []).extend(field_resource_ids) + data.update(self.get_data_context(cr, SUPERUSER_ID, pool, field_resource_ids, x2m_model, method, context=audittrail_context)) data[(model.id, resource_id)] = {'text':values_text, 'value': values} return data - def prepare_audittrail_log_line(self, cr, uid, pool, model, resource_id, method, old_values, new_values, field_list=None): + def get_data(self, cr, uid, pool, res_ids, model, method): + return self.get_data_context(cr, uid, pool, res_ids, model, method) + + def prepare_audittrail_log_line_context(self, cr, uid, pool, model, resource_id, method, old_values, new_values, field_list=None, context=None): """ This function compares the old data (i.e before the method was executed) and the new data (after the method was executed) and returns a structure with all the needed information to @@ -391,6 +401,8 @@ class audittrail_objects_proxy(object_proxy): The reason why the structure returned is build as above is because when modifying an existing record, we may have to log a change done in a x2many field of that object """ + if not context: + context = {} if field_list is None: field_list = [] key = (model.id, resource_id) @@ -423,8 +435,13 @@ class audittrail_objects_proxy(object_proxy): # we need to remove current resource_id from the many2many to prevent an infinit loop if resource_id in res_ids: res_ids.remove(resource_id) + for treated_resource_id in context.get('audittrail_treated_records', {}).get((model.model, resource_id), {}).get(x2m_model.model, []): + if treated_resource_id in res_ids: + res_ids.remove(treated_resource_id) + audittrail_context = dict(context) + audittrail_context.setdefault('audittrail_treated_records', {}).setdefault((model.model, resource_id), {}).setdefault(x2m_model.model, []).extend(res_ids) for res_id in res_ids: - lines.update(self.prepare_audittrail_log_line(cr, SUPERUSER_ID, pool, x2m_model, res_id, method, old_values, new_values, field_list)) + lines.update(self.prepare_audittrail_log_line_context(cr, SUPERUSER_ID, pool, x2m_model, res_id, method, old_values, new_values, field_list, context=audittrail_context)) # if the value value is different than the old value: record the change if key not in old_values or key not in new_values or old_values[key]['value'][field_name] != new_values[key]['value'][field_name]: data = { @@ -445,6 +462,10 @@ class audittrail_objects_proxy(object_proxy): lines[key].append(data) return lines + def prepare_audittrail_log_line(self, cr, uid, pool, model, resource_id, method, old_values, new_values, field_list=None): + return self.prepare_audittrail_log_line_context(cr, uid, pool, model, resource_id, method, old_values, new_values, field_list=field_list) + + def process_data(self, cr, uid, pool, res_ids, model, method, old_values=None, new_values=None, field_list=None): """ This function processes and iterates recursively to log the difference between the old