Merge lp:~openerp-dev/openobject-addons/trunk-bug-832635-nch into lp:openobject-addons

Proposed by Naresh(OpenERP)
Status: Merged
Merged at revision: 5754
Proposed branch: lp:~openerp-dev/openobject-addons/trunk-bug-832635-nch
Merge into: lp:openobject-addons
Diff against target: 591 lines (+267/-240)
1 file modified
audittrail/audittrail.py (+267/-240)
To merge this branch: bzr merge lp:~openerp-dev/openobject-addons/trunk-bug-832635-nch
Reviewer Review Type Date Requested Status
qdp (OpenERP) Needs Fixing
Antony Lesuisse (OpenERP) Pending
Vo Minh Thu Pending
Olivier Dony (Odoo) Pending
Review via email: mp+77861@code.launchpad.net

This proposal supersedes a proposal from 2011-09-07.

Description of the change

Hello,

This merge proposal contains fix for the linked bug as well as refactoring and improvement of the audit trail module.

please provide your feedback,

Thanks,

To post a comment you must log in.
Revision history for this message
Antony Lesuisse (OpenERP) (al-openerp) wrote : Posted in a previous version of this proposal

Could you update your merge proposal to the lastest trunk i committed a refactoring to
- override *_cr method to reuse the cursor.
- defer any database access after checking that auditrail is actually installed.

review: Needs Fixing
Revision history for this message
Antony Lesuisse (OpenERP) (al-openerp) wrote : Posted in a previous version of this proposal

Please use trunk version of

check_rules
execute_cr
exec_workflow_cr

instead of

check_rule_subscription
audit_log_call

because the former use _cr, are shorter and much faster (no useless db hits).
Remove all cr.commit().

review: Needs Fixing
Revision history for this message
Naresh(OpenERP) (nch-openerp) wrote : Posted in a previous version of this proposal

hello,

made compatible to new signatures...

Thanks,

Revision history for this message
qdp (OpenERP) (qdp) wrote :

Hi Naresh,

i've tested but i found a problem (Not sure i didn't break it myself during the merge, though):
*given you have an audittrail rule for res.partner and res.partner.address
*given you have a partner Agrolait with 4 existing addresses
*if you create a new address for this partner, it will create you the following logs
   - one write on contatcs fields of res.partner (correct)
   - four empty writes on res.partner.address where no changes are logged (not correct)
   - one write on res.partner.address with the data you just created (incorrect, the method logged should be 'create' instead of 'write', otherwise it's ok)

i've commited in your branch my merge with trunk, not to loose my work. You should starts from it. Also, let me know if i need to assign someone else on this task, as i'm not aware of your current load (but as you made this merge prop you'll probably be faster than anyone for it ;-) ).

Btw, i'm setting this merge prop as 'work in progress' so that it's not anymore on the pending merge we need to review. Reset its state back to 'needs review' when you're work is over.

Thanks,
Quentin

review: Needs Fixing
Revision history for this message
Naresh(OpenERP) (nch-openerp) wrote :

Hi Qdp,

Thanks for the review ! from your review I just found 1 bug(fixed it in the last commit) and for the rest I made gave explanation. Correct me If I missed here something.

> Hi Naresh,
>
> i've tested but i found a problem (Not sure i didn't break it myself during
> the merge, though):
> *given you have an audittrail rule for res.partner and res.partner.address
> *given you have a partner Agrolait with 4 existing addresses
> *if you create a new address for this partner, it will create you the
> following logs
> - one write on contatcs fields of res.partner (correct)

> - four empty writes on res.partner.address where no changes are logged (not
> correct)

yes, actual bug Fixed it in the last commit.

> - one write on res.partner.address with the data you just created
> (incorrect, the method logged should be 'create' instead of 'write', otherwise
> it's ok)

 according to our architecture the relational table like o2m (create,unlink,write,read) operations are handled by the server itself.i.e we pass an arg at the start of the tuple with values as '0':create, '1':write,'2':unlink etc which is used by server to perform the specific operation on the relational model.now as to log we need to hack the execution of the method before the server actually executes. So if you create a parent record along with a child the client issues a 'create' call for the parent record along with the values which will be logged as create but if you add a child record in an already saved parent record the client just issues a 'write' call for the parent record(which is correct) and the server handles the creation of the child records internally. Same is the case for *unlink*.

>
> i've commited in your branch my merge with trunk, not to loose my work. You
> should starts from it. Also, let me know if i need to assign someone else on
> this task, as i'm not aware of your current load (but as you made this merge
> prop you'll probably be faster than anyone for it ;-) ).

Nop, I managed it...:)
>
> Btw, i'm setting this merge prop as 'work in progress' so that it's not
> anymore on the pending merge we need to review. Reset its state back to 'needs
> review' when you're work is over.
>
 Needs review...work over !
> Thanks,
> Quentin

Regards,
Naresh

Revision history for this message
qdp (OpenERP) (qdp) wrote :

> - four empty writes on res.partner.address where no changes are logged (not
> correct)
yes but no, you didn't implemented it in the right way: what you should do is avoid creating the audittrail log if there is no log line, instead of creating it then deleting it if there is no line. It will avoid useless operations.

For that, you need to do a small change:
=> replace the function
     self.create_log_line(cr, uid, log_id, model, lines)
   by
     self.create_log(cr, uid, model, lines)

def create_log(self, cr, uid, model, lines=[]):
    if not lines:
        return True
    vals = { ... }
    vals['lines] = [(0, 0, { ... }), ...]
    return self.pool.get('audittrail.log').create(cr, uid, vals, context)

> - one write on res.partner.address with the data you just created
> (incorrect, the method logged should be 'create' instead of 'write', otherwise
> it's ok)
i know, but it's not a argument. You can force the method to use. For example, try to add those 2 lines (it seems doing the trick but i didn't test completly):
=== modified file 'audittrail/audittrail.py'
--- audittrail/audittrail.py 2011-11-08 16:55:34 +0000
+++ audittrail/audittrail.py 2011-11-09 09:43:22 +0000
@@ -354,6 +357,8 @@
                     vals = {'method': method,'object_id': model.id,'user_id': uid_orig }
                     for resource in resource_data:
                         resource_id = resource['id']
+ if resource_id not in dict_to_use.keys():
+ vals.update({'method': 'create'})
                         vals.update({'res_id': resource_id})
                         log_id = log_pool.create(cr, uid, vals)
                         lines = []

i set it back to 'work in progress' ;-)
Quentin

review: Needs Fixing
Revision history for this message
Naresh(OpenERP) (nch-openerp) wrote :

> For that, you need to do a small change:
> => replace the function
> self.create_log_line(cr, uid, log_id, model, lines)
> by
> self.create_log(cr, uid, model, lines)
>
> def create_log(self, cr, uid, model, lines=[]):
> if not lines:
> return True
> vals = { ... }
> vals['lines] = [(0, 0, { ... }), ...]
> return self.pool.get('audittrail.log').create(cr, uid, vals, context)
>

hmm...cannot do that because lines will always be there just the difference is whether the new values is equal to the old value or not if its equal don't create log line ignore it else create it. but of-cause there is a scope of improvement here by checking the condition before appending the lines. and also create the log entry only if there is atleast 1 line.

>
> > - one write on res.partner.address with the data you just created
> > (incorrect, the method logged should be 'create' instead of 'write',
> otherwise
> > it's ok)
> i know, but it's not a argument. You can force the method to use. For example,
> try to add those 2 lines (it seems doing the trick but i didn't test
> completly):
> === modified file 'audittrail/audittrail.py'
> --- audittrail/audittrail.py 2011-11-08 16:55:34 +0000
> +++ audittrail/audittrail.py 2011-11-09 09:43:22 +0000
> @@ -354,6 +357,8 @@
> vals = {'method': method,'object_id': model.id,'user_id':
> uid_orig }
> for resource in resource_data:
> resource_id = resource['id']
> + if resource_id not in dict_to_use.keys():
> + vals.update({'method': 'create'})
> vals.update({'res_id': resource_id})
> log_id = log_pool.create(cr, uid, vals)
> lines = []

yup, didn't think of this hack !

Regards,
Naresh

Revision history for this message
qdp (OpenERP) (qdp) wrote :

hi again Naresh,

before proceeding, i still have few remarks:
1) in the docstring of get_value_text() please add the meaning of the params and returned value
2) please document the function start_log_process()
3) the code of inline_process_old_data() and inline_process_new_data() seems really similar. I wonder if we couldn't factorize that code. Morover, having those functions inside another function make the code a bit unreadable.
4) a general effort of documenting/commenting is needed because the code is really difficult to understand.

Next time will be the good one, i'm pretty sure of it! (otherwise, the bug is totally fixed by now and the module seems to run perfectly!)

Regards,
Quentin

review: Needs Fixing
Revision history for this message
Naresh(OpenERP) (nch-openerp) wrote :

Hello Quentin,

Thanks for reviewing ! I had made the changes as required and also I have rewritten the method to process and log for the actions and workflow. So you will be required to perform again a test before merge. Fix few bugs too like useless logs were created for X2M models when a 'read' operation was performed.

hope now it want come back....Thanks again...

Regards,

Revision history for this message
qdp (OpenERP) (qdp) wrote :

Hi Naresh,

i finished the refactoring myself. Explain what should have been done would have taken so much time and iteration...

you can check: it's merged in revision 5754.

Thanks

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'audittrail/audittrail.py'
2--- audittrail/audittrail.py 2011-10-16 01:28:00 +0000
3+++ audittrail/audittrail.py 2011-11-21 16:41:25 +0000
4@@ -43,22 +43,17 @@
5 "log_create": fields.boolean("Log Creates",help="Select this if you want to keep track of creation on any record of the object of this rule"),
6 "log_action": fields.boolean("Log Action",help="Select this if you want to keep track of actions on the object of this rule"),
7 "log_workflow": fields.boolean("Log Workflow",help="Select this if you want to keep track of workflow on any record of the object of this rule"),
8- "state": fields.selection((("draft", "Draft"),
9- ("subscribed", "Subscribed")),
10- "State", required=True),
11+ "state": fields.selection((("draft", "Draft"), ("subscribed", "Subscribed")), "State", required=True),
12 "action_id": fields.many2one('ir.actions.act_window', "Action ID"),
13-
14 }
15-
16 _defaults = {
17- 'state': lambda *a: 'draft',
18- 'log_create': lambda *a: 1,
19- 'log_unlink': lambda *a: 1,
20- 'log_write': lambda *a: 1,
21+ 'state': 'draft',
22+ 'log_create': 1,
23+ 'log_unlink': 1,
24+ 'log_write': 1,
25 }
26-
27 _sql_constraints = [
28- ('model_uniq', 'unique (object_id)', """There is a rule defined on this object\n You cannot define another one the same object!""")
29+ ('model_uniq', 'unique (object_id)', """There is already a rule defined on this object\n You cannot define another: please edit the existing one.""")
30 ]
31 __functions = {}
32
33@@ -178,54 +173,33 @@
34 class audittrail_objects_proxy(object_proxy):
35 """ Uses Object proxy for auditing changes on object of subscribed Rules"""
36
37- def get_value_text(self, cr, uid, field_name, values, model, context=None):
38- """
39- Gets textual values for the fields
40- e.g.: For field of type many2one it gives its name value instead of id
41+ def get_value_text(self, cr, uid, pool, resource_pool, method, field, value):
42+ """
43+ Gets textual values for the fields.
44+ If the field is a many2one, it returns the name.
45+ If it's a one2many or a many2many, it returns a list of name.
46+ In other cases, it just returns the value.
47+ :param cr: the current row, from the database cursor,
48+ :param uid: the current user’s ID for security checks,
49+ :param pool: current db's pooler object.
50+ :param resource_pool: pooler object of the model which values are being changed.
51+ :param field: for which the text value is to be returned.
52+ :param value: value of the field.
53+ :param recursive: True or False, True will repeat the process recursively
54+ :return: string value or a list of values(for O2M/M2M)
55+ """
56
57- @param cr: the current row, from the database cursor,
58- @param uid: the current user’s ID for security checks,
59- @param field_name: List of fields for text values
60- @param values: Values for field to be converted into textual values
61- @return: values: List of textual values for given fields
62- """
63- if not context:
64- context = {}
65- if field_name in('__last_update','id'):
66- return values
67- pool = pooler.get_pool(cr.dbname)
68- field_pool = pool.get('ir.model.fields')
69- model_pool = pool.get('ir.model')
70- obj_pool = pool.get(model.model)
71- if obj_pool._inherits:
72- inherits_ids = model_pool.search(cr, uid, [('model', '=', obj_pool._inherits.keys()[0])])
73- field_ids = field_pool.search(cr, uid, [('name', '=', field_name), ('model_id', 'in', (model.id, inherits_ids[0]))])
74+ field_obj = (resource_pool._all_columns.get(field)).column
75+ if field_obj._type in ('one2many','many2many'):
76+ data = pool.get(field_obj._obj).name_get(cr, uid, value)
77+ #return the modifications on x2many fields as a list of names
78+ res = map(lambda x:x[1], data)
79+ elif field_obj._type == 'many2one':
80+ #return the modifications on a many2one field as its value returned by name_get()
81+ res = value and value[1] or value
82 else:
83- field_ids = field_pool.search(cr, uid, [('name', '=', field_name), ('model_id', '=', model.id)])
84- field_id = field_ids and field_ids[0] or False
85- assert field_id, _("'%s' field does not exist in '%s' model" %(field_name, model.model))
86-
87- field = field_pool.read(cr, uid, field_id)
88- relation_model = field['relation']
89- relation_model_pool = relation_model and pool.get(relation_model) or False
90-
91- if field['ttype'] == 'many2one':
92- res = False
93- relation_id = False
94- if values and type(values) == tuple:
95- relation_id = values[0]
96- if relation_id and relation_model_pool:
97- relation_model_object = relation_model_pool.read(cr, uid, relation_id, [relation_model_pool._rec_name])
98- res = relation_model_object[relation_model_pool._rec_name]
99- return res
100-
101- elif field['ttype'] in ('many2many','one2many'):
102- res = []
103- for relation_model_object in relation_model_pool.read(cr, uid, values, [relation_model_pool._rec_name]):
104- res.append(relation_model_object[relation_model_pool._rec_name])
105- return res
106-
107- return values
108+ res = value
109+ return res
110
111 def create_log_line(self, cr, uid, log_id, model, lines=[]):
112 """
113@@ -233,7 +207,7 @@
114
115 @param cr: the current row, from the database cursor,
116 @param uid: the current user’s ID for security checks,
117- @param model: Object who's values are being changed
118+ @param model: Object which values are being changed
119 @param lines: List of values for line is to be created
120 """
121 pool = pooler.get_pool(cr.dbname)
122@@ -241,216 +215,273 @@
123 model_pool = pool.get('ir.model')
124 field_pool = pool.get('ir.model.fields')
125 log_line_pool = pool.get('audittrail.log.line')
126- #start Loop
127 for line in lines:
128- if line['name'] in('__last_update','id'):
129- continue
130+ field_obj = obj_pool._all_columns.get(line['name'])
131+ assert field_obj, _("'%s' field does not exist in '%s' model" %(line['name'], model.model))
132+ field_obj = field_obj.column
133+ old_value = line.get('old_value', '')
134+ new_value = line.get('new_value', '')
135+ search_models = [model.id]
136 if obj_pool._inherits:
137- inherits_ids = model_pool.search(cr, uid, [('model', '=', obj_pool._inherits.keys()[0])])
138- field_ids = field_pool.search(cr, uid, [('name', '=', line['name']), ('model_id', 'in', (model.id, inherits_ids[0]))])
139- else:
140- field_ids = field_pool.search(cr, uid, [('name', '=', line['name']), ('model_id', '=', model.id)])
141- field_id = field_ids and field_ids[0] or False
142- assert field_id, _("'%s' field does not exist in '%s' model" %(line['name'], model.model))
143-
144- field = field_pool.read(cr, uid, field_id)
145- old_value = 'old_value' in line and line['old_value'] or ''
146- new_value = 'new_value' in line and line['new_value'] or ''
147- old_value_text = 'old_value_text' in line and line['old_value_text'] or ''
148- new_value_text = 'new_value_text' in line and line['new_value_text'] or ''
149-
150- if old_value_text == new_value_text:
151- continue
152- if field['ttype'] == 'many2one':
153- if type(old_value) == tuple:
154- old_value = old_value[0]
155- if type(new_value) == tuple:
156- new_value = new_value[0]
157+ search_models += model_pool.search(cr, uid, [('model', 'in', obj_pool._inherits.keys())])
158+ field_id = field_pool.search(cr, uid, [('name', '=', line['name']), ('model_id', 'in', search_models)])
159+ if field_obj._type == 'many2one':
160+ old_value = old_value and old_value[0] or old_value
161+ new_value = new_value and new_value[0] or new_value
162 vals = {
163 "log_id": log_id,
164- "field_id": field_id,
165+ "field_id": field_id and field_id[0] or False,
166 "old_value": old_value,
167 "new_value": new_value,
168- "old_value_text": old_value_text,
169- "new_value_text": new_value_text,
170- "field_description": field['field_description']
171+ "old_value_text": line.get('old_value_text', ''),
172+ "new_value_text": line.get('new_value_text', ''),
173+ "field_description": field_obj.string
174 }
175 line_id = log_line_pool.create(cr, uid, vals)
176- cr.commit()
177- #End Loop
178 return True
179
180- def log_fct(self, cr, uid, model, method, fct_src, *args):
181+ def log_fct(self, cr, uid_orig, model, method, fct_src, *args):
182 """
183- Logging function: This function is performs logging oprations according to method
184- @param db: the current database
185- @param uid: the current user’s ID for security checks,
186- @param object: Object who's values are being changed
187- @param method: method to log: create, read, write, unlink
188+ Logging function: This function is performing the logging operation
189+ @param model: Object whose values are being changed
190+ @param method: method to log: create, read, write, unlink, action or workflow action
191 @param fct_src: execute method of Object proxy
192
193 @return: Returns result as per method of Object proxy
194 """
195- uid_orig = uid
196- uid = 1
197- res2 = args
198 pool = pooler.get_pool(cr.dbname)
199 resource_pool = pool.get(model)
200- log_pool = pool.get('audittrail.log')
201 model_pool = pool.get('ir.model')
202-
203- model_ids = model_pool.search(cr, uid, [('model', '=', model)])
204+ model_ids = model_pool.search(cr, 1, [('model', '=', model)])
205 model_id = model_ids and model_ids[0] or False
206 assert model_id, _("'%s' Model does not exist..." %(model))
207- model = model_pool.browse(cr, uid, model_id)
208-
209- if method in ('create'):
210- res_id = fct_src(cr, uid_orig, model.model, method, *args)
211- resource = resource_pool.read(cr, uid, res_id, args[0].keys())
212- vals = {
213- "method": method,
214- "object_id": model.id,
215- "user_id": uid_orig,
216- "res_id": resource['id'],
217- }
218- if 'id' in resource:
219- del resource['id']
220- log_id = log_pool.create(cr, uid, vals)
221- lines = []
222- for field in resource:
223- line = {
224- 'name': field,
225- 'new_value': resource[field],
226- 'new_value_text': self.get_value_text(cr, uid, field, resource[field], model)
227- }
228- lines.append(line)
229- self.create_log_line(cr, uid, log_id, model, lines)
230-
231- return res_id
232-
233- elif method in ('read'):
234- res_ids = args[0]
235- old_values = {}
236- res = fct_src(cr, uid_orig, model.model, method, *args)
237- if type(res) == list:
238- for v in res:
239- old_values[v['id']] = v
240- else:
241- old_values[res['id']] = res
242- for res_id in old_values:
243- vals = {
244- "method": method,
245- "object_id": model.id,
246- "user_id": uid_orig,
247- "res_id": res_id,
248-
249- }
250- log_id = log_pool.create(cr, uid, vals)
251- lines = []
252- for field in old_values[res_id]:
253- line = {
254- 'name': field,
255- 'old_value': old_values[res_id][field],
256- 'old_value_text': self.get_value_text(cr, uid, field, old_values[res_id][field], model)
257- }
258- lines.append(line)
259-
260- self.create_log_line(cr, uid, log_id, model, lines)
261- return res
262-
263- elif method in ('unlink'):
264- res_ids = args[0]
265- old_values = {}
266- for res_id in res_ids:
267- old_values[res_id] = resource_pool.read(cr, uid, res_id)
268-
269- for res_id in res_ids:
270- vals = {
271- "method": method,
272- "object_id": model.id,
273- "user_id": uid_orig,
274- "res_id": res_id,
275-
276- }
277- log_id = log_pool.create(cr, uid, vals)
278- lines = []
279- for field in old_values[res_id]:
280- if field in ('id'):
281- continue
282- line = {
283- 'name': field,
284- 'old_value': old_values[res_id][field],
285- 'old_value_text': self.get_value_text(cr, uid, field, old_values[res_id][field], model)
286- }
287- lines.append(line)
288-
289- self.create_log_line(cr, uid, log_id, model, lines)
290- res = fct_src(cr, uid_orig, model.model, method, *args)
291- return res
292- else:
293- res_ids = []
294- res = True
295+ model = model_pool.browse(cr, 1, model_id)
296+
297+ # fields to log. currently only used by log on read()
298+ field_list = []
299+ old_values = new_values = {}
300+
301+ if method == 'create':
302+ res = fct_src(cr, uid_orig, model.model, method, *args)
303+ if res:
304+ res_ids = [res]
305+ new_values = self.get_data(cr, uid_orig, pool, res_ids, model, method)
306+ elif method == 'read':
307+ res = fct_src(cr, uid_orig, model.model, method, *args)
308+ # build the res_ids and the old_values dict. Here we don't use get_data() to
309+ # avoid performing an additional read()
310+ res_ids = []
311+ for record in res:
312+ res_ids.append(record['id'])
313+ old_values[(model.id, record['id'])] = {'value': record, 'text': record}
314+ # log only the fields read
315+ field_list = args[1]
316+ elif method == 'unlink':
317+ res_ids = args[0]
318+ old_values = self.get_data(cr, uid_orig, pool, res_ids, model, method)
319+ res = fct_src(cr, uid_orig, model.model, method, *args)
320+ else: # method is write, action or workflow action
321+ res_ids = []
322 if args:
323 res_ids = args[0]
324- old_values = {}
325- fields = []
326- if len(args)>1 and type(args[1]) == dict:
327- fields = args[1].keys()
328- if type(res_ids) in (long, int):
329+ if isinstance(res_ids, (long, int)):
330 res_ids = [res_ids]
331 if res_ids:
332- for resource in resource_pool.read(cr, uid, res_ids):
333- resource_id = resource['id']
334- if 'id' in resource:
335- del resource['id']
336- old_values_text = {}
337- old_value = {}
338- for field in resource.keys():
339- old_value[field] = resource[field]
340- old_values_text[field] = self.get_value_text(cr, uid, field, resource[field], model)
341- old_values[resource_id] = {'text':old_values_text, 'value': old_value}
342-
343+ # store the old values into a dictionary
344+ old_values = self.get_data(cr, uid_orig, pool, res_ids, model, method)
345+ # process the original function, workflow trigger...
346 res = fct_src(cr, uid_orig, model.model, method, *args)
347-
348+ if method == 'copy':
349+ res_ids = [res]
350 if res_ids:
351- for resource in resource_pool.read(cr, uid, res_ids):
352- resource_id = resource['id']
353- if 'id' in resource:
354- del resource['id']
355- vals = {
356- "method": method,
357- "object_id": model.id,
358- "user_id": uid_orig,
359- "res_id": resource_id,
360- }
361-
362-
363- log_id = log_pool.create(cr, uid, vals)
364- lines = []
365- for field in resource.keys():
366- line = {
367- 'name': field,
368- 'new_value': resource[field],
369- 'old_value': old_values[resource_id]['value'][field],
370- 'new_value_text': self.get_value_text(cr, uid, field, resource[field], model),
371- 'old_value_text': old_values[resource_id]['text'][field]
372- }
373- lines.append(line)
374-
375- self.create_log_line(cr, uid, log_id, model, lines)
376- return res
377+ # check the new values and store them into a dictionary
378+ new_values = self.get_data(cr, uid_orig, pool, res_ids, model, method)
379+ # compare the old and new values and create audittrail log if needed
380+ self.process_data(cr, uid_orig, pool, res_ids, model, method, old_values, new_values, field_list)
381+ return res
382+
383+ def get_data(self, cr, uid, pool, res_ids, model, method):
384+ """
385+ This function simply read all the fields of the given res_ids, and also recurisvely on
386+ all records of a x2m fields read that need to be logged. Then it returns the result in
387+ convenient structure that will be used as comparison basis.
388+
389+ :param cr: the current row, from the database cursor,
390+ :param uid: the current user’s ID. This parameter is currently not used as every
391+ operation to get data is made as super admin. Though, it could be usefull later.
392+ :param pool: current db's pooler object.
393+ :param res_ids: Id's of resource to be logged/compared.
394+ :param model: Object whose values are being changed
395+ :param method: method to log: create, read, unlink, write, actions, workflow actions
396+ :return: dict mapping a tuple (model_id, resource_id) with its value and textual value
397+ { (model_id, resource_id): { 'value': ...
398+ 'textual_value': ...
399+ },
400+ }
401+ """
402+ data = {}
403+ resource_pool = pool.get(model.model)
404+ # read all the fields of the given resources in super admin mode
405+ for resource in resource_pool.read(cr, 1, res_ids):
406+ values = {}
407+ values_text = {}
408+ resource_id = resource['id']
409+ # loop on each field on the res_ids we just have read
410+ for field in resource:
411+ if field in ('__last_update', 'id'):
412+ continue
413+ values[field] = resource[field]
414+ # get the textual value of that field for this record
415+ values_text[field] = self.get_value_text(cr, 1, pool, resource_pool, method, field, resource[field])
416+
417+ field_obj = resource_pool._all_columns.get(field).column
418+ if field_obj._type in ('one2many','many2many'):
419+ # check if an audittrail rule apply in super admin mode
420+ if self.check_rules(cr, 1, field_obj._obj, method):
421+ # check if the model associated to a *2m field exists, in super admin mode
422+ x2m_model_ids = pool.get('ir.model').search(cr, 1, [('model', '=', field_obj._obj)])
423+ x2m_model_id = x2m_model_ids and x2m_model_ids[0] or False
424+ assert x2m_model_id, _("'%s' Model does not exist..." %(field_obj._obj))
425+ x2m_model = pool.get('ir.model').browse(cr, 1, x2m_model_id)
426+ #recursive call on x2m fields that need to be checked too
427+ data.update(self.get_data(cr, 1, pool, resource[field], x2m_model, method))
428+ data[(model.id, resource_id)] = {'text':values_text, 'value': values}
429+ return data
430+
431+ def prepare_audittrail_log_line(self, cr, uid, pool, model, resource_id, method, old_values, new_values, field_list=[]):
432+ """
433+ This function compares the old data (i.e before the method was executed) and the new data
434+ (after the method was executed) and returns a structure with all the needed information to
435+ log those differences.
436+
437+ :param cr: the current row, from the database cursor,
438+ :param uid: the current user’s ID. This parameter is currently not used as every
439+ operation to get data is made as super admin. Though, it could be usefull later.
440+ :param pool: current db's pooler object.
441+ :param model: model object which values are being changed
442+ :param resource_id: ID of record to which values are being changed
443+ :param method: method to log: create, read, unlink, write, actions, workflow actions
444+ :param old_values: dict of values read before execution of the method
445+ :param new_values: dict of values read after execution of the method
446+ :param field_list: optional argument containing the list of fields to log. Currently only
447+ used when performing a read, it could be usefull later on if we want to log the write
448+ on specific fields only.
449+
450+ :return: dictionary with
451+ * keys: tuples build as ID of model object to log and ID of resource to log
452+ * values: list of all the changes in field values for this couple (model, resource)
453+ return {
454+ (model.id, resource_id): []
455+ }
456+
457+ The reason why the structure returned is build as above is because when modifying an existing
458+ record (res.partner, for example), we may have to log a change done in a x2many field (on
459+ res.partner.address, for example)
460+ """
461+ key = (model.id, resource_id)
462+ lines = {
463+ key: []
464+ }
465+ # loop on all the fields
466+ for field_name, field_definition in pool.get(model.model)._all_columns.items():
467+ #if the field_list param is given, skip all the fields not in that list
468+ if field_list and field_name not in field_list:
469+ continue
470+ field_obj = field_definition.column
471+ if field_obj._type in ('one2many','many2many'):
472+ # checking if an audittrail rule apply in super admin mode
473+ if self.check_rules(cr, 1, field_obj._obj, method):
474+ # checking if the model associated to a *2m field exists, in super admin mode
475+ x2m_model_ids = pool.get('ir.model').search(cr, 1, [('model', '=', field_obj._obj)])
476+ x2m_model_id = x2m_model_ids and x2m_model_ids[0] or False
477+ assert x2m_model_id, _("'%s' Model does not exist..." %(field_obj._obj))
478+ x2m_model = pool.get('ir.model').browse(cr, 1, x2m_model_id)
479+ # the resource_ids that need to be checked are the sum of both old and previous values (because we
480+ # need to log also creation or deletion in those lists).
481+ x2m_old_values_ids = old_values.get(key, {'value': {}})['value'].get(field_name, [])
482+ x2m_new_values_ids = new_values.get(key, {'value': {}})['value'].get(field_name, [])
483+ # We use list(set(...)) to remove duplicates.
484+ res_ids = list(set(x2m_old_values_ids + x2m_new_values_ids))
485+ for res_id in res_ids:
486+ lines.update(self.prepare_audittrail_log_line(cr, 1, pool, x2m_model, res_id, method, old_values, new_values, field_list))
487+ # if the value value is different than the old value: record the change
488+ 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]:
489+ data = {
490+ 'name': field_name,
491+ 'new_value': key in new_values and new_values[key]['value'].get(field_name),
492+ 'old_value': key in old_values and old_values[key]['value'].get(field_name),
493+ 'new_value_text': key in new_values and new_values[key]['text'].get(field_name),
494+ 'old_value_text': key in old_values and old_values[key]['text'].get(field_name)
495+ }
496+ lines[key].append(data)
497+ return lines
498+
499+ def process_data(self, cr, uid, pool, res_ids, model, method, old_values={}, new_values={}, field_list=[]):
500+ """
501+ This function processes and iterates recursively to log the difference between the old
502+ data (i.e before the method was executed) and the new data and creates audittrail log
503+ accordingly.
504+
505+ :param cr: the current row, from the database cursor,
506+ :param uid: the current user’s ID,
507+ :param pool: current db's pooler object.
508+ :param res_ids: Id's of resource to be logged/compared.
509+ :param model: model object which values are being changed
510+ :param method: method to log: create, read, unlink, write, actions, workflow actions
511+ :param old_values: dict of values read before execution of the method
512+ :param new_values: dict of values read after execution of the method
513+ :param field_list: optional argument containing the list of fields to log. Currently only
514+ used when performing a read, it could be usefull later on if we want to log the write
515+ on specific fields only.
516+ :return: True
517+ """
518+ # loop on all the given ids
519+ for res_id in res_ids:
520+ # compare old and new values and get audittrail log lines accordingly
521+ lines = self.prepare_audittrail_log_line(cr, uid, pool, model, res_id, method, old_values, new_values, field_list)
522+
523+ # if at least one modification has been found
524+ for model_id, resource_id in lines:
525+ vals = {
526+ 'method': method,
527+ 'object_id': model_id,
528+ 'user_id': uid,
529+ 'res_id': resource_id,
530+ }
531+ if (model_id, resource_id) not in old_values and method not in ('copy', 'read'):
532+ # the resource was not existing so we are forcing the method to 'create'
533+ # (because it could also come with the value 'write' if we are creating
534+ # new record through a one2many field)
535+ vals.update({'method': 'create'})
536+ if (model_id, resource_id) not in new_values and method not in ('copy', 'read'):
537+ # the resource is not existing anymore so we are forcing the method to 'unlink'
538+ # (because it could also come with the value 'write' if we are deleting the
539+ # record through a one2many field)
540+ vals.update({'method': 'unlink'})
541+ # create the audittrail log in super admin mode, only if a change has been detected
542+ if lines[(model_id, resource_id)]:
543+ log_id = pool.get('audittrail.log').create(cr, 1, vals)
544+ model = pool.get('ir.model').browse(cr, uid, model_id)
545+ self.create_log_line(cr, 1, log_id, model, lines[(model_id, resource_id)])
546 return True
547
548 def check_rules(self, cr, uid, model, method):
549+ """
550+ Checks if auditrails is installed for that db and then if one rule match
551+ @param cr: the current row, from the database cursor,
552+ @param uid: the current user’s ID,
553+ @param model: value of _name of the object which values are being changed
554+ @param method: method to log: create, read, unlink,write,actions,workflow actions
555+ @return: True or False
556+ """
557 pool = pooler.get_pool(cr.dbname)
558- # Check if auditrails is installed for that db and then if one rule match
559 if 'audittrail.rule' in pool.models:
560 model_ids = pool.get('ir.model').search(cr, 1, [('model', '=', model)])
561 model_id = model_ids and model_ids[0] or False
562 if model_id:
563 rule_ids = pool.get('audittrail.rule').search(cr, 1, [('object_id', '=', model_id), ('state', '=', 'subscribed')])
564 for rule in pool.get('audittrail.rule').read(cr, 1, rule_ids, ['user_id','log_read','log_write','log_create','log_unlink','log_action','log_workflow']):
565- if len(rule['user_id'])==0 or uid in rule['user_id']:
566+ if len(rule['user_id']) == 0 or uid in rule['user_id']:
567 if rule.get('log_'+method,0):
568 return True
569 elif method not in ('default_get','read','fields_view_get','fields_get','search','search_count','name_search','name_get','get','request_get', 'get_sc', 'unlink', 'write', 'create'):
570@@ -460,18 +491,14 @@
571 def execute_cr(self, cr, uid, model, method, *args, **kw):
572 fct_src = super(audittrail_objects_proxy, self).execute_cr
573 if self.check_rules(cr,uid,model,method):
574- res = self.log_fct(cr, uid, model, method, fct_src, *args)
575- else:
576- res = fct_src(cr, uid, model, method, *args)
577- return res
578+ return self.log_fct(cr, uid, model, method, fct_src, *args)
579+ return fct_src(cr, uid, model, method, *args)
580
581 def exec_workflow_cr(self, cr, uid, model, method, *args, **argv):
582 fct_src = super(audittrail_objects_proxy, self).exec_workflow_cr
583 if self.check_rules(cr,uid,model,'workflow'):
584- res = self.log_fct(cr, uid, model, method, fct_src, *args)
585- else:
586- res = fct_src(cr, uid, model, method, *args)
587- return res
588+ return self.log_fct(cr, uid, model, method, fct_src, *args)
589+ return fct_src(cr, uid, model, method, *args)
590
591 audittrail_objects_proxy()
592

Subscribers

People subscribed via source and target branches

to all changes: