[MERGE] merge with main addons
[odoo/odoo.git] / addons / audittrail / audittrail.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 from osv import fields, osv
23 from osv.osv import osv_pool
24 from tools.translate import _
25 import ir
26 import pooler
27 import time
28 import tools
29
30 class audittrail_rule(osv.osv):
31     """
32     For Auddittrail Rule
33     """
34     _name = 'audittrail.rule'
35     _description = "Audittrail Rule"
36     _columns = {
37         "name": fields.char("Rule Name", size=32, required=True),
38         "object_id": fields.many2one('ir.model', 'Object', required=True),
39         "user_id": fields.many2many('res.users', 'audittail_rules_users',
40                                             'user_id', 'rule_id', 'Users', help="if  User is not added then it will applicable for all users"),
41         "log_read": fields.boolean("Log Reads"),
42         "log_write": fields.boolean("Log Writes"),
43         "log_unlink": fields.boolean("Log Deletes"),
44         "log_create": fields.boolean("Log Creates"),
45         "log_action": fields.boolean("Log Action"),
46         "log_workflow": fields.boolean("Log Workflow"),
47         "state": fields.selection((("draft", "Draft"),
48                                    ("subscribed", "Subscribed")),
49                                    "State", required=True),
50         "action_id": fields.many2one('ir.actions.act_window', "Action ID"),
51
52     }
53
54     _defaults = {
55         'state': lambda *a: 'draft',
56         'log_create': lambda *a: 1,
57         'log_unlink': lambda *a: 1,
58         'log_write': lambda *a: 1,
59     }
60
61     _sql_constraints = [
62         ('model_uniq', 'unique (object_id)', """There is a rule defined on this object\n You can not define other on the same!""")
63     ]
64     __functions = {}
65
66     def subscribe(self, cr, uid, ids, *args):
67         """
68         Subscribe Rule for auditing changes on object and apply shortcut for logs on that object.
69         @param cr: the current row, from the database cursor,
70         @param uid: the current user’s ID for security checks,
71         @param ids: List of Auddittrail Rule’s IDs.
72         @return: True
73         """
74         obj_action = self.pool.get('ir.actions.act_window')
75         obj_model = self.pool.get('ir.model.data')
76         #start Loop
77         for thisrule in self.browse(cr, uid, ids):
78             obj = self.pool.get(thisrule.object_id.model)
79             if not obj:
80                 raise osv.except_osv(
81                         _('WARNING: audittrail is not part of the pool'),
82                         _('Change audittrail depends -- Setting rule as DRAFT'))
83                 self.write(cr, uid, [thisrule.id], {"state": "draft"})
84             val = {
85                  "name": 'View Log',
86                  "res_model": 'audittrail.log',
87                  "src_model": thisrule.object_id.model,
88                  "domain": "[('object_id','=', " + str(thisrule.object_id.id) + "), ('res_id', '=', active_id)]"
89
90             }
91             action_id = obj_action.create(cr, uid, val)
92             self.write(cr, uid, [thisrule.id], {"state": "subscribed", "action_id": action_id})
93             keyword = 'client_action_relate'
94             value = 'ir.actions.act_window,' + str(action_id)
95             res = obj_model.ir_set(cr, uid, 'action', keyword, 'View_log_' + thisrule.object_id.model, [thisrule.object_id.model], value, replace=True, isobject=True, xml_id=False)
96             #End Loop
97         return True
98
99     def unsubscribe(self, cr, uid, ids, *args):
100         """
101         Unsubscribe Auditing Rule on object
102         @param cr: the current row, from the database cursor,
103         @param uid: the current user’s ID for security checks,
104         @param ids: List of Auddittrail Rule’s IDs.
105         @return: True
106         """
107         obj_action = self.pool.get('ir.actions.act_window')
108         val_obj = self.pool.get('ir.values')
109         #start Loop
110         for thisrule in self.browse(cr, uid, ids):
111             if thisrule.id in self.__functions:
112                 for function in self.__functions[thisrule.id]:
113                     setattr(function[0], function[1], function[2])
114             w_id = obj_action.search(cr, uid, [('name', '=', 'View Log'), ('res_model', '=', 'audittrail.log'), ('src_model', '=', thisrule.object_id.model)])
115             obj_action.unlink(cr, uid, w_id)
116             value = "ir.actions.act_window" + ',' + str(w_id[0])
117             val_id = val_obj.search(cr, uid, [('model', '=', thisrule.object_id.model), ('value', '=', value)])
118             if val_id:
119                 res = ir.ir_del(cr, uid, val_id[0])
120             self.write(cr, uid, [thisrule.id], {"state": "draft"})
121         #End Loop
122         return True
123
124 audittrail_rule()
125
126
127 class audittrail_log(osv.osv):
128     """
129     For Audittrail Log
130     """
131     _name = 'audittrail.log'
132     _description = "Audittrail Log"
133
134     def _name_get_resname(self, cr, uid, ids, *args):
135         data = {}
136         for resname in self.browse(cr, uid, ids,[]):
137             model_object = resname.object_id
138             res_id = resname.res_id
139             if model_object and res_id:
140                 model_pool = self.pool.get(model_object.model)
141                 res = model_pool.read(cr, uid, res_id, ['name'])
142                 data[resname.id] = res['name']
143             else:
144                  data[resname.id] = False
145         return data
146
147     _columns = {
148         "name": fields.char("Resource Name",size=64),
149         "object_id": fields.many2one('ir.model', 'Object'),
150         "user_id": fields.many2one('res.users', 'User'),
151         "method": fields.char("Method", size=64),
152         "timestamp": fields.datetime("Date"),
153         "res_id": fields.integer('Resource Id'),
154         "line_ids": fields.one2many('audittrail.log.line', 'log_id', 'Log lines'),
155     }
156
157     _defaults = {
158         "timestamp": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S")
159     }
160     _order = "timestamp desc"
161
162 audittrail_log()
163
164
165 class audittrail_log_line(osv.osv):
166     """
167     Audittrail Log Line.
168     """
169     _name = 'audittrail.log.line'
170     _description = "Log Line"
171     _columns = {
172           'field_id': fields.many2one('ir.model.fields', 'Fields', required=True),
173           'log_id': fields.many2one('audittrail.log', 'Log'),
174           'log': fields.integer("Log ID"),
175           'old_value': fields.text("Old Value"),
176           'new_value': fields.text("New Value"),
177           'old_value_text': fields.text('Old value Text'),
178           'new_value_text': fields.text('New value Text'),
179           'field_description': fields.char('Field Description', size=64),
180         }
181
182 audittrail_log_line()
183
184
185 class audittrail_objects_proxy(osv_pool):
186     """ Uses Object proxy for auditing changes on object of subscribed Rules"""
187
188     def get_value_text(self, cr, uid, field_name, values, model, context=None):
189         """
190         Gets textual values for the fields
191         e.g.: For field of type many2one it gives its name value instead of id
192
193         @param cr: the current row, from the database cursor,
194         @param uid: the current user’s ID for security checks,
195         @param field_name: List of fields for text values
196         @param values: Values for field to be converted into textual values
197         @return: values: List of textual values for given fields
198         """
199         if not context:
200             context = {}
201         pool = pooler.get_pool(cr.dbname)
202         field_pool = pool.get('ir.model.fields')
203         model_pool = pool.get('ir.model')
204         obj_pool = pool.get(model.model)
205         if obj_pool._inherits:
206             inherits_ids = model_pool.search(cr, uid, [('model', '=', obj_pool._inherits.keys()[0])])
207             field_ids = field_pool.search(cr, uid, [('name', '=', field_name), ('model_id', 'in', (model.id, inherits_ids[0]))])
208         else:
209             field_ids = field_pool.search(cr, uid, [('name', '=', field_name), ('model_id', '=', model.id)])
210         field_id = field_ids and field_ids[0] or False
211         assert field_id, _("'%s' field is not exits in '%s' model" %(field_name, model.model))
212
213         field = field_pool.read(cr, uid, field_id)
214         relation_model = field['relation']
215         relation_model_pool = relation_model and pool.get(relation_model) or False
216
217         if field['ttype'] == 'many2one':
218             res = False
219             relation_id = False
220             if values and type(values) == tuple:
221                 relation_id = values[0]
222                 if relation_id and relation_model_pool:
223                     relation_model_object = relation_model_pool.read(cr, uid, relation_id, [relation_model_pool._rec_name])
224                     res = relation_model_object[relation_model_pool._rec_name]
225             return res
226
227         elif field['ttype'] in ('many2many','one2many'):
228             res = []
229             for relation_model_object in relation_model_pool.read(cr, uid, values, [relation_model_pool._rec_name]):
230                 res.append(relation_model_object[relation_model_pool._rec_name])
231             return res
232
233         return values
234
235     def create_log_line(self, cr, uid, log_id, model, lines=[]):
236         """
237         Creates lines for changed fields with its old and new values
238
239         @param cr: the current row, from the database cursor,
240         @param uid: the current user’s ID for security checks,
241         @param model: Object who's values are being changed
242         @param lines: List of values for line is to be created
243         """
244         pool = pooler.get_pool(cr.dbname)
245         obj_pool = pool.get(model.model)
246         model_pool = pool.get('ir.model')
247         field_pool = pool.get('ir.model.fields')
248         log_line_pool = pool.get('audittrail.log.line')
249         #start Loop
250         for line in lines:
251             if obj_pool._inherits:
252                 inherits_ids = model_pool.search(cr, uid, [('model', '=', obj_pool._inherits.keys()[0])])
253                 field_ids = field_pool.search(cr, uid, [('name', '=', line['name']), ('model_id', 'in', (model.id, inherits_ids[0]))])
254             else:
255                 field_ids = field_pool.search(cr, uid, [('name', '=', line['name']), ('model_id', '=', model.id)])
256             field_id = field_ids and field_ids[0] or False
257             assert field_id, _("'%s' field is not exits in '%s' model" %(line['name'], model.model))
258
259             field = field_pool.read(cr, uid, field_id)
260             old_value = 'old_value' in line and  line['old_value'] or ''
261             new_value = 'new_value' in line and  line['new_value'] or ''
262             old_value_text = 'old_value_text' in line and  line['old_value_text'] or ''
263             new_value_text = 'new_value_text' in line and  line['new_value_text'] or ''
264
265             if old_value_text == new_value_text:
266                 continue
267             if field['ttype'] == 'many2one':
268                 if type(old_value) == tuple:
269                     old_value = old_value[0]
270                 if type(new_value) == tuple:
271                     new_value = new_value[0]
272             vals = {
273                     "log_id": log_id,
274                     "field_id": field_id,
275                     "old_value": old_value,
276                     "new_value": new_value,
277                     "old_value_text": old_value_text,
278                     "new_value_text": new_value_text,
279                     "field_description": field['field_description']
280                     }
281             line_id = log_line_pool.create(cr, uid, vals)
282         #End Loop
283         return True
284
285
286     def log_fct(self, db, uid, model, method, fct_src, *args):
287         """
288         Logging function: This function is performs logging oprations according to method
289         @param db: the current database
290         @param uid: the current user’s ID for security checks,
291         @param object: Object who's values are being changed
292         @param method: method to log: create, read, write, unlink
293         @param fct_src: execute method of Object proxy
294
295         @return: Returns result as per method of Object proxy
296         """
297         res2 = args
298         pool = pooler.get_pool(db)
299         cr = pooler.get_db(db).cursor()
300         resource_pool = pool.get(model)
301         log_pool = pool.get('audittrail.log')
302         model_pool = pool.get('ir.model')
303
304         model_ids = model_pool.search(cr, uid, [('model', '=', model)])
305         model_id = model_ids and model_ids[0] or False
306         assert model_id, _("'%s' Model is not exits..." %(model))
307         model = model_pool.browse(cr, uid, model_id)
308
309         if method in ('create'):
310             res_id = fct_src(db, uid, model.model, method, *args)
311             cr.commit()
312             resource = resource_pool.read(cr, uid, res_id, args[0].keys())
313             vals = {
314                     "method": method,
315                     "object_id": model.id,
316                     "user_id": uid,
317                     "res_id": resource['id'],
318             }
319             if 'id' in resource:
320                 del resource['id']
321             log_id = log_pool.create(cr, uid, vals)
322             lines = []
323             for field in resource:
324                 line = {
325                       'name': field,
326                       'new_value': resource[field],
327                       'new_value_text': self.get_value_text(cr, uid, field, resource[field], model)
328                       }
329                 lines.append(line)
330             self.create_log_line(cr, uid, log_id, model, lines)
331
332             cr.commit()
333             cr.close()
334             return res_id
335
336         elif method in ('read'):
337             res_ids = args[0]
338             old_values = {}
339             res = fct_src(db, uid, model.model, method, *args)
340             if type(res) == list:
341                 for v in res:
342                     old_values[v['id']] = v
343             else:
344                 old_values[res['id']] = res
345             for res_id in old_values:
346                 vals = {
347                     "method": method,
348                     "object_id": model.id,
349                     "user_id": uid,
350                     "res_id": res_id,
351
352                 }
353                 log_id = log_pool.create(cr, uid, vals)
354                 lines = []
355                 for field in old_values[res_id]:
356                     line = {
357                               'name': field,
358                               'old_value': old_values[res_id][field],
359                               'old_value_text': self.get_value_text(cr, uid, field, old_values[res_id][field], model)
360                               }
361                     lines.append(line)
362
363                 self.create_log_line(cr, uid, log_id, model, lines)
364
365             cr.close()
366             return res
367
368         elif method in ('unlink'):
369             res_ids = args[0]
370             old_values = {}
371             for res_id in res_ids:
372                 old_values[res_id] = resource_pool.read(cr, uid, res_id)
373
374             for res_id in res_ids:
375                 vals = {
376                     "method": method,
377                     "object_id": model.id,
378                     "user_id": uid,
379                     "res_id": res_id,
380
381                 }
382                 log_id = log_pool.create(cr, uid, vals)
383                 lines = []
384                 for field in old_values[res_id]:
385                     if field in ('id'):
386                         continue
387                     line = {
388                           'name': field,
389                           'old_value': old_values[res_id][field],
390                           'old_value_text': self.get_value_text(cr, uid, field, old_values[res_id][field], model)
391                           }
392                     lines.append(line)
393
394                 self.create_log_line(cr, uid, log_id, model, lines)
395             res = fct_src(db, uid, model.model, method, *args)
396             cr.commit()
397             cr.close()
398             return res
399         else:
400             res_ids = args[0]
401             old_values = {}
402             fields = []
403             if len(args)>1 and type(args[1]) == dict:
404                 fields = args[1].keys()
405             if type(res_ids) in (long, int):
406                 res_ids = [res_ids]
407             if res_ids:
408                 for resource in resource_pool.read(cr, uid, res_ids):
409                     resource_id = resource['id']
410                     if 'id' in resource:
411                         del resource['id']
412                     old_values_text = {}
413                     old_value = {}
414                     for field in resource.keys():
415                         old_value[field] = resource[field]
416                         old_values_text[field] = self.get_value_text(cr, uid, field, resource[field], model)
417                     old_values[resource_id] = {'text':old_values_text, 'value': old_value}
418
419             res = fct_src(db, uid, model.model, method, *args)
420             cr.commit()
421
422             if res_ids:
423                 for resource in resource_pool.read(cr, uid, res_ids):
424                     resource_id = resource['id']
425                     if 'id' in resource:
426                         del resource['id']
427                     vals = {
428                         "method": method,
429                         "object_id": model.id,
430                         "user_id": uid,
431                         "res_id": resource_id,
432                     }
433
434
435                     log_id = log_pool.create(cr, uid, vals)
436                     lines = []
437                     for field in resource.keys():
438                         line = {
439                               'name': field,
440                               'new_value': resource[field],
441                               'old_value': old_values[resource_id]['value'][field],
442                               'new_value_text': self.get_value_text(cr, uid, field, resource[field], model),
443                               'old_value_text': old_values[resource_id]['text'][field]
444                               }
445                         lines.append(line)
446
447                     self.create_log_line(cr, uid, log_id, model, lines)
448                 cr.commit()
449             cr.close()
450             return res
451         return True
452
453
454
455     def execute(self, db, uid, model, method, *args, **kw):
456         """
457         Overrides Object Proxy execute method
458         @param db: the current database
459         @param uid: the current user’s ID for security checks,
460         @param object: Object who's values are being changed
461         @param method: get any method and create log
462
463         @return: Returns result as per method of Object proxy
464         """
465         pool = pooler.get_pool(db)
466         model_pool = pool.get('ir.model')
467         rule_pool = pool.get('audittrail.rule')
468         cr = pooler.get_db(db).cursor()
469         cr.autocommit(True)
470         logged_uids = []
471         fct_src = super(audittrail_objects_proxy, self).execute
472
473         def my_fct(db, uid, model, method, *args):
474             rule = False
475             model_ids = model_pool.search(cr, uid, [('model', '=', model)])
476             model_id = model_ids and model_ids[0] or False
477
478             for model_name in pool.obj_list():
479                 if model_name == 'audittrail.rule':
480                     rule = True
481             if not rule:
482                 return fct_src(db, uid, model, method, *args)
483             if not model_id:
484                 return fct_src(db, uid, model, method, *args)
485
486             rule_ids = rule_pool.search(cr, uid, [('object_id', '=', model_id), ('state', '=', 'subscribed')])
487             if not rule_ids:
488                 return fct_src(db, uid, model, method, *args)
489
490             for thisrule in rule_pool.browse(cr, uid, rule_ids):
491                 for user in thisrule.user_id:
492                     logged_uids.append(user.id)
493                 if not logged_uids or uid in logged_uids:
494                     if method in ('read', 'write', 'create', 'unlink'):
495                         if getattr(thisrule, 'log_' + method):
496                             return self.log_fct(db, uid, model, method, fct_src, *args)
497
498                     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'):
499                         if thisrule.log_action:
500                             return self.log_fct(db, uid, model, method, fct_src, *args)
501
502                 return fct_src(db, uid, model, method, *args)
503         res = my_fct(db, uid, model, method, *args)
504         cr.close()
505         return res
506
507
508     def exec_workflow(self, db, uid, model, method, *args, **argv):
509         pool = pooler.get_pool(db)
510         cr = pooler.get_db(db).cursor()
511         cr.autocommit(True)
512         logged_uids = []
513         fct_src = super(audittrail_objects_proxy, self).exec_workflow
514         field = method
515         rule = False
516         model_pool = pool.get('ir.model')
517         rule_pool = pool.get('audittrail.rule')
518         model_ids = model_pool.search(cr, uid, [('model', '=', model)])
519         for obj_name in pool.obj_list():
520             if obj_name == 'audittrail.rule':
521                 rule = True
522         if not rule:
523             cr.close()
524             return super(audittrail_objects_proxy, self).exec_workflow(db, uid, model, method, *args, **argv)
525         if not model_ids:
526             cr.close()
527             return super(audittrail_objects_proxy, self).exec_workflow(db, uid, model, method, *args, **argv)
528
529         rule_ids = rule_pool.search(cr, uid, [('object_id', 'in', model_ids), ('state', '=', 'subscribed')])
530         if not rule_ids:
531              cr.close()
532              return super(audittrail_objects_proxy, self).exec_workflow(db, uid, model, method, *args, **argv)
533
534         for thisrule in rule_pool.browse(cr, uid, rule_ids):
535             for user in thisrule.user_id:
536                 logged_uids.append(user.id)
537             if not logged_uids or uid in logged_uids:
538                  if thisrule.log_workflow:
539                      cr.close()
540                      return self.log_fct(db, uid, model, method, fct_src, *args)
541             cr.close()
542             return super(audittrail_objects_proxy, self).exec_workflow(db, uid, model, method, *args, **argv)
543
544         cr.close()
545         return True
546
547 audittrail_objects_proxy()
548
549 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
550