[IMP] improves the reporting view Expenses Analysis (add some measures to the table...
[odoo/odoo.git] / addons / anonymization / anonymization.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 from lxml import etree
24 import os
25 import base64
26 try:
27     import cPickle as pickle
28 except ImportError:
29     import pickle
30 import random
31 import datetime
32 from openerp.osv import fields, osv
33 from openerp.tools.translate import _
34
35 from itertools import groupby
36 from operator import itemgetter
37
38
39 FIELD_STATES = [('clear', 'Clear'), ('anonymized', 'Anonymized'), ('not_existing', 'Not Existing'), ('new', 'New')]
40 ANONYMIZATION_STATES = FIELD_STATES + [('unstable', 'Unstable')]
41 WIZARD_ANONYMIZATION_STATES = [('clear', 'Clear'), ('anonymized', 'Anonymized'), ('unstable', 'Unstable')]
42 ANONYMIZATION_HISTORY_STATE = [('started', 'Started'), ('done', 'Done'), ('in_exception', 'Exception occured')]
43 ANONYMIZATION_DIRECTION = [('clear -> anonymized', 'clear -> anonymized'), ('anonymized -> clear', 'anonymized -> clear')]
44
45
46 def group(lst, cols):
47     if isinstance(cols, basestring):
48         cols = [cols]
49     return dict((k, [v for v in itr]) for k, itr in groupby(sorted(lst, key=itemgetter(*cols)), itemgetter(*cols)))
50
51
52 class ir_model_fields_anonymization(osv.osv):
53     _name = 'ir.model.fields.anonymization'
54     _rec_name = 'field_id'
55
56     _columns = {
57         'model_name': fields.char('Object Name', size=128, required=True),
58         'model_id': fields.many2one('ir.model', 'Object', ondelete='set null'),
59         'field_name': fields.char('Field Name', size=128, required=True),
60         'field_id': fields.many2one('ir.model.fields', 'Field', ondelete='set null'),
61         'state': fields.selection(selection=FIELD_STATES, String='Status', required=True, readonly=True),
62     }
63
64     _sql_constraints = [
65         ('model_id_field_id_uniq', 'unique (model_name, field_name)', _("You cannot have two fields with the same name on the same object!")),
66     ]
67
68     def _get_global_state(self, cr, uid, context=None):
69         ids = self.search(cr, uid, [('state', '<>', 'not_existing')], context=context)
70         fields = self.browse(cr, uid, ids, context=context)
71         if not len(fields) or len(fields) == len([f for f in fields if f.state == 'clear']):
72             state = 'clear' # all fields are clear
73         elif len(fields) == len([f for f in fields if f.state == 'anonymized']):
74             state = 'anonymized' # all fields are anonymized
75         else:
76             state = 'unstable' # fields are mixed: this should be fixed
77
78         return state
79
80     def _check_write(self, cr, uid, context=None):
81         """check that the field is created from the menu and not from an database update
82            otherwise the database update can crash:"""
83         if context is None:
84             context = {}
85
86         if context.get('manual'):
87             global_state = self._get_global_state(cr, uid, context=context)
88             if global_state == 'anonymized':
89                 raise osv.except_osv('Error!', "The database is currently anonymized, you cannot create, modify or delete fields.")
90             elif global_state == 'unstable':
91                 msg = _("The database anonymization is currently in an unstable state. Some fields are anonymized," + \
92                       " while some fields are not anonymized. You should try to solve this problem before trying to create, write or delete fields.")
93                 raise osv.except_osv('Error!', msg)
94
95         return True
96
97     def _get_model_and_field_ids(self, cr, uid, vals, context=None):
98         model_and_field_ids = (False, False)
99
100         if 'field_name' in vals and vals['field_name'] and 'model_name' in vals and vals['model_name']:
101             ir_model_fields_obj = self.pool.get('ir.model.fields')
102             ir_model_obj = self.pool.get('ir.model')
103
104             model_ids = ir_model_obj.search(cr, uid, [('model', '=', vals['model_name'])], context=context)
105             if model_ids:
106                 field_ids = ir_model_fields_obj.search(cr, uid, [('name', '=', vals['field_name']), ('model_id', '=', model_ids[0])], context=context)
107                 if field_ids:
108                     field_id = field_ids[0]
109                     model_and_field_ids = (model_ids[0], field_id)
110
111         return model_and_field_ids
112
113     def create(self, cr, uid, vals, context=None):
114         # check field state: all should be clear before we can add a new field to anonymize:
115         self._check_write(cr, uid, context=context)
116
117         global_state = self._get_global_state(cr, uid, context=context)
118
119         if 'field_name' in vals and vals['field_name'] and 'model_name' in vals and vals['model_name']:
120             vals['model_id'], vals['field_id'] = self._get_model_and_field_ids(cr, uid, vals, context=context)
121
122         # check not existing fields:
123         if not vals.get('field_id'):
124             vals['state'] = 'not_existing'
125         else:
126             vals['state'] = global_state
127
128         res = super(ir_model_fields_anonymization, self).create(cr, uid, vals, context=context)
129
130         return res
131
132     def write(self, cr, uid, ids, vals, context=None):
133         # check field state: all should be clear before we can modify a field:
134         if not (len(vals.keys()) == 1 and vals.get('state') == 'clear'):
135             self._check_write(cr, uid, context=context)
136
137         if 'field_name' in vals and vals['field_name'] and 'model_name' in vals and vals['model_name']:
138             vals['model_id'], vals['field_id'] = self._get_model_and_field_ids(cr, uid, vals, context=context)
139
140         # check not existing fields:
141         if 'field_id' in vals:
142             if not vals.get('field_id'):
143                 vals['state'] = 'not_existing'
144             else:
145                 global_state = self._get_global_state(cr, uid, context)
146                 if global_state != 'unstable':
147                     vals['state'] = global_state
148
149         res = super(ir_model_fields_anonymization, self).write(cr, uid, ids, vals, context=context)
150
151         return res
152
153     def unlink(self, cr, uid, ids, context=None):
154         # check field state: all should be clear before we can unlink a field:
155         self._check_write(cr, uid, context=context)
156
157         res = super(ir_model_fields_anonymization, self).unlink(cr, uid, ids, context=context)
158         return res
159
160     def onchange_model_id(self, cr, uid, ids, model_id, context=None):
161         res = {'value': {
162                     'field_name': False,
163                     'field_id': False,
164                     'model_name': False,
165               }}
166
167         if model_id:
168             ir_model_obj = self.pool.get('ir.model')
169             model_ids = ir_model_obj.search(cr, uid, [('id', '=', model_id)])
170             model_id = model_ids and model_ids[0] or None
171             model_name = model_id and ir_model_obj.browse(cr, uid, model_id).model or False
172             res['value']['model_name'] = model_name
173
174         return res
175
176     def onchange_model_name(self, cr, uid, ids, model_name, context=None):
177         res = {'value': {
178                     'field_name': False,
179                     'field_id': False,
180                     'model_id': False,
181               }}
182
183         if model_name:
184             ir_model_obj = self.pool.get('ir.model')
185             model_ids = ir_model_obj.search(cr, uid, [('model', '=', model_name)])
186             model_id = model_ids and model_ids[0] or False
187             res['value']['model_id'] = model_id
188
189         return res
190
191     def onchange_field_name(self, cr, uid, ids, field_name, model_name):
192         res = {'value': {
193                 'field_id': False,
194             }}
195
196         if field_name and model_name:
197             ir_model_fields_obj = self.pool.get('ir.model.fields')
198             field_ids = ir_model_fields_obj.search(cr, uid, [('name', '=', field_name), ('model', '=', model_name)])
199             field_id = field_ids and field_ids[0] or False
200             res['value']['field_id'] = field_id
201
202         return res
203
204     def onchange_field_id(self, cr, uid, ids, field_id, model_name):
205         res = {'value': {
206                     'field_name': False,
207               }}
208
209         if field_id:
210             ir_model_fields_obj = self.pool.get('ir.model.fields')
211             field = ir_model_fields_obj.browse(cr, uid, field_id)
212             res['value']['field_name'] = field.name
213
214         return res
215
216     _defaults = {
217         'state': lambda *a: 'clear',
218     }
219
220
221 class ir_model_fields_anonymization_history(osv.osv):
222     _name = 'ir.model.fields.anonymization.history'
223     _order = "date desc"
224
225     _columns = {
226         'date': fields.datetime('Date', required=True, readonly=True),
227         'field_ids': fields.many2many('ir.model.fields.anonymization', 'anonymized_field_to_history_rel', 'field_id', 'history_id', 'Fields', readonly=True),
228         'state': fields.selection(selection=ANONYMIZATION_HISTORY_STATE, string='Status', required=True, readonly=True),
229         'direction': fields.selection(selection=ANONYMIZATION_DIRECTION, string='Direction', required=True, readonly=True),
230         'msg': fields.text('Message', readonly=True),
231         'filepath': fields.char(string='File path', size=256, readonly=True),
232     }
233
234
235 class ir_model_fields_anonymize_wizard(osv.osv_memory):
236     _name = 'ir.model.fields.anonymize.wizard'
237
238     def _get_state(self, cr, uid, ids, name, arg, context=None):
239         res = {}
240
241         state = self._get_state_value(cr, uid, context=None)
242         for id in ids:
243             res[id] = state
244
245         return res
246
247     def _get_summary(self, cr, uid, ids, name, arg, context=None):
248         res = {}
249         summary = self._get_summary_value(cr, uid, context)
250         for id in ids:
251             res[id] = summary
252
253         return res
254
255     _columns = {
256         'name': fields.char(size=64, string='File Name'),
257         'summary': fields.function(_get_summary, type='text', string='Summary'),
258         'file_export': fields.binary(string='Export'),
259         'file_import': fields.binary(string='Import', help="This is the file created by the anonymization process. It should have the '.pickle' extention."),
260         'state': fields.function(_get_state, string='Status', type='selection', selection=WIZARD_ANONYMIZATION_STATES, readonly=False),
261         'msg': fields.text(string='Message'),
262     }
263
264     def _get_state_value(self, cr, uid, context=None):
265         state = self.pool.get('ir.model.fields.anonymization')._get_global_state(cr, uid, context=context)
266         return state
267
268     def _get_summary_value(self, cr, uid, context=None):
269         summary = u''
270         anon_field_obj = self.pool.get('ir.model.fields.anonymization')
271         ir_model_fields_obj = self.pool.get('ir.model.fields')
272
273         anon_field_ids = anon_field_obj.search(cr, uid, [('state', '<>', 'not_existing')], context=context)
274         anon_fields = anon_field_obj.browse(cr, uid, anon_field_ids, context=context)
275
276         field_ids = [anon_field.field_id.id for anon_field in anon_fields if anon_field.field_id]
277         fields = ir_model_fields_obj.browse(cr, uid, field_ids, context=context)
278
279         fields_by_id = dict([(f.id, f) for f in fields])
280
281         for anon_field in anon_fields:
282             field = fields_by_id.get(anon_field.field_id.id)
283
284             values = {
285                 'model_name': field.model_id.name,
286                 'model_code': field.model_id.model,
287                 'field_code': field.name,
288                 'field_name': field.field_description,
289                 'state': anon_field.state,
290             }
291             summary += u" * %(model_name)s (%(model_code)s) -> %(field_name)s (%(field_code)s): state: (%(state)s)\n" % values
292
293         return summary
294
295     def default_get(self, cr, uid, fields_list, context=None):
296         res = {}
297         res['name'] = '.pickle'
298         res['summary'] = self._get_summary_value(cr, uid, context)
299         res['state'] = self._get_state_value(cr, uid, context)
300         res['msg'] = _("""Before executing the anonymization process, you should make a backup of your database.""")
301
302         return res
303
304     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, *args, **kwargs):
305         state = self.pool.get('ir.model.fields.anonymization')._get_global_state(cr, uid, context=context)
306
307         if context is None:
308             context = {}
309
310         step = context.get('step', 'new_window')
311
312         res = super(ir_model_fields_anonymize_wizard, self).fields_view_get(cr, uid, view_id, view_type, context, *args, **kwargs)
313
314         eview = etree.fromstring(res['arch'])
315         placeholder = eview.xpath("group[@name='placeholder1']")
316         if len(placeholder):
317             placeholder = placeholder[0]
318             if step == 'new_window' and state == 'clear':
319                 # clicked in the menu and the fields are not anonymized: warn the admin that backuping the db is very important
320                 placeholder.addnext(etree.Element('field', {'name': 'msg', 'colspan': '4', 'nolabel': '1'}))
321                 placeholder.addnext(etree.Element('newline'))
322                 placeholder.addnext(etree.Element('label', {'string': 'Warning'}))
323                 eview.remove(placeholder)
324             elif step == 'new_window' and state == 'anonymized':
325                 # clicked in the menu and the fields are already anonymized
326                 placeholder.addnext(etree.Element('newline'))
327                 placeholder.addnext(etree.Element('field', {'name': 'file_import', 'required': "1"}))
328                 placeholder.addnext(etree.Element('label', {'string': 'Anonymization file'}))
329                 eview.remove(placeholder)
330             elif step == 'just_anonymized':
331                 # we just ran the anonymization process, we need the file export field
332                 placeholder.addnext(etree.Element('newline'))
333                 placeholder.addnext(etree.Element('field', {'name': 'file_export'}))
334                 # we need to remove the button:
335                 buttons = eview.xpath("button")
336                 for button in buttons:
337                     eview.remove(button)
338                 # and add a message:
339                 placeholder.addnext(etree.Element('field', {'name': 'msg', 'colspan': '4', 'nolabel': '1'}))
340                 placeholder.addnext(etree.Element('newline'))
341                 placeholder.addnext(etree.Element('label', {'string': 'Result'}))
342                 # remove the placeholer:
343                 eview.remove(placeholder)
344             elif step == 'just_desanonymized':
345                 # we just reversed the anonymization process, we don't need any field
346                 # we need to remove the button
347                 buttons = eview.xpath("button")
348                 for button in buttons:
349                     eview.remove(button)
350                 # and add a message
351                 # and add a message:
352                 placeholder.addnext(etree.Element('field', {'name': 'msg', 'colspan': '4', 'nolabel': '1'}))
353                 placeholder.addnext(etree.Element('newline'))
354                 placeholder.addnext(etree.Element('label', {'string': 'Result'}))
355                 # remove the placeholer:
356                 eview.remove(placeholder)
357             else:
358                 msg = _("The database anonymization is currently in an unstable state. Some fields are anonymized," + \
359                   " while some fields are not anonymized. You should try to solve this problem before trying to do anything else.")
360                 raise osv.except_osv('Error!', msg)
361
362             res['arch'] = etree.tostring(eview)
363
364         return res
365
366     def _raise_after_history_update(self, cr, uid, history_id, error_type, error_msg):
367         self.pool.get('ir.model.fields.anonymization.history').write(cr, uid, history_id, {
368             'state': 'in_exception',
369             'msg': error_msg,
370         })
371         raise osv.except_osv(error_type, error_msg)
372
373     def anonymize_database(self, cr, uid, ids, context=None):
374         """Sets the 'anonymized' state to defined fields"""
375
376         # create a new history record:
377         anonymization_history_model = self.pool.get('ir.model.fields.anonymization.history')
378
379         vals = {
380             'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
381             'state': 'started',
382             'direction': 'clear -> anonymized',
383         }
384         history_id = anonymization_history_model.create(cr, uid, vals)
385
386         # check that all the defined fields are in the 'clear' state
387         state = self.pool.get('ir.model.fields.anonymization')._get_global_state(cr, uid, context=context)
388         if state == 'anonymized':
389             self._raise_after_history_update(cr, uid, history_id, _('Error !'), _("The database is currently anonymized, you cannot anonymize it again."))
390         elif state == 'unstable':
391             msg = _("The database anonymization is currently in an unstable state. Some fields are anonymized," + \
392                   " while some fields are not anonymized. You should try to solve this problem before trying to do anything.")
393             self._raise_after_history_update(cr, uid, history_id, 'Error !', msg)
394
395         # do the anonymization:
396         dirpath = os.environ.get('HOME') or os.getcwd()
397         rel_filepath = 'field_anonymization_%s_%s.pickle' % (cr.dbname, history_id)
398         abs_filepath = os.path.abspath(os.path.join(dirpath, rel_filepath))
399
400         ir_model_fields_anonymization_model = self.pool.get('ir.model.fields.anonymization')
401         field_ids = ir_model_fields_anonymization_model.search(cr, uid, [('state', '<>', 'not_existing')], context=context)
402         fields = ir_model_fields_anonymization_model.browse(cr, uid, field_ids, context=context)
403
404         if not fields:
405             msg = "No fields are going to be anonymized."
406             self._raise_after_history_update(cr, uid, history_id, 'Error !', msg)
407
408         data = []
409
410         for field in fields:
411             model_name = field.model_id.model
412             field_name = field.field_id.name
413             field_type = field.field_id.ttype
414             table_name = self.pool[model_name]._table
415
416             # get the current value
417             sql = "select id, %s from %s" % (field_name, table_name)
418             cr.execute(sql)
419             records = cr.dictfetchall()
420             for record in records:
421                 data.append({"model_id": model_name, "field_id": field_name, "id": record['id'], "value": record[field_name]})
422
423                 # anonymize the value:
424                 anonymized_value = None
425
426                 sid = str(record['id'])
427                 if field_type == 'char':
428                     anonymized_value = 'xxx'+sid
429                 elif field_type == 'selection':
430                     anonymized_value = 'xxx'+sid
431                 elif field_type == 'text':
432                     anonymized_value = 'xxx'+sid
433                 elif field_type == 'boolean':
434                     anonymized_value = random.choice([True, False])
435                 elif field_type == 'date':
436                     anonymized_value = '2011-11-11'
437                 elif field_type == 'datetime':
438                     anonymized_value = '2011-11-11 11:11:11'
439                 elif field_type == 'float':
440                     anonymized_value = 0.0
441                 elif field_type == 'integer':
442                     anonymized_value = 0
443                 elif field_type in ['binary', 'many2many', 'many2one', 'one2many', 'reference']: # cannot anonymize these kind of fields
444                     msg = _("Cannot anonymize fields of these types: binary, many2many, many2one, one2many, reference.")
445                     self._raise_after_history_update(cr, uid, history_id, 'Error !', msg)
446
447                 if anonymized_value is None:
448                     self._raise_after_history_update(cr, uid, history_id, _('Error !'), _("Anonymized value is None. This cannot happens."))
449
450                 sql = "update %(table)s set %(field)s = %%(anonymized_value)s where id = %%(id)s" % {
451                     'table': table_name,
452                     'field': field_name,
453                 }
454                 cr.execute(sql, {
455                     'anonymized_value': anonymized_value,
456                     'id': record['id']
457                 })
458
459         # save pickle:
460         fn = open(abs_filepath, 'w')
461         pickle.dump(data, fn, pickle.HIGHEST_PROTOCOL)
462
463         # update the anonymization fields:
464         values = {
465             'state': 'anonymized',
466         }
467         ir_model_fields_anonymization_model.write(cr, uid, field_ids, values, context=context)
468
469         # add a result message in the wizard:
470         msgs = ["Anonymization successful.",
471                "",
472                "Donot forget to save the resulting file to a safe place because you will not be able to revert the anonymization without this file.",
473                "",
474                "This file is also stored in the %s directory. The absolute file path is: %s.",
475               ]
476         msg = '\n'.join(msgs) % (dirpath, abs_filepath)
477
478         fn = open(abs_filepath, 'r')
479
480         self.write(cr, uid, ids, {
481             'msg': msg,
482             'file_export': base64.encodestring(fn.read()),
483         })
484         fn.close()
485
486         # update the history record:
487         anonymization_history_model.write(cr, uid, history_id, {
488             'field_ids': [[6, 0, field_ids]],
489             'msg': msg,
490             'filepath': abs_filepath,
491             'state': 'done',
492         })
493
494         # handle the view:
495         view_id = self._id_get(cr, uid, 'ir.ui.view', 'view_ir_model_fields_anonymize_wizard_form', 'anonymization')
496
497         return {
498                 'res_id': ids[0],
499                 'view_id': [view_id],
500                 'view_type': 'form',
501                 "view_mode": 'form',
502                 'res_model': 'ir.model.fields.anonymize.wizard',
503                 'type': 'ir.actions.act_window',
504                 'context': {'step': 'just_anonymized'},
505                 'target':'new',
506         }
507
508     def reverse_anonymize_database(self, cr, uid, ids, context=None):
509         """Set the 'clear' state to defined fields"""
510         ir_model_fields_anonymization_model = self.pool.get('ir.model.fields.anonymization')
511         anonymization_history_model = self.pool.get('ir.model.fields.anonymization.history')
512
513         # create a new history record:
514         vals = {
515             'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
516             'state': 'started',
517             'direction': 'anonymized -> clear',
518         }
519         history_id = anonymization_history_model.create(cr, uid, vals)
520
521         # check that all the defined fields are in the 'anonymized' state
522         state = ir_model_fields_anonymization_model._get_global_state(cr, uid, context=context)
523         if state == 'clear':
524             raise osv.except_osv_('Error!', "The database is not currently anonymized, you cannot reverse the anonymization.")
525         elif state == 'unstable':
526             msg = _("The database anonymization is currently in an unstable state. Some fields are anonymized," + \
527                   " while some fields are not anonymized. You should try to solve this problem before trying to do anything.")
528             raise osv.except_osv('Error!', msg)
529
530         wizards = self.browse(cr, uid, ids, context=context)
531         for wizard in wizards:
532             if not wizard.file_import:
533                 msg = _("It is not possible to reverse the anonymization process without supplying the anonymization export file.")
534                 self._raise_after_history_update(cr, uid, history_id, 'Error !', msg)
535
536             # reverse the anonymization:
537             # load the pickle file content into a data structure:
538             data = pickle.loads(base64.decodestring(wizard.file_import))
539
540             migration_fix_obj = self.pool.get('ir.model.fields.anonymization.migration.fix')
541             fix_ids = migration_fix_obj.search(cr, uid, [('target_version', '=', '7.0')])
542             fixes = migration_fix_obj.read(cr, uid, fix_ids, ['model_name', 'field_name', 'query', 'query_type', 'sequence'])
543             fixes = group(fixes, ('model_name', 'field_name'))
544
545             for line in data:
546                 queries = []
547                 table_name = self.pool[line['model_id']]._table if line['model_id'] in self.pool else None
548
549                 # check if custom sql exists:
550                 key = (line['model_id'], line['field_id'])
551                 custom_updates =  fixes.get(key)
552                 if custom_updates:
553                     custom_updates.sort(key=itemgetter('sequence'))
554                     queries = [(record['query'], record['query_type']) for record in custom_updates if record['query_type']]
555                 elif table_name:
556                     queries = [("update %(table)s set %(field)s = %%(value)s where id = %%(id)s" % {
557                         'table': table_name,
558                         'field': line['field_id'],
559                     }, 'sql')]
560
561                 for query in queries:
562                     if query[1] == 'sql':
563                         sql = query[0]
564                         cr.execute(sql, {
565                             'value': line['value'],
566                             'id': line['id']
567                         })
568                     elif query[1] == 'python':
569                         raw_code = query[0]
570                         code = raw_code % line
571                         eval(code)
572                     else:
573                         raise Exception("Unknown query type '%s'. Valid types are: sql, python." % (query['query_type'], ))
574
575             # update the anonymization fields:
576             ir_model_fields_anonymization_model = self.pool.get('ir.model.fields.anonymization')
577             field_ids = ir_model_fields_anonymization_model.search(cr, uid, [('state', '<>', 'not_existing')], context=context)
578             values = {
579                 'state': 'clear',
580             }
581             ir_model_fields_anonymization_model.write(cr, uid, field_ids, values, context=context)
582
583             # add a result message in the wizard:
584             msg = '\n'.join(["Successfully reversed the anonymization.",
585                              "",
586                             ])
587
588             self.write(cr, uid, ids, {'msg': msg})
589
590             # update the history record:
591             anonymization_history_model.write(cr, uid, history_id, {
592                 'field_ids': [[6, 0, field_ids]],
593                 'msg': msg,
594                 'filepath': False,
595                 'state': 'done',
596             })
597
598             # handle the view:
599             view_id = self._id_get(cr, uid, 'ir.ui.view', 'view_ir_model_fields_anonymize_wizard_form', 'anonymization')
600
601             return {
602                     'res_id': ids[0],
603                     'view_id': [view_id],
604                     'view_type': 'form',
605                     "view_mode": 'form',
606                     'res_model': 'ir.model.fields.anonymize.wizard',
607                     'type': 'ir.actions.act_window',
608                     'context': {'step': 'just_desanonymized'},
609                     'target':'new',
610             }
611
612     def _id_get(self, cr, uid, model, id_str, mod):
613         if '.' in id_str:
614             mod, id_str = id_str.split('.')
615         try:
616             idn = self.pool.get('ir.model.data')._get_id(cr, uid, mod, id_str)
617             res = int(self.pool.get('ir.model.data').read(cr, uid, [idn], ['res_id'])[0]['res_id'])
618         except:
619             res = None
620         return res
621
622
623 class ir_model_fields_anonymization_migration_fix(osv.osv):
624     _name = 'ir.model.fields.anonymization.migration.fix'
625     _order = "sequence"
626
627     _columns = {
628         'target_version': fields.char('Target Version'),
629         'model_name': fields.char('Model'),
630         'field_name': fields.char('Field'),
631         'query': fields.text('Query'),
632         'query_type': fields.selection(string='Query', selection=[('sql', 'sql'), ('python', 'python')]),
633         'sequence': fields.integer('Sequence'),
634     }
635
636 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
637