[ADD] Anonymization module
authornel@tinyerp.com <>
Fri, 7 Jan 2011 15:31:06 +0000 (16:31 +0100)
committernel@tinyerp.com <>
Fri, 7 Jan 2011 15:31:06 +0000 (16:31 +0100)
bzr revid: nel@tinyerp.com-20110107153106-6visyxwb8i21q262

addons/anonymization/__init__.py [new file with mode: 0644]
addons/anonymization/__terp__.py [new file with mode: 0644]
addons/anonymization/anonymization.py [new file with mode: 0644]
addons/anonymization/anonymization_demo.xml [new file with mode: 0644]
addons/anonymization/anonymization_view.xml [new file with mode: 0644]
addons/anonymization/i18n/anonymization.pot [new file with mode: 0644]
addons/anonymization/i18n/fr.po [new file with mode: 0644]
addons/anonymization/ir.model.fields.anonymization.csv [new file with mode: 0644]
addons/anonymization/security/ir.model.access.csv [new file with mode: 0644]

diff --git a/addons/anonymization/__init__.py b/addons/anonymization/__init__.py
new file mode 100644 (file)
index 0000000..d3c219a
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution  
+#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
+#    $Id$
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import anonymization
+import wizard
+
diff --git a/addons/anonymization/__terp__.py b/addons/anonymization/__terp__.py
new file mode 100644 (file)
index 0000000..60f6f6a
--- /dev/null
@@ -0,0 +1,48 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution  
+#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). All Rights Reserved
+#    $Id$
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+{
+    'name': 'Database anonymization module',
+    'version': '1.0',
+    'category': 'Tools',
+    'description': """
+This module allows you to anonymize a database.
+    """,
+    'author': 'OpenERP sa',
+    'website': 'http://www.openerp.com',
+    'depends': ['base'],
+    'init_xml': [],
+    'update_xml': [],
+    'demo_xml': [
+        'anonymization_demo.xml',
+    ],
+    'data': [
+        'ir.model.fields.anonymization.csv',
+        'security/ir.model.access.csv',
+        'anonymization_view.xml',
+    ],
+    'installable': True,
+    'active': False,
+    'certificate': '00719010980872226045',
+}
+
diff --git a/addons/anonymization/anonymization.py b/addons/anonymization/anonymization.py
new file mode 100644 (file)
index 0000000..252a922
--- /dev/null
@@ -0,0 +1,594 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
+#    $Id$
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from lxml import etree
+import os
+import base64
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+import random
+import datetime
+
+import netsvc
+import pooler, tools
+from osv import fields, osv
+from tools.translate import _
+
+
+FIELD_STATES = [('clear', 'Clear'), ('anonymized', 'Anonymized'), ('not_existing', 'Not Existing')]
+ANONYMIZATION_STATES = FIELD_STATES + [('unstable', 'Unstable')]
+ANONYMIZATION_HISTORY_STATE = [('started', 'Started'), ('done', 'Done'), ('in_exception', 'Exception occured')]
+ANONYMIZATION_DIRECTION = [('clear -> anonymized', 'clear -> anonymized'), ('anonymized -> clear', 'anonymized -> clear')]
+
+
+class ir_model_fields_anonymization(osv.osv):
+    _name = 'ir.model.fields.anonymization'
+    _rec_name = 'field_id'
+
+    _columns = {
+        'model_name': fields.char('Object Name', size=128, required=True),
+        'model_id': fields.many2one('ir.model', 'Object', ondelete='set null'),
+        'field_name': fields.char('Field Name', size=128, required=True),
+        'field_id': fields.many2one('ir.model.fields', 'Field', ondelete='set null'),
+        'state': fields.selection(selection=FIELD_STATES, String='State', required=True, readonly=True),
+    }
+
+    _sql_constraints = [
+        ('model_id_field_id_uniq', 'unique (model_name, field_name)', _("You cannot have two records having the same model and the same field")),
+    ]
+
+    def _get_global_state(self, cr, uid, context=None):
+        ids = self.search(cr, uid, [('state', '<>', 'not_existing')], context=context)
+        fields = self.browse(cr, uid, ids, context=context)
+        if not len(fields) or len(fields) == len([f for f in fields if f.state == 'clear']):
+            state = 'clear' # all fields are clear
+        elif len(fields) == len([f for f in fields if f.state == 'anonymized']):
+            state = 'anonymized' # all fields are anonymized
+        else:
+            state = 'unstable' # fields are mixed: this should be fixed
+        return state
+
+    def _check_write(self, cr, uid, context=None):
+        # check that the field is created from the menu and not from an database update
+        # otherwise the database update can crash:
+        if context.get('manual'):
+            global_state = self._get_global_state(cr, uid, context=context)
+            if global_state == 'anonymized':
+                raise osv.except_osv('Error !', "The database is currently anonymized, you cannot create, modify or delete fields.")
+            elif global_state == 'unstable':
+                msg = "The database anonymization is currently in an unstable state. Some fields are anonymized," + \
+                      " while some fields are not anonymized. You should try to solve this problem before trying to create, write or delete fields."
+                raise osv.except_osv('Error !', msg)
+
+        return True
+
+    def _get_model_and_field_ids(self, cr, uid, vals, context=None):
+        model_and_field_ids = (False, False)
+
+        if 'field_name' in vals and vals['field_name'] and 'model_name' in vals and vals['model_name']:
+            ir_model_fields_obj = self.pool.get('ir.model.fields')
+            ir_model_obj = self.pool.get('ir.model')
+
+            model_ids = ir_model_obj.search(cr, uid, [('model', '=', vals['model_name'])], context=context)
+            if model_ids:
+                field_ids = ir_model_fields_obj.search(cr, uid, [('name', '=', vals['field_name']), ('model_id', '=', model_ids[0])], context=context)
+                if field_ids:
+                    field_id = field_ids[0]
+                    model_and_field_ids = (model_ids[0], field_id)
+
+        return model_and_field_ids
+
+    def create(self, cr, uid, vals, context=None):
+        # check field state: all should be clear before we can add a new field to anonymize:
+        self._check_write(cr, uid, context=context)
+
+        if 'field_name' in vals and vals['field_name'] and 'model_name' in vals and vals['model_name']:
+            vals['model_id'], vals['field_id'] = self._get_model_and_field_ids(cr, uid, vals, context=context)
+
+        # check not existing fields:
+        if not vals.get('field_id'):
+            vals['state'] = 'not_existing'
+
+        res = super(ir_model_fields_anonymization, self).create(cr, uid, vals, context=context)
+
+        return res
+
+    def write(self, cr, uid, ids, vals, context=None):
+        # check field state: all should be clear before we can modify a field:
+        if not (len(vals.keys()) == 1 and vals.get('state') == 'clear'):
+            self._check_write(cr, uid, context=context)
+
+        if 'field_name' in vals and vals['field_name'] and 'model_name' in vals and vals['model_name']:
+            vals['model_id'], vals['field_id'] = self._get_model_and_field_ids(cr, uid, vals, context=context)
+
+        # check not existing fields:
+        if 'field_id' in vals:
+            if not vals.get('field_id'):
+                vals['state'] = 'not_existing'
+            else:
+                global_state = self._get_global_state(cr, uid, context)
+                if global_state != 'unstable':
+                    vals['state'] = global_state
+
+        res = super(ir_model_fields_anonymization, self).write(cr, uid, ids, vals, context=context)
+
+        return res
+
+    def unlink(self, cr, uid, ids, context=None):
+        # check field state: all should be clear before we can unlink a field:
+        self._check_write(cr, uid, context=context)
+
+        res = super(ir_model_fields_anonymization, self).unlink(cr, uid, ids, context=context)
+        return res
+
+    def onchange_model_id(self, cr, uid, ids, model_id, context=None):
+        res = {'value': {
+                    'field_name': False,
+                    'field_id': False,
+                    'model_name': False,
+              }}
+
+        if model_id:
+            ir_model_obj = self.pool.get('ir.model')
+            model_ids = ir_model_obj.search(cr, uid, [('id', '=', model_id)])
+            model_id = model_ids and model_ids[0] or None
+            model_name = model_id and ir_model_obj.browse(cr, uid, model_id).model or False
+            res['value']['model_name'] = model_name
+
+        return res
+
+    def onchange_model_name(self, cr, uid, ids, model_name, context=None):
+        res = {'value': {
+                    'field_name': False,
+                    'field_id': False,
+                    'model_id': False,
+              }}
+
+        if model_name:
+            ir_model_obj = self.pool.get('ir.model')
+            model_ids = ir_model_obj.search(cr, uid, [('model', '=', model_name)])
+            model_id = model_ids and model_ids[0] or False
+            res['value']['model_id'] = model_id
+
+        return res
+
+    def onchange_field_name(self, cr, uid, ids, field_name, model_name):
+        res = {'value': {
+                'field_id': False,
+            }}
+
+        if field_name and model_name:
+            ir_model_fields_obj = self.pool.get('ir.model.fields')
+            field_ids = ir_model_fields_obj.search(cr, uid, [('name', '=', field_name), ('model', '=', model_name)])
+            field_id = field_ids and field_ids[0] or False
+            res['value']['field_id'] = field_id
+
+        return res
+
+    def onchange_field_id(self, cr, uid, ids, field_id, model_name):
+        res = {'value': {
+                    'field_name': False,
+              }}
+
+        if field_id:
+            ir_model_fields_obj = self.pool.get('ir.model.fields')
+            field = ir_model_fields_obj.browse(cr, uid, field_id)
+            res['value']['field_name'] = field.name
+
+        return res
+
+    _defaults = {
+        'state': lambda *a: 'clear',
+    }
+
+ir_model_fields_anonymization()
+
+
+class ir_model_fields_anonymization_history(osv.osv):
+    _name = 'ir.model.fields.anonymization.history'
+    _order = "date desc"
+
+    _columns = {
+        'date': fields.datetime('Date', required=True, readonly=True),
+        'field_ids': fields.many2many('ir.model.fields.anonymization', 'anonymized_field_to_history_rel', 'field_id', 'history_id', 'Fields', readonly=True),
+        'state': fields.selection(selection=ANONYMIZATION_HISTORY_STATE, string='State', required=True, readonly=True),
+        'direction': fields.selection(selection=ANONYMIZATION_DIRECTION, string='Direction', required=True, readonly=True),
+        'msg': fields.text('Message', readonly=True),
+        'filepath': fields.char(string='File path', size=256, readonly=True),
+    }
+
+ir_model_fields_anonymization_history()
+
+
+class ir_model_fields_anonymize_wizard(osv.osv_memory):
+    _name = 'ir.model.fields.anonymize.wizard'
+
+    def _get_state(self, cr, uid, ids, name, arg, context=None):
+        res = {}
+
+        state = self._get_state_value(cr, uid, context=None)
+        for id in ids:
+            res[id] = state
+
+        return res
+
+    def _get_summary(self, cr, uid, ids, name, arg, context=None):
+        res = {}
+        summary = self._get_summary_value(cr, uid, context)
+        for id in ids:
+            res[id] = summary
+
+        return res
+
+    _columns = {
+        'name': fields.char(size='64', string='File Name'),
+        'summary': fields.function(_get_summary, method=True, type='text', string='Summary'),
+        'file_export': fields.binary(string='Export'),
+        'file_import': fields.binary(string='Import'),
+        'state': fields.function(_get_state, method=True, string='State', type='selection', selection=ANONYMIZATION_STATES, readonly=False),
+        'msg': fields.text(string='Message'),
+    }
+
+    def _get_state_value(self, cr, uid, context=None):
+        state = self.pool.get('ir.model.fields.anonymization')._get_global_state(cr, uid, context=context)
+        return state
+
+    def _get_summary_value(self, cr, uid, context=None):
+        summary = u''
+        anon_field_obj = self.pool.get('ir.model.fields.anonymization')
+        ir_model_obj = self.pool.get('ir.model')
+        ir_model_fields_obj = self.pool.get('ir.model.fields')
+
+        anon_field_ids = anon_field_obj.search(cr, uid, [('state', '<>', 'not_existing')], context=context)
+        anon_fields = anon_field_obj.browse(cr, uid, anon_field_ids, context=context)
+
+        field_ids = [anon_field.field_id.id for anon_field in anon_fields if anon_field.field_id]
+        fields = ir_model_fields_obj.browse(cr, uid, field_ids, context=context)
+
+        fields_by_id = dict([(f.id, f) for f in fields])
+
+        for anon_field in anon_fields:
+            field = fields_by_id.get(anon_field.field_id.id)
+
+            values = {
+                'model_name': field.model_id.name,
+                'model_code': field.model_id.model,
+                'field_code': field.name,
+                'field_name': field.field_description,
+                'state': anon_field.state,
+            }
+            summary += u" * %(model_name)s (%(model_code)s) -> %(field_name)s (%(field_code)s): state: (%(state)s)\n" % values
+
+        return summary
+
+    def default_get(self, cr, uid, fields_list, context=None):
+        res = {}
+        res['name'] = '.pickle'
+        res['summary'] = self._get_summary_value(cr, uid, context)
+        res['state'] = self._get_state_value(cr, uid, context)
+        res['msg'] = """Before executing the anonymization process, you should make a backup of your database."""
+
+        return res
+
+    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, *args, **kwargs):
+        state = self.pool.get('ir.model.fields.anonymization')._get_global_state(cr, uid, context=context)
+        step = context.get('step', 'new_window')
+
+        res = super(ir_model_fields_anonymize_wizard, self).fields_view_get(cr, uid, view_id, view_type, context, *args, **kwargs)
+
+        eview = etree.fromstring(res['arch'])
+        placeholder = eview.xpath("group[@name='placeholder1']")
+        placeholder = len(placeholder) and placeholder[0] or None
+
+        if placeholder:
+            if step == 'new_window' and state == 'clear':
+                # clicked in the menu and the fields are not anonymized: warn the admin that backuping the db is very important
+                placeholder.addnext(etree.Element('field', {'name': 'msg', 'colspan': '4', 'nolabel': '1'}))
+                placeholder.addnext(etree.Element('newline'))
+                placeholder.addnext(etree.Element('label', {'string': 'Warning'}))
+                eview.remove(placeholder)
+            elif step == 'new_window' and state == 'anonymized':
+                # clicked in the menu and the fields are already anonymized
+                placeholder.addnext(etree.Element('newline'))
+                placeholder.addnext(etree.Element('field', {'name': 'file_import', 'required': "1"}))
+                eview.remove(placeholder)
+            elif step == 'just_anonymized':
+                # we just ran the anonymization process, we need the file export field
+                placeholder.addnext(etree.Element('newline'))
+                placeholder.addnext(etree.Element('field', {'name': 'file_export'}))
+                # we need to remove the button:
+                buttons = eview.xpath("button")
+                for button in buttons:
+                    eview.remove(button)
+                # and add a message:
+                placeholder.addnext(etree.Element('field', {'name': 'msg', 'colspan': '4', 'nolabel': '1'}))
+                placeholder.addnext(etree.Element('newline'))
+                placeholder.addnext(etree.Element('label', {'string': 'Result'}))
+                # remove the placeholer:
+                eview.remove(placeholder)
+            elif step == 'just_desanonymized':
+                # we just reversed the anonymization process, we don't need any field
+                # we need to remove the button
+                buttons = eview.xpath("button")
+                for button in buttons:
+                    eview.remove(button)
+                # and add a message
+                # and add a message:
+                placeholder.addnext(etree.Element('field', {'name': 'msg', 'colspan': '4', 'nolabel': '1'}))
+                placeholder.addnext(etree.Element('newline'))
+                placeholder.addnext(etree.Element('label', {'string': 'Result'}))
+                # remove the placeholer:
+                eview.remove(placeholder)
+            else:
+                from olilib.openerp import Terp, ppt, pst
+                import pydb; pydb.debugger(['set listsize 40'])
+                print
+
+                # unstable ?
+                raise
+
+            res['arch'] = etree.tostring(eview)
+
+        return res
+
+    def _raise_after_history_update(self, cr, uid, history_id, error_type, error_msg):
+        self.pool.get('ir.model.fields.anonymization.history').write(cr, uid, history_id, {
+            'state': 'in_exception',
+            'msg': error_msg,
+        })
+        raise osv.except_osv(error_type, error_msg)
+
+    def anonymize_database(self,cr, uid, ids, context=None):
+        """Sets the 'anonymized' state to defined fields"""
+
+        # create a new history record:
+        anonymization_history_model = self.pool.get('ir.model.fields.anonymization.history')
+
+        vals = {
+            'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+            'state': 'started',
+            'direction': 'clear -> anonymized',
+        }
+        history_id = anonymization_history_model.create(cr, uid, vals)
+
+        # check that all the defined fields are in the 'clear' state
+        state = self.pool.get('ir.model.fields.anonymization')._get_global_state(cr, uid, context=context)
+        if state == 'anonymized':
+            self._raise_after_history_update(cr, uid, history_id, 'Error !', "The database is currently anonymized, you cannot anonymize it again.")
+        elif state == 'unstable':
+            msg = "The database anonymization is currently in an unstable state. Some fields are anonymized," + \
+                  " while some fields are not anonymized. You should try to solve this problem before trying to do anything."
+            self._raise_after_history_update(cr, uid, history_id, 'Error !', msg)
+
+        # do the anonymization:
+        dirpath = os.environ.get('HOME') or os.getcwd()
+        rel_filepath = 'field_anonymization_%s_%s.pickle' % (cr.dbname, history_id)
+        abs_filepath = os.path.abspath(os.path.join(dirpath, rel_filepath))
+
+        ir_model_fields_anonymization_model = self.pool.get('ir.model.fields.anonymization')
+        field_ids = ir_model_fields_anonymization_model.search(cr, uid, [('state', '<>', 'not_existing')], context=context)
+        fields = ir_model_fields_anonymization_model.browse(cr, uid, field_ids, context=context)
+
+        if not fields:
+            msg = "No fields are going to be anonymized."
+            self._raise_after_history_update(cr, uid, history_id, 'Error !', msg)
+
+        data = []
+
+        for field in fields:
+            model_name = field.model_id.model
+            field_name = field.field_id.name
+            field_type = field.field_id.ttype
+            table_name = self.pool.get(model_name)._table
+
+            # get the current value
+            sql = "select id, %s from %s" % (field_name, table_name)
+            cr.execute(sql)
+            records = cr.dictfetchall()
+            for record in records:
+                data.append({"model_id": model_name, "field_id": field_name, "id": record['id'], "value": record[field_name]})
+
+                # anonymize the value:
+                anonymized_value = None
+
+                sid = str(record['id'])
+                if field_type == 'char':
+                    anonymized_value = 'xxx'+sid
+                elif field_type == 'selection':
+                    anonymized_value = 'xxx'+sid
+                elif field_type == 'text':
+                    anonymized_value = 'xxx'+sid
+                elif field_type == 'boolean':
+                    anonymized_value = random.choice([True, False])
+                elif field_type == 'date':
+                    anonymized_value = '2011-11-11'
+                elif field_type == 'datetime':
+                    anonymized_value = '2011-11-11 11:11:11'
+                elif field_type == 'float':
+                    if record[field_name] > 0:
+                        anonymized_value = 1.0
+                    elif record[field_name] < 0:
+                        anonymized_value = -1.0
+                    else:
+                        anonymized_value = 0.0
+                elif field_type == 'integer':
+                    anonymized_value = 1
+                elif field_type in ['binary', 'many2many', 'many2one', 'one2many', 'reference']: # cannot anonymize these kind of fields
+                    msg = "Cannot anonymize fields of these types: binary, many2many, many2one, one2many, reference"
+                    self._raise_after_history_update(cr, uid, history_id, 'Error !', msg)
+
+                if anonymized_value is None:
+                    self._raise_after_history_update(cr, uid, history_id, 'Error !', "Anonymized value is None. This cannot happens.")
+
+                sql = "update %(table)s set %(field)s = %%(anonymized_value)s where id = %%(id)s" % {
+                    'table': table_name,
+                    'field': field_name,
+                }
+                cr.execute(sql, {
+                    'anonymized_value': anonymized_value,
+                    'id': record['id']
+                })
+
+        # save pickle:
+        fn = open(abs_filepath, 'w')
+        pickle.dump(data, fn, pickle.HIGHEST_PROTOCOL)
+
+        # update the anonymization fields:
+        values = {
+            'state': 'anonymized',
+        }
+        res = ir_model_fields_anonymization_model.write(cr, uid, field_ids, values, context=context)
+
+        # add a result message in the wizard:
+        msgs = ["Anonymization successful.",
+               "",
+               "Don't forget to save the resulting file to a safe place because you will not be able to revert the anonymization without this file.",
+               "",
+               "This file is also stored in the %s directory. The absolute file path is: %s",
+              ]
+        msg = '\n'.join(msgs) % (dirpath, abs_filepath)
+
+        fn = open(abs_filepath, 'r')
+
+        self.write(cr, uid, ids, {
+            'msg': msg,
+            'file_export': base64.encodestring(fn.read()),
+        })
+        fn.close()
+
+        # update the history record:
+        anonymization_history_model.write(cr, uid, history_id, {
+            'field_ids': [[6, 0, field_ids]],
+            'msg': msg,
+            'filepath': abs_filepath,
+            'state': 'done',
+        })
+
+        # handle the view:
+        view_id = self._id_get(cr, uid, 'ir.ui.view', 'view_ir_model_fields_anonymize_wizard_form', 'anonymization')
+
+        return {
+                'res_id': ids[0],
+                'view_id': [view_id],
+                'view_type': 'form',
+                "view_mode": 'form',
+                'res_model': 'ir.model.fields.anonymize.wizard',
+                'type': 'ir.actions.act_window',
+                'context': {'step': 'just_anonymized'},
+                'target':'new',
+        }
+
+    def reverse_anonymize_database(self,cr, uid, ids, context=None):
+        """Set the 'clear' state to defined fields"""
+
+        ir_model_fields_anonymization_model = self.pool.get('ir.model.fields.anonymization')
+        anonymization_history_model = self.pool.get('ir.model.fields.anonymization.history')
+
+        # create a new history record:
+        vals = {
+            'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+            'state': 'started',
+            'direction': 'anonymized -> clear',
+        }
+        history_id = anonymization_history_model.create(cr, uid, vals)
+
+        # check that all the defined fields are in the 'anonymized' state
+        state = ir_model_fields_anonymization_model._get_global_state(cr, uid, context=context)
+        if state == 'clear':
+            raise osv.except_osv('Error !', "The database is not currently anonymized, you cannot reverse the anonymization.")
+        elif state == 'unstable':
+            msg = "The database anonymization is currently in an unstable state. Some fields are anonymized," + \
+                  " while some fields are not anonymized. You should try to solve this problem before trying to do anything."
+            raise osv.except_osv('Error !', msg)
+
+        wizards = self.browse(cr, uid, ids, context=context)
+        for wizard in wizards:
+            if not wizard.file_import:
+                msg = "The anonymization export file was not supplied. It is not possible to reverse the anonymization process without this file."
+                self._raise_after_history_update(cr, uid, history_id, 'Error !', msg)
+
+            # reverse the anonymization:
+            # load the pickle file content into a data structure:
+            data = pickle.loads(base64.decodestring(wizard.file_import))
+
+            for line in data:
+                table_name = self.pool.get(line['model_id'])._table
+                sql = "update %(table)s set %(field)s = %%(value)s where id = %%(id)s" % {
+                    'table': table_name,
+                    'field': line['field_id'],
+                }
+                cr.execute(sql, {
+                    'value': line['value'],
+                    'id': line['id']
+                })
+
+            # update the anonymization fields:
+            ir_model_fields_anonymization_model = self.pool.get('ir.model.fields.anonymization')
+            field_ids = ir_model_fields_anonymization_model.search(cr, uid, [('state', '<>', 'not_existing')], context=context)
+            values = {
+                'state': 'clear',
+            }
+            res = ir_model_fields_anonymization_model.write(cr, uid, field_ids, values, context=context)
+
+            # add a result message in the wizard:
+            msg = '\n'.join(["Successfully reversed the anonymization.",
+                             "",
+                            ])
+
+            self.write(cr, uid, ids, {'msg': msg})
+
+            # update the history record:
+            anonymization_history_model.write(cr, uid, history_id, {
+                'field_ids': [[6, 0, field_ids]],
+                'msg': msg,
+                'filepath': False,
+                'state': 'done',
+            })
+
+            # handle the view:
+            view_id = self._id_get(cr, uid, 'ir.ui.view', 'view_ir_model_fields_anonymize_wizard_form', 'anonymization')
+
+            return {
+                    'res_id': ids[0],
+                    'view_id': [view_id],
+                    'view_type': 'form',
+                    "view_mode": 'form',
+                    'res_model': 'ir.model.fields.anonymize.wizard',
+                    'type': 'ir.actions.act_window',
+                    'context': {'step': 'just_desanonymized'},
+                    'target':'new',
+            }
+
+    def _id_get(self, cr, uid, model, id_str, mod):
+        if '.' in id_str:
+            mod, id_str = id_str.split('.')
+        try:
+            idn = self.pool.get('ir.model.data')._get_id(cr, uid, mod, id_str)
+            res = int(self.pool.get('ir.model.data').read(cr, uid, [idn], ['res_id'])[0]['res_id'])
+        except Exception, e:
+            res = None
+        return res
+
+ir_model_fields_anonymize_wizard()
+
diff --git a/addons/anonymization/anonymization_demo.xml b/addons/anonymization/anonymization_demo.xml
new file mode 100644 (file)
index 0000000..1183cc9
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<openerp>
+<data noupdate="1">
+
+    <record id="anonymization_field_ir_actions_todo_name" model="ir.model.fields.anonymization">
+        <field name="model_name">ir.actions.todo</field>
+        <field name="field_name">name</field>
+    </record>
+
+    <record id="anonymization_field_a_non_existing_field_in_an_existing_model" model="ir.model.fields.anonymization">
+        <field name="model_name">res.partner</field>
+        <field name="field_name">a_non_existing_field</field>
+    </record>
+
+    <record id="anonymization_field_a_non_existing_field_in_a_non_existing_model" model="ir.model.fields.anonymization">
+        <field name="model_name">a.non.existing.model</field>
+        <field name="field_name">a_non_existing_field</field>
+    </record>
+
+</data>
+</openerp>
+
diff --git a/addons/anonymization/anonymization_view.xml b/addons/anonymization/anonymization_view.xml
new file mode 100644 (file)
index 0000000..c9b424d
--- /dev/null
@@ -0,0 +1,173 @@
+<?xml version="1.0" ?>
+<openerp>
+<data>
+
+    <!-- FIELD LIST -->
+
+    <record model="ir.ui.view" id="view_ir_model_fields_anonymization_form">
+        <field name="name">ir.model.fields.anonymization.form</field>
+        <field name="model">ir.model.fields.anonymization</field>
+        <field name="type">form</field>
+        <field name="arch" type="xml">
+            <form string="Anonymized Field">
+                <group colspan="4" col="8">
+                    <field name="model_id" select="1" on_change="onchange_model_id(model_id)" />
+                    <field name="model_name" select="1" on_change="onchange_model_name(model_name)" />
+                    <field name="field_id"
+                           select="1"
+                           on_change="onchange_field_id(field_id, model_name)"
+                           domain="[('model_id','=',model_id), ('ttype', 'not in', ['function', 'binary', 'many2many', 'many2one', 'one2many', 'reference'])]" />
+                    <field name="field_name" select="1" on_change="onchange_field_name(field_name, model_name)" />
+                </group>
+                <field name="state" />
+            </form>
+        </field>
+    </record>
+
+    <record model="ir.ui.view" id="view_ir_model_fields_anonymization_tree">
+        <field name="name">ir.model.fields.anonymization.tree</field>
+        <field name="model">ir.model.fields.anonymization</field>
+        <field name="type">tree</field>
+        <field name="arch" type="xml">
+            <tree string="Anonymized Fields">
+                <field name="model_id" />
+                <field name="model_name" />
+                <field name="field_id" />
+                <field name="field_name" />
+                <field name="state" />
+            </tree>
+        </field>
+    </record>
+
+    <record model="ir.actions.act_window" id="action_ir_model_fields_anonymization_tree">
+        <field name="name">Anonymized Fields</field>
+        <field name="res_model">ir.model.fields.anonymization</field>
+        <field name="view_type">form</field>
+        <field name="context">{'manual': True}</field>
+        <field name="view_mode">tree,form</field>
+    </record>
+
+    <!-- ANONYMIZE WIZARD -->
+      <!-- VIEW 1 -->
+
+    <record model="ir.ui.view" id="view_ir_model_fields_anonymize_wizard_form">
+        <field name="name">ir.model.fields.anonymize.wizard.form</field>
+        <field name="model">ir.model.fields.anonymize.wizard</field>
+        <field name="type">form</field>
+        <field name="arch" type="xml">
+            <form string="Database Anonymization">
+                <label string="Summary" />
+                <newline />
+                <group colspan="4" col="4">
+                    <field name="summary" nolabel="1" readonly="0" width="400" />
+                </group>
+                <newline />
+                <group name="placeholder1">
+                    <field name="file_export" />
+                    <field name="file_import" />
+                    <field name="msg" />
+                </group>
+                <button name="anonymize_database"
+                        string="Anonymize Database"
+                        type="object"
+                        states="clear" />
+                <button name="reverse_anonymize_database"
+                        string="Reverse the Database Anonymization"
+                        type="object"
+                        states="anonymized" />
+                        <newline />
+                <field name="state" />
+            </form>
+        </field>
+    </record>
+
+      <!-- ACTION -->
+
+    <record model="ir.actions.act_window" id="action_ir_model_fields_anonymize_wizard">
+        <field name="name">Anonymize Database</field>
+        <field name="res_model">ir.model.fields.anonymize.wizard</field>
+        <field name="target">new</field>
+        <field name="view_type">form</field>
+        <field name="view_mode">form</field>
+    </record>
+
+    <!-- HISTORY -->
+
+    <record model="ir.actions.act_window" id="action_ir_model_fields_anonymization_history_tree">
+        <field name="name">Anonymization History</field>
+        <field name="res_model">ir.model.fields.anonymization.history</field>
+        <field name="view_type">form</field>
+        <field name="view_mode">tree,form</field>
+    </record>
+
+    <record model="ir.ui.view" id="view_ir_model_fields_anonymization_history_form">
+        <field name="name">ir.model.fields.anonymization.history.form</field>
+        <field name="model">ir.model.fields.anonymization.history</field>
+        <field name="type">form</field>
+        <field name="arch" type="xml">
+            <form string="Anonymization History">
+                <group colspan="4" col="16">
+                    <field name="date" select="1" colspan="1" />
+                    <field name="state" select="1" colspan="1" />
+                    <field name="filepath" colspan="7" />
+                    <field name="direction" colspan="3" />
+                </group>
+                <group colspan="4" col="2">
+                    <label string="Message" />
+                    <field name="msg" nolabel="1" colspan="4" readonly="0" height="150" />
+                </group>
+                <group colspan="4" col="2">
+                    <label string="Fields" />
+                    <field name="field_ids" nolabel="1" colspan="4" height="300">
+                        <tree>
+                            <field name="model_id" />
+                            <field name="field_id" />
+                        </tree>
+                    </field>
+                </group>
+            </form>
+        </field>
+    </record>
+
+    <record model="ir.ui.view" id="view_ir_model_fields_anonymization_history_tree">
+        <field name="name">ir.model.fields.anonymization.history.tree</field>
+        <field name="model">ir.model.fields.anonymization.history</field>
+        <field name="type">tree</field>
+        <field name="arch" type="xml">
+            <tree string="Anonymization History">
+                <field name="date" />
+                <field name="state" />
+                <field name="filepath" />
+                <field name="direction" />
+            </tree>
+        </field>
+    </record>
+
+    <!-- MENUS -->
+
+    <menuitem id="menu_administration_anonymization"
+              name="Database anonymization"
+              parent="base.menu_administration"
+              sequence="30" />
+
+    <menuitem id="menu_administration_anonymization_history"
+              name="Anonymization History"
+              action="action_ir_model_fields_anonymization_history_tree"
+              parent="menu_administration_anonymization"
+              sequence="10" />
+
+    <menuitem id="menu_administration_anonymization_fields"
+              name="Anonymized Fields"
+              action="action_ir_model_fields_anonymization_tree"
+              parent="menu_administration_anonymization"
+              sequence="20" />
+
+    <menuitem id="menu_administration_anonymization_wizard"
+              action="action_ir_model_fields_anonymize_wizard"
+              name="Anonymize database"
+              parent="menu_administration_anonymization"
+              sequence="30" />
+
+</data>
+</openerp>
+
diff --git a/addons/anonymization/i18n/anonymization.pot b/addons/anonymization/i18n/anonymization.pot
new file mode 100644 (file)
index 0000000..2dd0e73
--- /dev/null
@@ -0,0 +1,201 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#      * anonymization
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.0.0-rc1\n"
+"Report-Msgid-Bugs-To: support@openerp.com\n"
+"POT-Creation-Date: 2010-12-10 14:48:44+0000\n"
+"PO-Revision-Date: 2010-12-10 15:49+0100\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: \n"
+
+#. module: anonymization
+#: model:ir.model,name:anonymization.model_ir_model_fields_anonymize_wizard
+msgid "ir.model.fields.anonymize.wizard"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymization,field_id:0
+msgid "Field"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymization.history,state:0
+#: field:ir.model.fields.anonymize.wizard,state:0
+msgid "State"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymize.wizard,file_import:0
+msgid "Import"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.model,name:anonymization.model_ir_model_fields_anonymization
+msgid "ir.model.fields.anonymization"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.module.module,shortdesc:anonymization.module_meta_information
+msgid "Database anonymization module"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymization.history,direction:0
+msgid "Direction"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.actions.act_window,name:anonymization.action_ir_model_fields_anonymization_tree
+#: model:ir.ui.menu,name:anonymization.menu_administration_anonymization_fields
+msgid "Anonymized Fields"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.ui.menu,name:anonymization.menu_administration_anonymization
+msgid "Database anonymization"
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymization.history,direction:0
+msgid "clear -> anonymized"
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymization,state:0
+#: selection:ir.model.fields.anonymize.wizard,state:0
+msgid "Anonymized"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymization,state:0
+msgid "unknown"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymization,model_id:0
+msgid "Object"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymization.history,filepath:0
+msgid "File path"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymization.history,date:0
+msgid "Date"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymize.wizard,file_export:0
+msgid "Export"
+msgstr ""
+
+#. module: anonymization
+#: view:ir.model.fields.anonymize.wizard:0
+msgid "Reverse the Database Anonymization"
+msgstr ""
+
+#. module: anonymization
+#: view:ir.model.fields.anonymize.wizard:0
+msgid "Database Anonymization"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.ui.menu,name:anonymization.menu_administration_anonymization_wizard
+msgid "Anonymize database"
+msgstr ""
+
+#. module: anonymization
+#: view:ir.model.fields.anonymization.history:0
+#: field:ir.model.fields.anonymization.history,field_ids:0
+msgid "Fields"
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymization,state:0
+#: selection:ir.model.fields.anonymize.wizard,state:0
+msgid "Clear"
+msgstr ""
+
+#. module: anonymization
+#: view:ir.model.fields.anonymize.wizard:0
+#: field:ir.model.fields.anonymize.wizard,summary:0
+msgid "Summary"
+msgstr ""
+
+#. module: anonymization
+#: view:ir.model.fields.anonymization:0
+msgid "Anonymized Field"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.module.module,description:anonymization.module_meta_information
+msgid ""
+"\n"
+"This module allows you to anonymize a database.\n"
+"    "
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymize.wizard,state:0
+msgid "Unstable"
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymization.history,state:0
+msgid "Exception occured"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.actions.act_window,name:anonymization.action_ir_model_fields_anonymization_history_tree
+#: view:ir.model.fields.anonymization.history:0
+#: model:ir.ui.menu,name:anonymization.menu_administration_anonymization_history
+msgid "Anonymization History"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.model,name:anonymization.model_ir_model_fields_anonymization_history
+msgid "ir.model.fields.anonymization.history"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.actions.act_window,name:anonymization.action_ir_model_fields_anonymize_wizard
+#: view:ir.model.fields.anonymize.wizard:0
+msgid "Anonymize Database"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymize.wizard,name:0
+msgid "File Name"
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymization.history,direction:0
+msgid "anonymized -> clear"
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymization.history,state:0
+msgid "Started"
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymization.history,state:0
+msgid "Done"
+msgstr ""
+
+#. module: anonymization
+#: view:ir.model.fields.anonymization.history:0
+#: field:ir.model.fields.anonymization.history,msg:0
+#: field:ir.model.fields.anonymize.wizard,msg:0
+msgid "Message"
+msgstr ""
+
diff --git a/addons/anonymization/i18n/fr.po b/addons/anonymization/i18n/fr.po
new file mode 100644 (file)
index 0000000..2dd0e73
--- /dev/null
@@ -0,0 +1,201 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#      * anonymization
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.0.0-rc1\n"
+"Report-Msgid-Bugs-To: support@openerp.com\n"
+"POT-Creation-Date: 2010-12-10 14:48:44+0000\n"
+"PO-Revision-Date: 2010-12-10 15:49+0100\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: \n"
+
+#. module: anonymization
+#: model:ir.model,name:anonymization.model_ir_model_fields_anonymize_wizard
+msgid "ir.model.fields.anonymize.wizard"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymization,field_id:0
+msgid "Field"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymization.history,state:0
+#: field:ir.model.fields.anonymize.wizard,state:0
+msgid "State"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymize.wizard,file_import:0
+msgid "Import"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.model,name:anonymization.model_ir_model_fields_anonymization
+msgid "ir.model.fields.anonymization"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.module.module,shortdesc:anonymization.module_meta_information
+msgid "Database anonymization module"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymization.history,direction:0
+msgid "Direction"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.actions.act_window,name:anonymization.action_ir_model_fields_anonymization_tree
+#: model:ir.ui.menu,name:anonymization.menu_administration_anonymization_fields
+msgid "Anonymized Fields"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.ui.menu,name:anonymization.menu_administration_anonymization
+msgid "Database anonymization"
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymization.history,direction:0
+msgid "clear -> anonymized"
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymization,state:0
+#: selection:ir.model.fields.anonymize.wizard,state:0
+msgid "Anonymized"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymization,state:0
+msgid "unknown"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymization,model_id:0
+msgid "Object"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymization.history,filepath:0
+msgid "File path"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymization.history,date:0
+msgid "Date"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymize.wizard,file_export:0
+msgid "Export"
+msgstr ""
+
+#. module: anonymization
+#: view:ir.model.fields.anonymize.wizard:0
+msgid "Reverse the Database Anonymization"
+msgstr ""
+
+#. module: anonymization
+#: view:ir.model.fields.anonymize.wizard:0
+msgid "Database Anonymization"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.ui.menu,name:anonymization.menu_administration_anonymization_wizard
+msgid "Anonymize database"
+msgstr ""
+
+#. module: anonymization
+#: view:ir.model.fields.anonymization.history:0
+#: field:ir.model.fields.anonymization.history,field_ids:0
+msgid "Fields"
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymization,state:0
+#: selection:ir.model.fields.anonymize.wizard,state:0
+msgid "Clear"
+msgstr ""
+
+#. module: anonymization
+#: view:ir.model.fields.anonymize.wizard:0
+#: field:ir.model.fields.anonymize.wizard,summary:0
+msgid "Summary"
+msgstr ""
+
+#. module: anonymization
+#: view:ir.model.fields.anonymization:0
+msgid "Anonymized Field"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.module.module,description:anonymization.module_meta_information
+msgid ""
+"\n"
+"This module allows you to anonymize a database.\n"
+"    "
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymize.wizard,state:0
+msgid "Unstable"
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymization.history,state:0
+msgid "Exception occured"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.actions.act_window,name:anonymization.action_ir_model_fields_anonymization_history_tree
+#: view:ir.model.fields.anonymization.history:0
+#: model:ir.ui.menu,name:anonymization.menu_administration_anonymization_history
+msgid "Anonymization History"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.model,name:anonymization.model_ir_model_fields_anonymization_history
+msgid "ir.model.fields.anonymization.history"
+msgstr ""
+
+#. module: anonymization
+#: model:ir.actions.act_window,name:anonymization.action_ir_model_fields_anonymize_wizard
+#: view:ir.model.fields.anonymize.wizard:0
+msgid "Anonymize Database"
+msgstr ""
+
+#. module: anonymization
+#: field:ir.model.fields.anonymize.wizard,name:0
+msgid "File Name"
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymization.history,direction:0
+msgid "anonymized -> clear"
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymization.history,state:0
+msgid "Started"
+msgstr ""
+
+#. module: anonymization
+#: selection:ir.model.fields.anonymization.history,state:0
+msgid "Done"
+msgstr ""
+
+#. module: anonymization
+#: view:ir.model.fields.anonymization.history:0
+#: field:ir.model.fields.anonymization.history,msg:0
+#: field:ir.model.fields.anonymize.wizard,msg:0
+msgid "Message"
+msgstr ""
+
diff --git a/addons/anonymization/ir.model.fields.anonymization.csv b/addons/anonymization/ir.model.fields.anonymization.csv
new file mode 100644 (file)
index 0000000..7f04f81
--- /dev/null
@@ -0,0 +1,35 @@
+id,model_name,field_name
+anonymization_field_res_partner_name,res.partner,name
+anonymization_field_res_partner_code,res.partner,ref
+anonymization_field_res_partner_address_name,res.partner.address,name
+anonymization_field_res_partner_address_city,res.partner.address,city
+anonymization_field_res_partner_address_street,res.partner.address,street
+anonymization_field_res_partner_address_street2,res.partner.address,street2
+anonymization_field_res_partner_address_zip,res.partner.address,zip
+anonymization_field_res_partner_address_phone,res.partner.address,phone
+anonymization_field_res_partner_address_fax,res.partner.address,fax
+anonymization_field_res_partner_address_mobile,res.partner.address,mobile
+anonymization_field_res_partner_address_email,res.partner.address,email
+anonymization_field_account_invoice_amount_untaxed,account.invoice,amount_untaxed
+anonymization_field_account_invoice_amount_tax,account.invoice,amount_tax
+anonymization_field_account_invoice_amount_total,account.invoice,amount_total
+anonymization_field_account_invoice_check_total,account.invoice,check_total
+anonymization_field_account_invoice_residual,account.invoice,residual
+anonymization_field_account_invoice_line_price_unit,account.invoice.line,price_unit
+anonymization_field_account_invoice_line_price_subtotal,account.invoice.line,price_subtotal
+anonymization_field_account_move_line_debit,account.move.line,debit
+anonymization_field_account_move_line_credit,account.move.line,credit
+anonymization_field_account_move_line_tax_amount,account.move.line,tax_amount
+anonymization_field_account_move_line_amount_currency,account.move.line,amount_currency
+anonymization_field_account_move_line_amount_taxed,account.move.line,amount_taxed
+anonymization_field_sale_order_amount_tax,sale.order,amount_tax
+anonymization_field_sale_order_amount_total,sale.order,amount_total
+anonymization_field_sale_order_amount_untaxed,sale.order,amount_untaxed
+anonymization_field_sale_order_line_price_unit,sale.order.line,price_unit
+anonymization_field_sale_order_line_discount,sale.order.line,discount
+anonymization_field_purchase_order_amount_total,purchase.order,amount_total
+anonymization_field_purchase_order_amount_tax,purchase.order,amount_tax
+anonymization_field_purchase_order_amount_untaxed,purchase.order,amount_untaxed
+anonymization_field_purchase_order_line_price_unit,purchase.order.line,price_unit
+anonymization_field_account_invoice_tax_amount,account.invoice.tax,amount
+anonymization_field_account_invoice_tax_base,account.invoice.tax,base
diff --git a/addons/anonymization/security/ir.model.access.csv b/addons/anonymization/security/ir.model.access.csv
new file mode 100644 (file)
index 0000000..b558227
--- /dev/null
@@ -0,0 +1,7 @@
+"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
+"access_ir_model_fields_anonymization_group_system","ir_model_fields_anonymization group_user","model_ir_model_fields_anonymization","base.group_system",1,1,1,1
+"access_ir_model_fields_anonymization_user","ir_model_fields_anonymization user","model_ir_model_fields_anonymization",,1,0,0,0
+"access_ir_model_fields_anonymization_history_group_system","ir_model_fields_anonymization_history group_user","model_ir_model_fields_anonymization_history","base.group_system",1,1,1,1
+"access_ir_model_fields_anonymization_history_user","ir_model_fields_anonymization_history user","model_ir_model_fields_anonymization_history",,1,0,0,0
+"access_ir_model_fields_anonymize_wizard_group_system","ir_model_fields_anonymize_wizard group_user","model_ir_model_fields_anonymize_wizard","base.group_system",1,1,1,1
+"access_ir_model_fields_anonymize_wizard_user","ir_model_fields_anonymize_wizard user","model_ir_model_fields_anonymize_wizard",,1,0,0,0