Merge lp:~openerp-dev/openobject-addons/trunk-bug-832635-nch into lp:openobject-addons
- trunk-bug-832635-nch
- Merge into trunk
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 |
Related bugs: |
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.
Commit message
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,
Antony Lesuisse (OpenERP) (al-openerp) wrote : Posted in a previous version of this proposal | # |
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_
audit_log_call
because the former use _cr, are shorter and much faster (no useless db hits).
Remove all cr.commit().
Naresh(OpenERP) (nch-openerp) wrote : Posted in a previous version of this proposal | # |
hello,
made compatible to new signatures...
Thanks,
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
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,
>
> 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
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.
by
self.
def create_log(self, cr, uid, model, lines=[]):
if not lines:
return True
vals = { ... }
vals['lines] = [(0, 0, { ... }), ...]
return self.pool.
> - 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/
+++ audittrail/
@@ -354,6 +357,8 @@
+ if resource_id not in dict_to_use.keys():
+ vals.update(
i set it back to 'work in progress' ;-)
Quentin
Naresh(OpenERP) (nch-openerp) wrote : | # |
> For that, you need to do a small change:
> => replace the function
> self.create_
> 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.
>
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/
> +++ audittrail/
> @@ -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(
> vals.update(
> log_id = log_pool.create(cr, uid, vals)
> lines = []
yup, didn't think of this hack !
Regards,
Naresh
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_
4) a general effort of documenting/
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
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,
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
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 |
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.