From 991eee798ef14a029503afe7f2c09e03c314e205 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 10 Sep 2015 16:50:16 +0200 Subject: [PATCH 01/42] Create empty addons --- partner_revision/README.rst | 15 +++++++++++++++ partner_revision/__init__.py | 1 + partner_revision/__openerp__.py | 34 +++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 partner_revision/README.rst create mode 100644 partner_revision/__init__.py create mode 100644 partner_revision/__openerp__.py diff --git a/partner_revision/README.rst b/partner_revision/README.rst new file mode 100644 index 000000000..9339e1ae2 --- /dev/null +++ b/partner_revision/README.rst @@ -0,0 +1,15 @@ +Partner Revisions +================= + +* Modèle de données et vues : création de la base du module ; création des + modèles sans logique et de leurs vues ; création d’un champ fonction pour + l’affichage des valeurs afin de pouvoir afficher les champs relation comme il + faut (“name” de la relation au lieu de l’id.) → le champ “backend_id” devra + être un champ de type “reference” car il peut être lié à plusieurs types de + backend différents ; +* Logique : gestion des états des entrées du journal ; application manuelle des + changements ; application automatique des entrées selon la configuration du + comportement par défaut ; ne pas appliquer automatiquement de changements si + une entrée plus récente existe (dans le cas de création d’entrée antidatée) ; + création d’entrée “validée” lors de saisie manuelle sur les partenaires ; + écriture de tests unitaires ; diff --git a/partner_revision/__init__.py b/partner_revision/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/partner_revision/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/partner_revision/__openerp__.py b/partner_revision/__openerp__.py new file mode 100644 index 000000000..dab47e0d4 --- /dev/null +++ b/partner_revision/__openerp__.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# +# +# Authors: Guewen Baconnier +# Copyright 2015 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# + +{'name': 'Partner Revisions', + 'version': '1.0', + 'author': 'Camptocamp', + 'license': 'AGPL-3', + 'category': 'Sales Management', + 'depends': ['base', + ], + 'website': 'http://www.camptocamp.com', + 'data': [], + 'test': [], + 'installable': True, + 'auto_install': False, + } From 4f415f67e6bc0961464d8da174fd2385d5b23498 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 15 Sep 2015 17:13:30 +0200 Subject: [PATCH 02/42] Create a partner revision on writes --- partner_revision/__init__.py | 2 + partner_revision/__openerp__.py | 4 +- partner_revision/models/__init__.py | 5 ++ partner_revision/models/res_partner.py | 79 +++++++++++++++++++ .../models/res_partner_revision.py | 63 +++++++++++++++ partner_revision/models/revision_behavior.py | 60 ++++++++++++++ .../views/res_partner_revision_views.xml | 67 ++++++++++++++++ .../views/revision_behavior_views.xml | 58 ++++++++++++++ 8 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 partner_revision/models/__init__.py create mode 100644 partner_revision/models/res_partner.py create mode 100644 partner_revision/models/res_partner_revision.py create mode 100644 partner_revision/models/revision_behavior.py create mode 100644 partner_revision/views/res_partner_revision_views.xml create mode 100644 partner_revision/views/revision_behavior_views.xml diff --git a/partner_revision/__init__.py b/partner_revision/__init__.py index 40a96afc6..cde864bae 100644 --- a/partner_revision/__init__.py +++ b/partner_revision/__init__.py @@ -1 +1,3 @@ # -*- coding: utf-8 -*- + +from . import models diff --git a/partner_revision/__openerp__.py b/partner_revision/__openerp__.py index dab47e0d4..0494543ce 100644 --- a/partner_revision/__openerp__.py +++ b/partner_revision/__openerp__.py @@ -27,7 +27,9 @@ 'depends': ['base', ], 'website': 'http://www.camptocamp.com', - 'data': [], + 'data': ['views/res_partner_revision_views.xml', + 'views/revision_behavior_views.xml', + ], 'test': [], 'installable': True, 'auto_install': False, diff --git a/partner_revision/models/__init__.py b/partner_revision/models/__init__.py new file mode 100644 index 000000000..9a890dbb1 --- /dev/null +++ b/partner_revision/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from . import res_partner +from . import res_partner_revision +from . import revision_behavior diff --git a/partner_revision/models/res_partner.py b/partner_revision/models/res_partner.py new file mode 100644 index 000000000..c3ca31e3d --- /dev/null +++ b/partner_revision/models/res_partner.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# +# +# Authors: Guewen Baconnier +# Copyright 2015 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# + +from openerp import models, fields, api + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + @api.multi + def write(self, values): + for record in self: + values = record._add_revision(values) + return super(ResPartner, self).write(values) + + @api.multi + def _add_revision(self, values): + """ Add a revision on a partner + + :param values: the values being written on the partner + :type values: dict + + :returns: dict of values that should be wrote on the partner + (fields with a 'Validate' or 'Never' rule are excluded) + + """ + self.ensure_one() + write_values = values.copy() + changes = [] + rules = self.env['revision.behavior'].get_rules(self._model._name) + for field in values: + rule = rules.get(field) + if not rule: + continue + if field in values: + # TODO: if a change is done manually, values are always + # written but we create 'done' changes + if self[field] == values[field]: + # TODO handle relations, types + continue + change = { + 'current_value': self[field], + 'new_value': values[field], + 'field_id': rule.field_id.id, + } + if rule.default_behavior == 'auto': + change['state'] = 'done' + elif rule.default_behavior == 'validate': + change['state'] = 'draft' + write_values.pop(field) # change to apply manually + elif rule.default_behavior == 'never': + change['state'] = 'cancel' + write_values.pop(field) # change never applied + changes.append(change) + if changes: + self.env['res.partner.revision'].create({ + 'partner_id': self.id, + 'change_ids': [(0, 0, vals) for vals in changes], + 'date': fields.Datetime.now(), + }) + return write_values diff --git a/partner_revision/models/res_partner_revision.py b/partner_revision/models/res_partner_revision.py new file mode 100644 index 000000000..b8e0ebe15 --- /dev/null +++ b/partner_revision/models/res_partner_revision.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# +# +# Authors: Guewen Baconnier +# Copyright 2015 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# + +from openerp import models, fields + + +class ResPartnerRevision(models.Model): + _name = 'res.partner.revision' + _description = 'Partner Revision' + _order = 'date desc' + _rec_name = 'date' + + partner_id = fields.Many2one(comodel_name='res.partner', + string='Partner', + required=True) + change_ids = fields.One2many(comodel_name='res.partner.revision.change', + inverse_name='revision_id', + string='Changes') + date = fields.Datetime() + note = fields.Text() + + +class ResPartnerRevisionChange(models.Model): + _name = 'res.partner.revision.change' + _description = 'Partner Revision Change' + _rec_name = 'new_value' + + revision_id = fields.Many2one(comodel_name='res.partner.revision', + required=True, + string='Revision', + ondelete='cascade') + field_id = fields.Many2one(comodel_name='ir.model.fields', + string='Field', + required=True) + # TODO: different types of fields + current_value = fields.Char('Current') + new_value = fields.Char('New') + state = fields.Selection( + selection=[('draft', 'Waiting'), + ('done', 'Accepted'), + ('cancel', 'Refused'), + ], + required=True, + default='draft', + ) diff --git a/partner_revision/models/revision_behavior.py b/partner_revision/models/revision_behavior.py new file mode 100644 index 000000000..a125e6ed8 --- /dev/null +++ b/partner_revision/models/revision_behavior.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# +# +# Authors: Guewen Baconnier +# Copyright 2015 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# + +from openerp import models, fields, api + + +class RevisionBehavior(models.Model): + _name = 'revision.behavior' + _description = 'Revision Behavior' + _rec_name = 'field_id' + + model_id = fields.Many2one(comodel_name='ir.model', + string='Model', + ondelete='cascade', + default=lambda self: self._default_model_id()) + field_id = fields.Many2one(comodel_name='ir.model.fields', + string='Field', + ondelete='cascade') + default_behavior = fields.Selection( + selection='_selection_default_behavior', + string='Default Behavior', + help="Auto: always apply a change.\n" + "Validate: manually applied by an administrator.\n" + "Never: change never applied.", + ) + + @api.model + def _default_model_id(self): + return self.env.ref('base.model_res_partner').id + + @api.model + def _selection_default_behavior(self): + return [('auto', 'Auto'), + ('validate', 'Validate'), + ('never', 'Never'), + ] + + # TODO: cache + @api.model + def get_rules(self, model_name): + rules = self.search([('model_id', '=', model_name)]) + return {rule.field_id.name: rule for rule in rules} diff --git a/partner_revision/views/res_partner_revision_views.xml b/partner_revision/views/res_partner_revision_views.xml new file mode 100644 index 000000000..3b4c233d1 --- /dev/null +++ b/partner_revision/views/res_partner_revision_views.xml @@ -0,0 +1,67 @@ + + + + + res.partner.revision.tree + res.partner.revision + + + + + + + + + + res.partner.revision.form + res.partner.revision + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+ + + res.partner.revision.search + res.partner.revision + + + + + + + + + Partner Revision + ir.actions.act_window + res.partner.revision + form + tree,form + + + + +
+
diff --git a/partner_revision/views/revision_behavior_views.xml b/partner_revision/views/revision_behavior_views.xml new file mode 100644 index 000000000..78553125d --- /dev/null +++ b/partner_revision/views/revision_behavior_views.xml @@ -0,0 +1,58 @@ + + + + + revision.behavior.tree + revision.behavior + + + + + + + + + + + revision.behavior.form + revision.behavior + +
+ + + + + + + +
+
+
+ + + revision.behavior.search + revision.behavior + + + + + + + + + + + Revision Behavior + ir.actions.act_window + revision.behavior + form + tree,form + + + + +
+
From a67c40d77e7e4292ad600aa0cfccc069e463917f Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 16 Sep 2015 11:29:55 +0200 Subject: [PATCH 03/42] Add first tests on revisions --- partner_revision/models/res_partner.py | 13 +- partner_revision/tests/__init__.py | 3 + partner_revision/tests/common.py | 64 +++++++++ partner_revision/tests/test_revision_flow.py | 137 +++++++++++++++++++ 4 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 partner_revision/tests/__init__.py create mode 100644 partner_revision/tests/common.py create mode 100644 partner_revision/tests/test_revision_flow.py diff --git a/partner_revision/models/res_partner.py b/partner_revision/models/res_partner.py index c3ca31e3d..0e415b05e 100644 --- a/partner_revision/models/res_partner.py +++ b/partner_revision/models/res_partner.py @@ -35,6 +35,12 @@ class ResPartner(models.Model): def _add_revision(self, values): """ Add a revision on a partner + By default, when a partner is modified by a user or by the + system, the changes are applied and a validated revision is + created. Callers which want to delegate the write of some + fields to the revision must explicitly ask for it by providing a + key ``__revision_rules`` in the environment's context. + :param values: the values being written on the partner :type values: dict @@ -51,8 +57,6 @@ class ResPartner(models.Model): if not rule: continue if field in values: - # TODO: if a change is done manually, values are always - # written but we create 'done' changes if self[field] == values[field]: # TODO handle relations, types continue @@ -61,7 +65,10 @@ class ResPartner(models.Model): 'new_value': values[field], 'field_id': rule.field_id.id, } - if rule.default_behavior == 'auto': + if not self.env.context.get('__revision_rules'): + # by default always write on partner + change['state'] = 'done' + elif rule.default_behavior == 'auto': change['state'] = 'done' elif rule.default_behavior == 'validate': change['state'] = 'draft' diff --git a/partner_revision/tests/__init__.py b/partner_revision/tests/__init__.py new file mode 100644 index 000000000..1f4968042 --- /dev/null +++ b/partner_revision/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import test_revision_flow diff --git a/partner_revision/tests/common.py b/partner_revision/tests/common.py new file mode 100644 index 000000000..f42a0240d --- /dev/null +++ b/partner_revision/tests/common.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# +# +# Authors: Guewen Baconnier +# Copyright 2015 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# + + +class RevisionMixin(object): + + def assert_revision(self, partner, expected_changes): + """ Check if a revision has been created according to expected values + + The partner should have no prior revision than the one created in the + test (so it has exactly 1 revision). + + The expected changes are tuples with (field, current_value, + new_value, state) + + :param partner: record of partner having a revision + :param expected_changes: contains tuples with the changes + :type expected_changes: list(tuple)) + """ + revision = self.env['res.partner.revision'].search( + [('partner_id', '=', partner.id)], + ) + self.assertEqual(len(revision), 1, + "1 revision expected, got %s" % (revision,)) + changes = revision.change_ids + missing = [] + for expected_change in expected_changes: + for change in changes: + if (change.field_id, change.current_value, change.new_value, + change.state) == expected_change: + changes -= change + break + else: + missing.append(expected_change) + message = u'' + for field, current_value, new_value, state in missing: + message += ("- field: '%s', current_value: '%s', " + "new_value: '%s', state: '%s'\n" % + (field.name, current_value, new_value, state)) + for change in changes: + message += ("+ field: '%s', current_value: '%s', " + "new_value: '%s', state: '%s'\n" % + (change.field_id.name, change.current_value, + change.new_value, change.state)) + if message: + raise AssertionError('Changes do not match\n\n:%s' % message) diff --git a/partner_revision/tests/test_revision_flow.py b/partner_revision/tests/test_revision_flow.py new file mode 100644 index 000000000..ecdb47569 --- /dev/null +++ b/partner_revision/tests/test_revision_flow.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# +# +# Authors: Guewen Baconnier +# Copyright 2015 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# + + +from openerp.tests import common +from .common import RevisionMixin + + +class TestRevisionFlow(RevisionMixin, common.TransactionCase): + """ Check how revision are generated and applied based on the rules. + + We do not really care about the types of the fields in this test suite, + but we have to ensure that the general revision flows work as expected. + """ + + def _setup_behavior(self): + RevisionBehavior = self.env['revision.behavior'] + partner_model_id = self.env.ref('base.model_res_partner').id + self.field_name = self.env.ref('base.field_res_partner_name') + self.field_street = self.env.ref('base.field_res_partner_street') + self.field_street2 = self.env.ref('base.field_res_partner_street2') + RevisionBehavior.create({ + 'model_id': partner_model_id, + 'field_id': self.field_name.id, + 'default_behavior': 'auto', + }) + RevisionBehavior.create({ + 'model_id': partner_model_id, + 'field_id': self.field_street.id, + 'default_behavior': 'validate', + }) + RevisionBehavior.create({ + 'model_id': partner_model_id, + 'field_id': self.field_street2.id, + 'default_behavior': 'never', + }) + + def setUp(self): + super(TestRevisionFlow, self).setUp() + self._setup_behavior() + self.partner = self.env['res.partner'].create({ + 'name': 'X', + 'street': 'street X', + 'street2': 'street2 X', + }) + + def assert_revision(self, partner, expected_changes): + """ Check if a revision has been created according to expected values + + The partner should have no prior revision than the one created in the + test (so it has exactly 1 revision). + + The expected changes are tuples with (field, current_value, + new_value, state) + + :param partner: record of partner having a revision + :param expected_changes: contains tuples with the changes + :type expected_changes: list(tuple)) + """ + revision = self.env['res.partner.revision'].search( + [('partner_id', '=', partner.id)], + ) + self.assertEqual(len(revision), 1, + "1 revision expected, got %s" % (revision,)) + changes = revision.change_ids + missing = [] + for expected_change in expected_changes: + for change in changes: + if (change.field_id, change.current_value, change.new_value, + change.state) == expected_change: + changes -= change + break + else: + missing.append(expected_change) + message = u'' + for field, current_value, new_value, state in missing: + message += ("- field: '%s', current_value: '%s', " + "new_value: '%s', state: '%s'\n" % + (field.name, current_value, new_value, state)) + for change in changes: + message += ("+ field: '%s', current_value: '%s', " + "new_value: '%s', state: '%s'\n" % + (change.field_id.name, change.current_value, + change.new_value, change.state)) + if message: + raise AssertionError('Changes do not match\n\n:%s' % message) + + def test_new_revision(self): + """ Add a new revision on a partner """ + self.partner.with_context(__revision_rules=True).write({ + 'name': 'Y', + 'street': 'street Y', + 'street2': 'street2 Y', + }) + self.assert_revision( + self.partner, + [(self.field_name, 'X', 'Y', 'done'), + (self.field_street, 'street X', 'street Y', 'draft'), + (self.field_street2, 'street2 X', 'street2 Y', 'cancel'), + ], + ) + + def test_manual_edition(self): + """ A manual edition of a partner should always be applied + + But should create a 'done' revision + """ + self.partner.write({ + 'name': 'Y', + 'street': 'street Y', + 'street2': 'street2 Y', + }) + self.assert_revision( + self.partner, + [(self.field_name, 'X', 'Y', 'done'), + (self.field_street, 'street X', 'street Y', 'done'), + (self.field_street2, 'street2 X', 'street2 Y', 'done'), + ], + ) From ea818e9cea9e2a05b7ca7e6b738b22e320d1696f Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 16 Sep 2015 15:14:28 +0200 Subject: [PATCH 04/42] Apply changesets --- .../models/res_partner_revision.py | 16 ++- partner_revision/tests/common.py | 21 ++++ partner_revision/tests/test_revision_flow.py | 119 ++++++++++++------ 3 files changed, 113 insertions(+), 43 deletions(-) diff --git a/partner_revision/models/res_partner_revision.py b/partner_revision/models/res_partner_revision.py index b8e0ebe15..c8665d6d8 100644 --- a/partner_revision/models/res_partner_revision.py +++ b/partner_revision/models/res_partner_revision.py @@ -19,7 +19,7 @@ # # -from openerp import models, fields +from openerp import models, fields, api class ResPartnerRevision(models.Model): @@ -34,9 +34,13 @@ class ResPartnerRevision(models.Model): change_ids = fields.One2many(comodel_name='res.partner.revision.change', inverse_name='revision_id', string='Changes') - date = fields.Datetime() + date = fields.Datetime(default=fields.Datetime.now) note = fields.Text() + @api.multi + def apply(self): + self.mapped('change_ids').apply() + class ResPartnerRevisionChange(models.Model): _name = 'res.partner.revision.change' @@ -61,3 +65,11 @@ class ResPartnerRevisionChange(models.Model): required=True, default='draft', ) + + @api.multi + def apply(self): + for change in self: + if change.state in ('cancel', 'done'): + continue + partner = change.revision_id.partner_id + partner.write({change.field_id.name: change.new_value}) diff --git a/partner_revision/tests/common.py b/partner_revision/tests/common.py index f42a0240d..a7ed1d38e 100644 --- a/partner_revision/tests/common.py +++ b/partner_revision/tests/common.py @@ -62,3 +62,24 @@ class RevisionMixin(object): change.new_value, change.state)) if message: raise AssertionError('Changes do not match\n\n:%s' % message) + + def _create_revision(self, partner, changes): + """ Create a revision and its associated changes + + :param partner: 'res.partner' record + :param changes: list of changes [(field, new value, state)] + :returns: 'res.partner.revision' record + """ + change_values = [ + (0, 0, { + 'field_id': field.id, + 'current_value': partner[field.name], + 'new_value': value, + 'state': state, + }) for field, value, state in changes + ] + values = { + 'partner_id': partner.id, + 'change_ids': change_values, + } + return self.env['res.partner.revision'].create(values) diff --git a/partner_revision/tests/test_revision_flow.py b/partner_revision/tests/test_revision_flow.py index ecdb47569..ffd79a2c6 100644 --- a/partner_revision/tests/test_revision_flow.py +++ b/partner_revision/tests/test_revision_flow.py @@ -62,47 +62,6 @@ class TestRevisionFlow(RevisionMixin, common.TransactionCase): 'street2': 'street2 X', }) - def assert_revision(self, partner, expected_changes): - """ Check if a revision has been created according to expected values - - The partner should have no prior revision than the one created in the - test (so it has exactly 1 revision). - - The expected changes are tuples with (field, current_value, - new_value, state) - - :param partner: record of partner having a revision - :param expected_changes: contains tuples with the changes - :type expected_changes: list(tuple)) - """ - revision = self.env['res.partner.revision'].search( - [('partner_id', '=', partner.id)], - ) - self.assertEqual(len(revision), 1, - "1 revision expected, got %s" % (revision,)) - changes = revision.change_ids - missing = [] - for expected_change in expected_changes: - for change in changes: - if (change.field_id, change.current_value, change.new_value, - change.state) == expected_change: - changes -= change - break - else: - missing.append(expected_change) - message = u'' - for field, current_value, new_value, state in missing: - message += ("- field: '%s', current_value: '%s', " - "new_value: '%s', state: '%s'\n" % - (field.name, current_value, new_value, state)) - for change in changes: - message += ("+ field: '%s', current_value: '%s', " - "new_value: '%s', state: '%s'\n" % - (change.field_id.name, change.current_value, - change.new_value, change.state)) - if message: - raise AssertionError('Changes do not match\n\n:%s' % message) - def test_new_revision(self): """ Add a new revision on a partner """ self.partner.with_context(__revision_rules=True).write({ @@ -117,6 +76,19 @@ class TestRevisionFlow(RevisionMixin, common.TransactionCase): (self.field_street2, 'street2 X', 'street2 Y', 'cancel'), ], ) + self.assertEqual(self.partner.name, 'Y') + self.assertEqual(self.partner.street, 'street X') + self.assertEqual(self.partner.street2, 'street2 X') + + def test_new_revision_empty_value(self): + """ Create a revision change that empty a value """ + self.partner.with_context(__revision_rules=True).write({ + 'street': False, + }) + self.assert_revision( + self.partner, + [(self.field_street, 'street X', False, 'draft')] + ) def test_manual_edition(self): """ A manual edition of a partner should always be applied @@ -135,3 +107,68 @@ class TestRevisionFlow(RevisionMixin, common.TransactionCase): (self.field_street2, 'street2 X', 'street2 Y', 'done'), ], ) + self.assertEqual(self.partner.name, 'Y') + self.assertEqual(self.partner.street, 'street Y') + self.assertEqual(self.partner.street2, 'street2 Y') + + def test_apply_change(self): + """ Apply a revision change on a partner """ + changes = [ + (self.field_name, 'Y', 'draft'), + ] + revision = self._create_revision(self.partner, changes) + revision.change_ids.apply() + self.assertEqual(self.partner.name, 'Y') + + def test_apply_done_change(self): + """ Done changes do not apply (already applied) """ + changes = [ + (self.field_name, 'Y', 'done'), + ] + revision = self._create_revision(self.partner, changes) + revision.change_ids.apply() + self.assertEqual(self.partner.name, 'X') + + def test_apply_cancel_change(self): + """ Cancel changes do not apply """ + changes = [ + (self.field_name, 'Y', 'cancel'), + ] + revision = self._create_revision(self.partner, changes) + revision.change_ids.apply() + self.assertEqual(self.partner.name, 'X') + + def test_apply_empty_value(self): + """ Apply a change that empty a value """ + changes = [ + (self.field_street, False, 'draft'), + ] + revision = self._create_revision(self.partner, changes) + revision.change_ids.apply() + self.assertFalse(self.partner.street) + + def test_apply_change_loop(self): + """ Test @api.multi on the changes """ + changes = [ + (self.field_name, 'Y', 'draft'), + (self.field_street, 'street Y', 'draft'), + (self.field_street2, 'street2 Y', 'draft'), + ] + revision = self._create_revision(self.partner, changes) + revision.change_ids.apply() + self.assertEqual(self.partner.name, 'Y') + self.assertEqual(self.partner.street, 'street Y') + self.assertEqual(self.partner.street2, 'street2 Y') + + def test_apply(self): + """ Apply a full revision on a partner """ + changes = [ + (self.field_name, 'Y', 'draft'), + (self.field_street, 'street Y', 'draft'), + (self.field_street2, 'street2 Y', 'draft'), + ] + revision = self._create_revision(self.partner, changes) + revision.apply() + self.assertEqual(self.partner.name, 'Y') + self.assertEqual(self.partner.street, 'street Y') + self.assertEqual(self.partner.street2, 'street2 Y') From c7d550a192c42729bb16f31b76b6e95de2e5af1b Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 16 Sep 2015 15:25:17 +0200 Subject: [PATCH 05/42] Document TestRevisionFlow testsuite --- partner_revision/tests/test_revision_flow.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/partner_revision/tests/test_revision_flow.py b/partner_revision/tests/test_revision_flow.py index ffd79a2c6..3fedcc678 100644 --- a/partner_revision/tests/test_revision_flow.py +++ b/partner_revision/tests/test_revision_flow.py @@ -27,8 +27,16 @@ from .common import RevisionMixin class TestRevisionFlow(RevisionMixin, common.TransactionCase): """ Check how revision are generated and applied based on the rules. - We do not really care about the types of the fields in this test suite, - but we have to ensure that the general revision flows work as expected. + We do not really care about the types of the fields in this test + suite, so we only use 'char' fields. We have to ensure that the + general revision flows work as expected, that is: + + * create a 'done' revision when a manual/system write is made on partner + * create a revision according to the revision rules when the key + '__revision_rules' is passed in the context + * apply a revision change writes the value on the partner + * apply a whole revision writes all the changes' values on the partner + * changes in state 'cancel' or 'done' do not write on the partner """ def _setup_behavior(self): From 16e8f45fa254cc4fbd7704c2ac79edb033ba233b Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 16 Sep 2015 16:53:56 +0200 Subject: [PATCH 06/42] Testing different types of fields (with failures) --- partner_revision/tests/__init__.py | 1 + .../tests/test_revision_field_type.py | 160 ++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 partner_revision/tests/test_revision_field_type.py diff --git a/partner_revision/tests/__init__.py b/partner_revision/tests/__init__.py index 1f4968042..b7aa994f5 100644 --- a/partner_revision/tests/__init__.py +++ b/partner_revision/tests/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- from . import test_revision_flow +from . import test_revision_field_type diff --git a/partner_revision/tests/test_revision_field_type.py b/partner_revision/tests/test_revision_field_type.py new file mode 100644 index 000000000..7f88ec2e0 --- /dev/null +++ b/partner_revision/tests/test_revision_field_type.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +# +# +# Authors: Guewen Baconnier +# Copyright 2015 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# + +from openerp.tests import common +from .common import RevisionMixin + + +class TestRevisionFieldType(RevisionMixin, common.TransactionCase): + """ Check how revision are generated and applied based on the rules. + + We do not really care about the types of the fields in this test suite, + but we have to ensure that the general revision flows work as expected. + """ + + def _setup_behavior(self): + RevisionBehavior = self.env['revision.behavior'] + partner_model_id = self.env.ref('base.model_res_partner').id + fields = (('char', 'ref'), + ('text', 'comment'), + ('boolean', 'customer'), + ('date', 'date'), + ('integer', 'color'), + ('float', 'credit_limit'), + ('selection', 'type'), + ('many2one', 'country_id'), + ('many2many', 'category_id'), + ('one2many', 'user_ids'), + ('binary', 'image'), + ) + for field_type, field in fields: + attr_name = 'field_%s' % field_type + field_record = self.env.ref('base.field_res_partner_%s' % field) + # set attribute such as 'self.field_char' is a + # ir.model.fields record of the field res_partner.ref + setattr(self, attr_name, field_record) + RevisionBehavior.create({ + 'model_id': partner_model_id, + 'field_id': field_record.id, + 'default_behavior': 'validate', + }) + + def setUp(self): + super(TestRevisionFieldType, self).setUp() + self._setup_behavior() + self.partner = self.env['res.partner'].create({ + 'name': 'Original Name', + 'street': 'Original Street', + }) + + def test_char(self): + """ Apply a change on a Char field """ + changes = [(self.field_char, 'New Ref', 'draft')] + revision = self._create_revision(self.partner, changes) + revision.change_ids.apply() + self.assertEqual(self.partner[self.field_char.name], 'New Ref') + + def test_text(self): + """ Apply a change on a Text field """ + changes = [(self.field_text, 'New comment\non 2 lines', 'draft')] + revision = self._create_revision(self.partner, changes) + revision.change_ids.apply() + self.assertEqual(self.partner[self.field_text.name], + 'New comment\non 2 lines') + + def test_boolean(self): + """ Apply a change on a Boolean field """ + # ensure the revision has to change the value + self.partner.write({self.field_boolean.name: False}) + + changes = [(self.field_boolean, True, 'draft')] + revision = self._create_revision(self.partner, changes) + revision.change_ids.apply() + self.assertEqual(self.partner[self.field_boolean.name], True) + + changes = [(self.field_boolean, False, 'draft')] + revision = self._create_revision(self.partner, changes) + revision.change_ids.apply() + self.assertEqual(self.partner[self.field_boolean.name], False) + + def test_date(self): + """ Apply a change on a Date field """ + changes = [(self.field_date, '2015-09-15', 'draft')] + revision = self._create_revision(self.partner, changes) + revision.change_ids.apply() + self.assertAlmostEqual(self.partner[self.field_date.name], + '2015-09-15') + + def test_integer(self): + """ Apply a change on a Integer field """ + changes = [(self.field_integer, 42, 'draft')] + revision = self._create_revision(self.partner, changes) + revision.change_ids.apply() + self.assertAlmostEqual(self.partner[self.field_integer.name], 42) + + def test_float(self): + """ Apply a change on a Float field """ + changes = [(self.field_float, 52.47, 'draft')] + revision = self._create_revision(self.partner, changes) + revision.change_ids.apply() + self.assertAlmostEqual(self.partner[self.field_float.name], 52.47) + + def test_selection(self): + """ Apply a change on a Selection field """ + changes = [(self.field_selection, 'delivery', 'draft')] + revision = self._create_revision(self.partner, changes) + revision.change_ids.apply() + self.assertAlmostEqual(self.partner[self.field_selection.name], + 'delivery') + + def test_many2one(self): + """ Apply a change on a Many2one field """ + changes = [(self.field_many2one, + self.env.ref('base.ch').id, + 'draft')] + revision = self._create_revision(self.partner, changes) + revision.change_ids.apply() + self.assertAlmostEqual(self.partner[self.field_many2one.name], + self.env.ref('base.ch').id) + + def test_many2many(self): + """ Apply a change on a Many2many field """ + changes = [(self.field_many2many, + self.env.ref('base.ch').id, + 'draft')] + with self.assertRaises(NotImplementedError): + self._create_revision(self.partner, changes) + + def test_one2many(self): + """ Apply a change on a One2many field """ + changes = [(self.field_one2many, + [self.env.ref('base.user_root').id, + self.env.ref('base.user_demo').id, + ], + 'draft')] + with self.assertRaises(NotImplementedError): + self._create_revision(self.partner, changes) + + def test_binary(self): + """ Apply a change on a Binary field """ + changes = [(self.field_one2many, '', 'draft')] + with self.assertRaises(NotImplementedError): + self._create_revision(self.partner, changes) From 6ba0a83d9f964eb92b0ceda28d25b3ed8a00d924 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 17 Sep 2015 08:56:11 +0200 Subject: [PATCH 07/42] Support main types of fields --- partner_revision/models/res_partner.py | 91 +++++++--- .../models/res_partner_revision.py | 93 +++++++++- partner_revision/tests/common.py | 30 +++- .../tests/test_revision_field_type.py | 170 ++++++++++++++++-- .../views/res_partner_revision_views.xml | 5 +- 5 files changed, 334 insertions(+), 55 deletions(-) diff --git a/partner_revision/models/res_partner.py b/partner_revision/models/res_partner.py index 0e415b05e..76ce1416a 100644 --- a/partner_revision/models/res_partner.py +++ b/partner_revision/models/res_partner.py @@ -27,9 +27,70 @@ class ResPartner(models.Model): @api.multi def write(self, values): - for record in self: - values = record._add_revision(values) - return super(ResPartner, self).write(values) + if self.env.context.get('__no_revision'): + return super(ResPartner, self).write(values) + else: + for record in self: + values = record._add_revision(values) + super(ResPartner, record).write(values) + return True + + @api.multi + def _has_field_changed(self, field, value): + self.ensure_one() + field_def = self._fields[field] + return field_def.convert_to_write(self[field]) != value + + @api.multi + def convert_field_for_revision(self, field, value): + field_def = self._fields[field] + if field_def.type == 'many2one': + # store as 'reference' + comodel = field_def.comodel_name + return "%s,%s" % (comodel, value) if value else False + else: + return value + + @api.multi + def _prepare_revision_change(self, rule, field, value): + """ Prepare data for a revision change + + It returns a dict of the values to write on the revision change + and a boolean that indicates if the value should be popped out + of the values to write on the model. + + :returns: dict of values, boolean + """ + field_def = self._fields[field] + # get a ready to write value for the type of the field, + # for instance takes '.id' from a many2one's record (the + # new value is already a value as expected for the + # write) + current_value = field_def.convert_to_write(self[field]) + # get values ready to write as expected by the revision + # (for instance, a many2one is written in a reference + # field) + current_value = self.convert_field_for_revision(field, + current_value) + new_value = self.convert_field_for_revision(field, value) + change = { + 'current_value': current_value, + 'new_value': new_value, + 'field_id': rule.field_id.id, + } + pop_value = False + if not self.env.context.get('__revision_rules'): + # by default always write on partner + change['state'] = 'done' + elif rule.default_behavior == 'auto': + change['state'] = 'done' + elif rule.default_behavior == 'validate': + change['state'] = 'draft' + pop_value = True # change to apply manually + elif rule.default_behavior == 'never': + change['state'] = 'cancel' + pop_value = True # change never applied + return change, pop_value @api.multi def _add_revision(self, values): @@ -57,25 +118,13 @@ class ResPartner(models.Model): if not rule: continue if field in values: - if self[field] == values[field]: - # TODO handle relations, types + if not self._has_field_changed(field, values[field]): continue - change = { - 'current_value': self[field], - 'new_value': values[field], - 'field_id': rule.field_id.id, - } - if not self.env.context.get('__revision_rules'): - # by default always write on partner - change['state'] = 'done' - elif rule.default_behavior == 'auto': - change['state'] = 'done' - elif rule.default_behavior == 'validate': - change['state'] = 'draft' - write_values.pop(field) # change to apply manually - elif rule.default_behavior == 'never': - change['state'] = 'cancel' - write_values.pop(field) # change never applied + change, pop_value = self._prepare_revision_change( + rule, field, values[field] + ) + if pop_value: + write_values.pop(field) changes.append(change) if changes: self.env['res.partner.revision'].create({ diff --git a/partner_revision/models/res_partner_revision.py b/partner_revision/models/res_partner_revision.py index c8665d6d8..17a9cf19b 100644 --- a/partner_revision/models/res_partner_revision.py +++ b/partner_revision/models/res_partner_revision.py @@ -45,7 +45,7 @@ class ResPartnerRevision(models.Model): class ResPartnerRevisionChange(models.Model): _name = 'res.partner.revision.change' _description = 'Partner Revision Change' - _rec_name = 'new_value' + _rec_name = 'field_id' revision_id = fields.Many2one(comodel_name='res.partner.revision', required=True, @@ -54,9 +54,27 @@ class ResPartnerRevisionChange(models.Model): field_id = fields.Many2one(comodel_name='ir.model.fields', string='Field', required=True) - # TODO: different types of fields - current_value = fields.Char('Current') - new_value = fields.Char('New') + + current_value_char = fields.Char(string='Current') + current_value_date = fields.Date(string='Current') + current_value_datetime = fields.Datetime(string='Current') + current_value_float = fields.Float(string='Current') + current_value_integer = fields.Integer(string='Current') + current_value_text = fields.Text(string='Current') + current_value_boolean = fields.Boolean(string='Current') + current_value_reference = fields.Reference(string='Current', + selection='_reference_models') + + new_value_char = fields.Char(string='New') + new_value_date = fields.Date(string='New') + new_value_datetime = fields.Datetime(string='New') + new_value_float = fields.Float(string='New') + new_value_integer = fields.Integer(string='New') + new_value_text = fields.Text(string='New') + new_value_boolean = fields.Boolean(string='New') + new_value_reference = fields.Reference(string='New', + selection='_reference_models') + state = fields.Selection( selection=[('draft', 'Waiting'), ('done', 'Accepted'), @@ -66,10 +84,75 @@ class ResPartnerRevisionChange(models.Model): default='draft', ) + @api.model + def _reference_models(self): + models = self.env['ir.model'].search([]) + return [(model.model, model.name) for model in models] + + _type_to_field = { + 'char': 'char', + 'date': 'date', + 'datetime': 'datetime', + 'float': 'float', + 'integer': 'integer', + 'text': 'text', + 'boolean': 'boolean', + 'many2one': 'reference', + 'selection': 'char', + } + + @api.model + def create(self, vals): + vals = vals.copy() + field = self.env['ir.model.fields'].browse(vals.get('field_id')) + if 'current_value' in vals: + current_value = vals.pop('current_value') + if field: + current_field_name = self.get_field_for_type(field, 'current') + vals[current_field_name] = current_value + if 'new_value' in vals: + new_value = vals.pop('new_value') + if field: + new_field_name = self.get_field_for_type(field, 'new') + vals[new_field_name] = new_value + return super(ResPartnerRevisionChange, self).create(vals) + + @api.model + def get_field_for_type(self, field, current_or_new): + assert current_or_new in ('new', 'current') + field_type = self._type_to_field.get(field.ttype) + if not field_type: + # TODO: prevent to create unsupported types from the views + raise NotImplementedError( + 'field type %s is not supported' % field_type + ) + return '%s_value_%s' % (current_or_new, field_type) + + @api.multi + def get_current_value(self): + self.ensure_one() + field_name = self.get_field_for_type(self.field_id, 'current') + return self[field_name] + + @api.multi + def get_new_value(self): + self.ensure_one() + field_name = self.get_field_for_type(self.field_id, 'new') + return self[field_name] + + @api.multi + def _convert_value_for_write(self, value): + model = self.env[self.field_id.model_id.model] + model_field_def = model._fields[self.field_id.name] + return model_field_def.convert_to_write(value) + @api.multi def apply(self): for change in self: if change.state in ('cancel', 'done'): continue partner = change.revision_id.partner_id - partner.write({change.field_id.name: change.new_value}) + value_for_write = change._convert_value_for_write( + change.get_new_value() + ) + partner.write({change.field_id.name: value_for_write}) diff --git a/partner_revision/tests/common.py b/partner_revision/tests/common.py index a7ed1d38e..b30beaa62 100644 --- a/partner_revision/tests/common.py +++ b/partner_revision/tests/common.py @@ -44,7 +44,9 @@ class RevisionMixin(object): missing = [] for expected_change in expected_changes: for change in changes: - if (change.field_id, change.current_value, change.new_value, + if (change.field_id, + change.get_current_value(), + change.get_new_value(), change.state) == expected_change: changes -= change break @@ -58,8 +60,10 @@ class RevisionMixin(object): for change in changes: message += ("+ field: '%s', current_value: '%s', " "new_value: '%s', state: '%s'\n" % - (change.field_id.name, change.current_value, - change.new_value, change.state)) + (change.field_id.name, + change.get_current_value(), + change.get_new_value(), + change.state)) if message: raise AssertionError('Changes do not match\n\n:%s' % message) @@ -70,14 +74,22 @@ class RevisionMixin(object): :param changes: list of changes [(field, new value, state)] :returns: 'res.partner.revision' record """ - change_values = [ - (0, 0, { + get_field = self.env['res.partner.revision.change'].get_field_for_type + convert = self.env['res.partner'].convert_field_for_revision + change_values = [] + for field, value, state in changes: + field_def = self.env['res.partner']._fields[field.name] + current_value = field_def.convert_to_write(partner[field.name]) + current_value = convert(field.name, current_value) + change = { 'field_id': field.id, - 'current_value': partner[field.name], - 'new_value': value, + # write in the field of the appropriate type for the + # current field (char, many2one, ...) + get_field(field, 'current'): current_value, + get_field(field, 'new'): value, 'state': state, - }) for field, value, state in changes - ] + } + change_values.append((0, 0, change)) values = { 'partner_id': partner.id, 'change_ids': change_values, diff --git a/partner_revision/tests/test_revision_field_type.py b/partner_revision/tests/test_revision_field_type.py index 7f88ec2e0..1e7d64d17 100644 --- a/partner_revision/tests/test_revision_field_type.py +++ b/partner_revision/tests/test_revision_field_type.py @@ -47,7 +47,10 @@ class TestRevisionFieldType(RevisionMixin, common.TransactionCase): ) for field_type, field in fields: attr_name = 'field_%s' % field_type - field_record = self.env.ref('base.field_res_partner_%s' % field) + field_record = self.env['ir.model.fields'].search([ + ('model', '=', 'res.partner'), + ('name', '=', field), + ]) # set attribute such as 'self.field_char' is a # ir.model.fields record of the field res_partner.ref setattr(self, attr_name, field_record) @@ -65,14 +68,140 @@ class TestRevisionFieldType(RevisionMixin, common.TransactionCase): 'street': 'Original Street', }) - def test_char(self): + def test_new_revision_char(self): + """ Add a new revision on a Char field """ + self.partner.with_context(__revision_rules=True).write({ + self.field_char.name: 'New value', + }) + self.assert_revision( + self.partner, + [(self.field_char, self.partner[self.field_char.name], + 'New value', 'draft'), + ] + ) + + def test_new_revision_text(self): + """ Add a new revision on a Text field """ + self.partner.with_context(__revision_rules=True).write({ + self.field_text.name: 'New comment\non 2 lines', + }) + self.assert_revision( + self.partner, + [(self.field_text, self.partner[self.field_text.name], + 'New comment\non 2 lines', 'draft'), + ] + ) + + def test_new_revision_boolean(self): + """ Add a new revision on a Boolean field """ + # ensure the revision has to change the value + self.partner.with_context(__no_revision=True).write({ + self.field_boolean.name: False, + }) + + self.partner.with_context(__revision_rules=True).write({ + self.field_boolean.name: True, + }) + self.assert_revision( + self.partner, + [(self.field_boolean, self.partner[self.field_boolean.name], + True, 'draft'), + ] + ) + + def test_new_revision_date(self): + """ Add a new revision on a Date field """ + self.partner.with_context(__revision_rules=True).write({ + self.field_date.name: '2015-09-15', + }) + self.assert_revision( + self.partner, + [(self.field_date, self.partner[self.field_date.name], + '2015-09-15', 'draft'), + ] + ) + + def test_new_revision_integer(self): + """ Add a new revision on a Integer field """ + self.partner.with_context(__revision_rules=True).write({ + self.field_integer.name: 42, + }) + self.assert_revision( + self.partner, + [(self.field_integer, self.partner[self.field_integer.name], + 42, 'draft'), + ] + ) + + def test_new_revision_float(self): + """ Add a new revision on a Float field """ + self.partner.with_context(__revision_rules=True).write({ + self.field_float.name: 3.1415, + }) + self.assert_revision( + self.partner, + [(self.field_float, self.partner[self.field_float.name], + 3.1415, 'draft'), + ] + ) + + def test_new_revision_selection(self): + """ Add a new revision on a Selection field """ + self.partner.with_context(__revision_rules=True).write({ + self.field_selection.name: 'delivery', + }) + self.assert_revision( + self.partner, + [(self.field_selection, self.partner[self.field_selection.name], + 'delivery', 'draft'), + ] + ) + + def test_new_revision_many2one(self): + """ Add a new revision on a Many2one field """ + self.partner.with_context(__no_revision=True).write({ + self.field_many2one.name: self.env.ref('base.fr').id, + + }) + self.partner.with_context(__revision_rules=True).write({ + self.field_many2one.name: self.env.ref('base.ch').id, + }) + self.assert_revision( + self.partner, + [(self.field_many2one, self.partner[self.field_many2one.name], + self.env.ref('base.ch'), 'draft'), + ] + ) + + def test_new_revision_many2many(self): + """ Add a new revision on a Many2many field is not supported """ + with self.assertRaises(NotImplementedError): + self.partner.with_context(__revision_rules=True).write({ + self.field_many2many.name: [self.env.ref('base.ch').id], + }) + + def test_new_revision_one2many(self): + """ Add a new revision on a One2many field is not supported """ + with self.assertRaises(NotImplementedError): + self.partner.with_context(__revision_rules=True).write({ + self.field_one2many.name: [self.env.ref('base.user_root').id], + }) + + def test_new_revision_binary(self): + """ Add a new revision on a Binary field is not supported """ + with self.assertRaises(NotImplementedError): + self.partner.with_context(__revision_rules=True).write({ + self.field_binary.name: '', + }) + + def test_apply_char(self): """ Apply a change on a Char field """ changes = [(self.field_char, 'New Ref', 'draft')] revision = self._create_revision(self.partner, changes) revision.change_ids.apply() self.assertEqual(self.partner[self.field_char.name], 'New Ref') - def test_text(self): + def test_apply_text(self): """ Apply a change on a Text field """ changes = [(self.field_text, 'New comment\non 2 lines', 'draft')] revision = self._create_revision(self.partner, changes) @@ -80,7 +209,7 @@ class TestRevisionFieldType(RevisionMixin, common.TransactionCase): self.assertEqual(self.partner[self.field_text.name], 'New comment\non 2 lines') - def test_boolean(self): + def test_apply_boolean(self): """ Apply a change on a Boolean field """ # ensure the revision has to change the value self.partner.write({self.field_boolean.name: False}) @@ -95,7 +224,7 @@ class TestRevisionFieldType(RevisionMixin, common.TransactionCase): revision.change_ids.apply() self.assertEqual(self.partner[self.field_boolean.name], False) - def test_date(self): + def test_apply_date(self): """ Apply a change on a Date field """ changes = [(self.field_date, '2015-09-15', 'draft')] revision = self._create_revision(self.partner, changes) @@ -103,21 +232,21 @@ class TestRevisionFieldType(RevisionMixin, common.TransactionCase): self.assertAlmostEqual(self.partner[self.field_date.name], '2015-09-15') - def test_integer(self): + def test_apply_integer(self): """ Apply a change on a Integer field """ changes = [(self.field_integer, 42, 'draft')] revision = self._create_revision(self.partner, changes) revision.change_ids.apply() self.assertAlmostEqual(self.partner[self.field_integer.name], 42) - def test_float(self): + def test_apply_float(self): """ Apply a change on a Float field """ changes = [(self.field_float, 52.47, 'draft')] revision = self._create_revision(self.partner, changes) revision.change_ids.apply() self.assertAlmostEqual(self.partner[self.field_float.name], 52.47) - def test_selection(self): + def test_apply_selection(self): """ Apply a change on a Selection field """ changes = [(self.field_selection, 'delivery', 'draft')] revision = self._create_revision(self.partner, changes) @@ -125,26 +254,31 @@ class TestRevisionFieldType(RevisionMixin, common.TransactionCase): self.assertAlmostEqual(self.partner[self.field_selection.name], 'delivery') - def test_many2one(self): + def test_apply_many2one(self): """ Apply a change on a Many2one field """ + self.s = True + self.partner.with_context(__no_revision=True).write({ + self.field_many2one.name: self.env.ref('base.fr').id, + + }) changes = [(self.field_many2one, - self.env.ref('base.ch').id, + 'res.country,%d' % self.env.ref('base.ch').id, 'draft')] revision = self._create_revision(self.partner, changes) revision.change_ids.apply() - self.assertAlmostEqual(self.partner[self.field_many2one.name], - self.env.ref('base.ch').id) + self.assertEqual(self.partner[self.field_many2one.name], + self.env.ref('base.ch')) - def test_many2many(self): - """ Apply a change on a Many2many field """ + def test_apply_many2many(self): + """ Apply a change on a Many2many field is not supported """ changes = [(self.field_many2many, self.env.ref('base.ch').id, 'draft')] with self.assertRaises(NotImplementedError): self._create_revision(self.partner, changes) - def test_one2many(self): - """ Apply a change on a One2many field """ + def test_apply_one2many(self): + """ Apply a change on a One2many field is not supported """ changes = [(self.field_one2many, [self.env.ref('base.user_root').id, self.env.ref('base.user_demo').id, @@ -153,8 +287,8 @@ class TestRevisionFieldType(RevisionMixin, common.TransactionCase): with self.assertRaises(NotImplementedError): self._create_revision(self.partner, changes) - def test_binary(self): - """ Apply a change on a Binary field """ + def test_apply_binary(self): + """ Apply a change on a Binary field is not supported """ changes = [(self.field_one2many, '', 'draft')] with self.assertRaises(NotImplementedError): self._create_revision(self.partner, changes) diff --git a/partner_revision/views/res_partner_revision_views.xml b/partner_revision/views/res_partner_revision_views.xml index 3b4c233d1..8dd8c880e 100644 --- a/partner_revision/views/res_partner_revision_views.xml +++ b/partner_revision/views/res_partner_revision_views.xml @@ -29,8 +29,9 @@ - - + + + From 421abd14c2bf715d11a19eb8b05bff799bc70669 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 17 Sep 2015 14:46:38 +0200 Subject: [PATCH 08/42] Refactoring: move methods from partner to revision models --- partner_revision/models/res_partner.py | 106 +--------------- .../models/res_partner_revision.py | 117 +++++++++++++++++- partner_revision/tests/common.py | 7 +- 3 files changed, 119 insertions(+), 111 deletions(-) diff --git a/partner_revision/models/res_partner.py b/partner_revision/models/res_partner.py index 76ce1416a..d7c06d8e2 100644 --- a/partner_revision/models/res_partner.py +++ b/partner_revision/models/res_partner.py @@ -19,7 +19,7 @@ # # -from openerp import models, fields, api +from openerp import models, api class ResPartner(models.Model): @@ -30,106 +30,8 @@ class ResPartner(models.Model): if self.env.context.get('__no_revision'): return super(ResPartner, self).write(values) else: + revision_model = self.env['res.partner.revision'] for record in self: - values = record._add_revision(values) - super(ResPartner, record).write(values) + local_values = revision_model.add_revision(record, values) + super(ResPartner, record).write(local_values) return True - - @api.multi - def _has_field_changed(self, field, value): - self.ensure_one() - field_def = self._fields[field] - return field_def.convert_to_write(self[field]) != value - - @api.multi - def convert_field_for_revision(self, field, value): - field_def = self._fields[field] - if field_def.type == 'many2one': - # store as 'reference' - comodel = field_def.comodel_name - return "%s,%s" % (comodel, value) if value else False - else: - return value - - @api.multi - def _prepare_revision_change(self, rule, field, value): - """ Prepare data for a revision change - - It returns a dict of the values to write on the revision change - and a boolean that indicates if the value should be popped out - of the values to write on the model. - - :returns: dict of values, boolean - """ - field_def = self._fields[field] - # get a ready to write value for the type of the field, - # for instance takes '.id' from a many2one's record (the - # new value is already a value as expected for the - # write) - current_value = field_def.convert_to_write(self[field]) - # get values ready to write as expected by the revision - # (for instance, a many2one is written in a reference - # field) - current_value = self.convert_field_for_revision(field, - current_value) - new_value = self.convert_field_for_revision(field, value) - change = { - 'current_value': current_value, - 'new_value': new_value, - 'field_id': rule.field_id.id, - } - pop_value = False - if not self.env.context.get('__revision_rules'): - # by default always write on partner - change['state'] = 'done' - elif rule.default_behavior == 'auto': - change['state'] = 'done' - elif rule.default_behavior == 'validate': - change['state'] = 'draft' - pop_value = True # change to apply manually - elif rule.default_behavior == 'never': - change['state'] = 'cancel' - pop_value = True # change never applied - return change, pop_value - - @api.multi - def _add_revision(self, values): - """ Add a revision on a partner - - By default, when a partner is modified by a user or by the - system, the changes are applied and a validated revision is - created. Callers which want to delegate the write of some - fields to the revision must explicitly ask for it by providing a - key ``__revision_rules`` in the environment's context. - - :param values: the values being written on the partner - :type values: dict - - :returns: dict of values that should be wrote on the partner - (fields with a 'Validate' or 'Never' rule are excluded) - - """ - self.ensure_one() - write_values = values.copy() - changes = [] - rules = self.env['revision.behavior'].get_rules(self._model._name) - for field in values: - rule = rules.get(field) - if not rule: - continue - if field in values: - if not self._has_field_changed(field, values[field]): - continue - change, pop_value = self._prepare_revision_change( - rule, field, values[field] - ) - if pop_value: - write_values.pop(field) - changes.append(change) - if changes: - self.env['res.partner.revision'].create({ - 'partner_id': self.id, - 'change_ids': [(0, 0, vals) for vals in changes], - 'date': fields.Datetime.now(), - }) - return write_values diff --git a/partner_revision/models/res_partner_revision.py b/partner_revision/models/res_partner_revision.py index 17a9cf19b..62e4a8286 100644 --- a/partner_revision/models/res_partner_revision.py +++ b/partner_revision/models/res_partner_revision.py @@ -41,6 +41,55 @@ class ResPartnerRevision(models.Model): def apply(self): self.mapped('change_ids').apply() + @api.multi + def add_revision(self, record, values): + """ Add a revision on a partner + + By default, when a partner is modified by a user or by the + system, the changes are applied and a validated revision is + created. Callers which want to delegate the write of some + fields to the revision must explicitly ask for it by providing a + key ``__revision_rules`` in the environment's context. + + Should be called before the execution of ``write`` on the record + so we can keep track of the existing value and also because the + returned values should be used for ``write`` as some of the + values may have been removed. + + :param values: the values being written on the partner + :type values: dict + + :returns: dict of values that should be wrote on the partner + (fields with a 'Validate' or 'Never' rule are excluded) + + """ + record.ensure_one() + change_model = self.env['res.partner.revision.change'] + write_values = values.copy() + changes = [] + rules = self.env['revision.behavior'].get_rules(record._model._name) + for field in values: + rule = rules.get(field) + if not rule: + continue + if field in values: + if not change_model._has_field_changed(record, field, + values[field]): + continue + change, pop_value = change_model._prepare_revision_change( + record, rule, field, values[field] + ) + if pop_value: + write_values.pop(field) + changes.append(change) + if changes: + self.env['res.partner.revision'].create({ + 'partner_id': record.id, + 'change_ids': [(0, 0, vals) for vals in changes], + 'date': fields.Datetime.now(), + }) + return write_values + class ResPartnerRevisionChange(models.Model): _name = 'res.partner.revision.change' @@ -140,12 +189,6 @@ class ResPartnerRevisionChange(models.Model): field_name = self.get_field_for_type(self.field_id, 'new') return self[field_name] - @api.multi - def _convert_value_for_write(self, value): - model = self.env[self.field_id.model_id.model] - model_field_def = model._fields[self.field_id.name] - return model_field_def.convert_to_write(value) - @api.multi def apply(self): for change in self: @@ -156,3 +199,65 @@ class ResPartnerRevisionChange(models.Model): change.get_new_value() ) partner.write({change.field_id.name: value_for_write}) + + @api.model + def _has_field_changed(self, record, field, value): + field_def = record._fields[field] + return field_def.convert_to_write(record[field]) != value + + @api.multi + def _convert_value_for_write(self, value): + model = self.env[self.field_id.model_id.model] + model_field_def = model._fields[self.field_id.name] + return model_field_def.convert_to_write(value) + + @api.model + def _convert_value_for_revision(self, record, field, value): + field_def = record._fields[field] + if field_def.type == 'many2one': + # store as 'reference' + comodel = field_def.comodel_name + return "%s,%s" % (comodel, value) if value else False + else: + return value + + @api.multi + def _prepare_revision_change(self, record, rule, field, value): + """ Prepare data for a revision change + + It returns a dict of the values to write on the revision change + and a boolean that indicates if the value should be popped out + of the values to write on the model. + + :returns: dict of values, boolean + """ + field_def = record._fields[field] + # get a ready to write value for the type of the field, + # for instance takes '.id' from a many2one's record (the + # new value is already a value as expected for the + # write) + current_value = field_def.convert_to_write(record[field]) + # get values ready to write as expected by the revision + # (for instance, a many2one is written in a reference + # field) + current_value = self._convert_value_for_revision(record, field, + current_value) + new_value = self._convert_value_for_revision(record, field, value) + change = { + 'current_value': current_value, + 'new_value': new_value, + 'field_id': rule.field_id.id, + } + pop_value = False + if not self.env.context.get('__revision_rules'): + # by default always write on partner + change['state'] = 'done' + elif rule.default_behavior == 'auto': + change['state'] = 'done' + elif rule.default_behavior == 'validate': + change['state'] = 'draft' + pop_value = True # change to apply manually + elif rule.default_behavior == 'never': + change['state'] = 'cancel' + pop_value = True # change never applied + return change, pop_value diff --git a/partner_revision/tests/common.py b/partner_revision/tests/common.py index b30beaa62..be8a4d4c2 100644 --- a/partner_revision/tests/common.py +++ b/partner_revision/tests/common.py @@ -74,13 +74,14 @@ class RevisionMixin(object): :param changes: list of changes [(field, new value, state)] :returns: 'res.partner.revision' record """ - get_field = self.env['res.partner.revision.change'].get_field_for_type - convert = self.env['res.partner'].convert_field_for_revision + RevisionChange = self.env['res.partner.revision.change'] + get_field = RevisionChange.get_field_for_type + convert = RevisionChange._convert_value_for_revision change_values = [] for field, value, state in changes: field_def = self.env['res.partner']._fields[field.name] current_value = field_def.convert_to_write(partner[field.name]) - current_value = convert(field.name, current_value) + current_value = convert(partner, field.name, current_value) change = { 'field_id': field.id, # write in the field of the appropriate type for the From e6b4296a1e495efba51fe2b0f0127be4401885bb Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 17 Sep 2015 20:17:36 +0200 Subject: [PATCH 09/42] Improve the revision user interface --- partner_revision/__openerp__.py | 3 +- .../models/res_partner_revision.py | 94 ++++++++++++++++--- partner_revision/tests/test_revision_flow.py | 1 + partner_revision/views/menu.xml | 9 ++ .../views/res_partner_revision_views.xml | 80 ++++++++++++++-- .../views/revision_behavior_views.xml | 7 +- 6 files changed, 169 insertions(+), 25 deletions(-) create mode 100644 partner_revision/views/menu.xml diff --git a/partner_revision/__openerp__.py b/partner_revision/__openerp__.py index 0494543ce..9588a7081 100644 --- a/partner_revision/__openerp__.py +++ b/partner_revision/__openerp__.py @@ -27,7 +27,8 @@ 'depends': ['base', ], 'website': 'http://www.camptocamp.com', - 'data': ['views/res_partner_revision_views.xml', + 'data': ['views/menu.xml', + 'views/res_partner_revision_views.xml', 'views/revision_behavior_views.xml', ], 'test': [], diff --git a/partner_revision/models/res_partner_revision.py b/partner_revision/models/res_partner_revision.py index 62e4a8286..12612284b 100644 --- a/partner_revision/models/res_partner_revision.py +++ b/partner_revision/models/res_partner_revision.py @@ -19,7 +19,10 @@ # # -from openerp import models, fields, api +from lxml import etree + +from openerp import models, fields, api, exceptions, _ +from openerp.osv.orm import setup_modifiers class ResPartnerRevision(models.Model): @@ -35,12 +38,18 @@ class ResPartnerRevision(models.Model): inverse_name='revision_id', string='Changes') date = fields.Datetime(default=fields.Datetime.now) + # TODO: add a revision state, done when all lines are done or + # canceled note = fields.Text() @api.multi def apply(self): self.mapped('change_ids').apply() + @api.multi + def cancel(self): + self.mapped('change_ids').cancel() + @api.multi def add_revision(self, record, values): """ Add a revision on a partner @@ -103,6 +112,18 @@ class ResPartnerRevisionChange(models.Model): field_id = fields.Many2one(comodel_name='ir.model.fields', string='Field', required=True) + field_type = fields.Selection(related='field_id.ttype', + string='Field Type', + readonly=True) + + current_value_display = fields.Char( + string='Current', + compute='_compute_value_display', + ) + new_value_display = fields.Char( + string='New', + compute='_compute_value_display', + ) current_value_char = fields.Char(string='Current') current_value_date = fields.Date(string='Current') @@ -138,18 +159,36 @@ class ResPartnerRevisionChange(models.Model): models = self.env['ir.model'].search([]) return [(model.model, model.name) for model in models] - _type_to_field = { - 'char': 'char', - 'date': 'date', - 'datetime': 'datetime', - 'float': 'float', - 'integer': 'integer', - 'text': 'text', - 'boolean': 'boolean', - 'many2one': 'reference', - 'selection': 'char', + _suffix_to_types = { + 'char': ('char', 'selection'), + 'date': ('date',), + 'datetime': ('datetime',), + 'float': ('float',), + 'integer': ('integer',), + 'text': ('text',), + 'boolean': ('boolean',), + 'reference': ('many2one',), } + _type_to_suffix = {ftype: suffix + for suffix, ftypes in _suffix_to_types.iteritems() + for ftype in ftypes} + + _current_value_fields = ['current_value_%s' % suffix + for suffix in _suffix_to_types] + _new_value_fields = ['new_value_%s' % suffix + for suffix in _suffix_to_types] + _value_fields = _current_value_fields + _new_value_fields + + @api.one + @api.depends(lambda self: self._value_fields) + def _compute_value_display(self): + for prefix in ('current', 'new'): + value = getattr(self, 'get_%s_value' % prefix)() + if self.field_id.ttype == 'many2one' and value: + value = value.display_name + setattr(self, '%s_value_display' % prefix, value) + @api.model def create(self, vals): vals = vals.copy() @@ -169,9 +208,8 @@ class ResPartnerRevisionChange(models.Model): @api.model def get_field_for_type(self, field, current_or_new): assert current_or_new in ('new', 'current') - field_type = self._type_to_field.get(field.ttype) + field_type = self._type_to_suffix.get(field.ttype) if not field_type: - # TODO: prevent to create unsupported types from the views raise NotImplementedError( 'field type %s is not supported' % field_type ) @@ -191,6 +229,7 @@ class ResPartnerRevisionChange(models.Model): @api.multi def apply(self): + # TODO: optimize with 1 write for all fields, group by revision for change in self: if change.state in ('cancel', 'done'): continue @@ -199,6 +238,15 @@ class ResPartnerRevisionChange(models.Model): change.get_new_value() ) partner.write({change.field_id.name: value_for_write}) + change.write({'state': 'done'}) + + @api.multi + def cancel(self): + if any(change.state == 'done' for change in self): + raise exceptions.Warning( + _('This change has already be applied.') + ) + self.write({'state': 'cancel'}) @api.model def _has_field_changed(self, record, field, value): @@ -261,3 +309,23 @@ class ResPartnerRevisionChange(models.Model): change['state'] = 'cancel' pop_value = True # change never applied return change, pop_value + + def fields_view_get(self, *args, **kwargs): + _super = super(ResPartnerRevisionChange, self) + result = _super.fields_view_get(*args, **kwargs) + if result['type'] != 'form': + return + doc = etree.XML(result['arch']) + for suffix, ftypes in self._suffix_to_types.iteritems(): + for prefix in ('current', 'new'): + field_name = '%s_value_%s' % (prefix, suffix) + field_nodes = doc.xpath("//field[@name='%s']" % field_name) + for node in field_nodes: + node.set( + 'attrs', + "{'invisible': " + "[('field_type', 'not in', %s)]}" % (ftypes,) + ) + setup_modifiers(node) + result['arch'] = etree.tostring(doc) + return result diff --git a/partner_revision/tests/test_revision_flow.py b/partner_revision/tests/test_revision_flow.py index 3fedcc678..b56c370fb 100644 --- a/partner_revision/tests/test_revision_flow.py +++ b/partner_revision/tests/test_revision_flow.py @@ -127,6 +127,7 @@ class TestRevisionFlow(RevisionMixin, common.TransactionCase): revision = self._create_revision(self.partner, changes) revision.change_ids.apply() self.assertEqual(self.partner.name, 'Y') + self.assertEqual(revision.change_ids.state, 'done') def test_apply_done_change(self): """ Done changes do not apply (already applied) """ diff --git a/partner_revision/views/menu.xml b/partner_revision/views/menu.xml new file mode 100644 index 000000000..a0137e792 --- /dev/null +++ b/partner_revision/views/menu.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/partner_revision/views/res_partner_revision_views.xml b/partner_revision/views/res_partner_revision_views.xml index 8dd8c880e..8d44348e0 100644 --- a/partner_revision/views/res_partner_revision_views.xml +++ b/partner_revision/views/res_partner_revision_views.xml @@ -22,20 +22,82 @@ - - - - + - + - - - + + + + + + + + + + + + res.partner.select + res.partner + + + + + + + + + + + From 98e84802fc38175ac8636e33ea62a29cc714c601 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Fri, 18 Sep 2015 16:22:06 +0200 Subject: [PATCH 19/42] Documentation in README --- partner_revision/README.rst | 88 ++++++++++++++++--- .../models/res_partner_revision.py | 2 +- 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/partner_revision/README.rst b/partner_revision/README.rst index 9339e1ae2..6fb2dcb26 100644 --- a/partner_revision/README.rst +++ b/partner_revision/README.rst @@ -1,15 +1,79 @@ +================= Partner Revisions ================= -* Modèle de données et vues : création de la base du module ; création des - modèles sans logique et de leurs vues ; création d’un champ fonction pour - l’affichage des valeurs afin de pouvoir afficher les champs relation comme il - faut (“name” de la relation au lieu de l’id.) → le champ “backend_id” devra - être un champ de type “reference” car il peut être lié à plusieurs types de - backend différents ; -* Logique : gestion des états des entrées du journal ; application manuelle des - changements ; application automatique des entrées selon la configuration du - comportement par défaut ; ne pas appliquer automatiquement de changements si - une entrée plus récente existe (dans le cas de création d’entrée antidatée) ; - création d’entrée “validée” lors de saisie manuelle sur les partenaires ; - écriture de tests unitaires ; +Configuration +============= + +Access Rights +------------- + +The revisions rules must be edited by users with the group ``Revision +Configuration``. The revisions can be applied or canceled only by users +with the group ``Revisions Validations`` + +Revision Rules +-------------- + +The revision rules can be configured in ``Sales > Configuration > +Partner Revisions > Revision Fields Rules``. For each partner field, an +action can be defined: + +* Auto: the changes made on this field are always applied +* Validate: the changes made on this field must be manually confirmed by + a 'Revision User' user +* Never: the changes made on this field are always refused + +In any case, all the changes made by the users are always applied +directly on the users, but a 'validated' revision is created for the +history. + +The supported fields are: + +* Char +* Text +* Date +* Datetime +* Integer +* Float +* Boolean +* Many2one + +Usage +===== + +General case +------------ + +When users modify the partners, new 'validated' revisions are created so +there is nothing to do. Addons wanting to create revisions which need a +validation should pass the key ``__revision_rules`` in the context when +they write on the partner. + +Finding changesets +------------------ + +A menu shows all the changesets in ``Sales > Configuration > Partner +Revisions > Partner Revision``. + +However, it is more convenient to access them directly from the +partners. Pending revisions can be accessed directly from the top right +of the partners' view. A new filter on the partners shows the partners +having at least one pending revision. + +Handling changesets +------------------- + +A revision shows the list of the changes made on a partner. Some of the +changes may be 'Pending', some 'Accepted' or 'Rejected' according to the +revision rules. The only changes that need an action from the user are +'Pending' changes. When a change is accepted, the value is written on +the user. + +The changes view shows the name of the partner's field, the Origin value +and the New value alongside the state of the change. By clicking on the +change in some cases a more detailed view is displayed, for instance, +links for relations can be clicked on. + +A button on a changeset allows to apply or reject all the changes at +once. diff --git a/partner_revision/models/res_partner_revision.py b/partner_revision/models/res_partner_revision.py index eb99ae59c..4c94ce3c7 100644 --- a/partner_revision/models/res_partner_revision.py +++ b/partner_revision/models/res_partner_revision.py @@ -250,7 +250,7 @@ class ResPartnerRevisionChange(models.Model): state = fields.Selection( selection=[('draft', 'Pending'), ('done', 'Accepted'), - ('cancel', 'Refused'), + ('cancel', 'Rejected'), ], required=True, default='draft', From e3c299828e005c710f955bfa81e6f72269198ead Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Fri, 18 Sep 2015 16:35:52 +0200 Subject: [PATCH 20/42] Rename 'revision' to 'changeset' Because the term seems more adapted to what this module does: it does not create revisions as one could expect with the full content of the partner, instead it proposes changesets on a number of fields. The renaming of the files follows in a second commit. --- partner_revision/README.rst | 40 ++--- partner_revision/__openerp__.py | 2 +- partner_revision/models/res_partner.py | 34 ++-- .../models/res_partner_revision.py | 104 ++++++------ .../models/revision_field_rule.py | 12 +- partner_revision/security/ir.model.access.csv | 18 +-- partner_revision/security/security.xml | 14 +- partner_revision/tests/common.py | 32 ++-- .../tests/test_revision_field_type.py | 146 ++++++++--------- partner_revision/tests/test_revision_flow.py | 150 +++++++++--------- .../tests/test_revision_origin.py | 34 ++-- partner_revision/views/menu.xml | 6 +- .../views/res_partner_revision_views.xml | 51 +++--- partner_revision/views/res_partner_views.xml | 16 +- .../views/revision_field_rule_views.xml | 43 ++--- 15 files changed, 352 insertions(+), 350 deletions(-) diff --git a/partner_revision/README.rst b/partner_revision/README.rst index 6fb2dcb26..cfafb7908 100644 --- a/partner_revision/README.rst +++ b/partner_revision/README.rst @@ -1,6 +1,6 @@ -================= -Partner Revisions -================= +================== +Partner Changesets +================== Configuration ============= @@ -8,24 +8,24 @@ Configuration Access Rights ------------- -The revisions rules must be edited by users with the group ``Revision -Configuration``. The revisions can be applied or canceled only by users -with the group ``Revisions Validations`` +The changesets rules must be edited by users with the group ``Changesets +Configuration``. The changesets can be applied or canceled only by users +with the group ``Changesets Validations`` -Revision Rules --------------- +Changesets Rules +---------------- -The revision rules can be configured in ``Sales > Configuration > -Partner Revisions > Revision Fields Rules``. For each partner field, an +The changesets rules can be configured in ``Sales > Configuration > +Partner Changesets > Fields Rules``. For each partner field, an action can be defined: * Auto: the changes made on this field are always applied * Validate: the changes made on this field must be manually confirmed by - a 'Revision User' user + a 'Changesets User' user * Never: the changes made on this field are always refused In any case, all the changes made by the users are always applied -directly on the users, but a 'validated' revision is created for the +directly on the users, but a 'validated' changeset is created for the history. The supported fields are: @@ -45,28 +45,28 @@ Usage General case ------------ -When users modify the partners, new 'validated' revisions are created so -there is nothing to do. Addons wanting to create revisions which need a -validation should pass the key ``__revision_rules`` in the context when +When users modify the partners, new 'validated' changeset are created so +there is nothing to do. Addons wanting to create changeset which need a +validation should pass the key ``_changeset_rules`` in the context when they write on the partner. Finding changesets ------------------ A menu shows all the changesets in ``Sales > Configuration > Partner -Revisions > Partner Revision``. +Changesets > Changesets``. However, it is more convenient to access them directly from the -partners. Pending revisions can be accessed directly from the top right +partners. Pending changesets can be accessed directly from the top right of the partners' view. A new filter on the partners shows the partners -having at least one pending revision. +having at least one pending changeset. Handling changesets ------------------- -A revision shows the list of the changes made on a partner. Some of the +A changeset shows the list of the changes made on a partner. Some of the changes may be 'Pending', some 'Accepted' or 'Rejected' according to the -revision rules. The only changes that need an action from the user are +changeset rules. The only changes that need an action from the user are 'Pending' changes. When a change is accepted, the value is written on the user. diff --git a/partner_revision/__openerp__.py b/partner_revision/__openerp__.py index 0ef423979..8665bd91c 100644 --- a/partner_revision/__openerp__.py +++ b/partner_revision/__openerp__.py @@ -19,7 +19,7 @@ # # -{'name': 'Partner Revisions', +{'name': 'Partner Changesets', 'version': '1.0', 'author': 'Camptocamp', 'license': 'AGPL-3', diff --git a/partner_revision/models/res_partner.py b/partner_revision/models/res_partner.py index 83fce66f3..db96b97b8 100644 --- a/partner_revision/models/res_partner.py +++ b/partner_revision/models/res_partner.py @@ -25,40 +25,40 @@ from openerp import models, fields, api class ResPartner(models.Model): _inherit = 'res.partner' - revision_ids = fields.One2many(comodel_name='res.partner.revision', - inverse_name='partner_id', - string='Revisions', - readonly=True) - count_pending_revisions = fields.Integer( - string='Pending Revisions', - compute='_count_pending_revisions', - search='_search_count_pending_revisions') + changeset_ids = fields.One2many(comodel_name='res.partner.changeset', + inverse_name='partner_id', + string='Changesets', + readonly=True) + count_pending_changesets = fields.Integer( + string='Pending Changesets', + compute='_count_pending_changesets', + search='_search_count_pending_changesets') @api.one - @api.depends('revision_ids', 'revision_ids.state') - def _count_pending_revisions(self): - revisions = self.revision_ids.filtered( + @api.depends('changeset_ids', 'changeset_ids.state') + def _count_pending_changesets(self): + changesets = self.changeset_ids.filtered( lambda rev: rev.state == 'draft' and rev.partner_id == self ) - self.count_pending_revisions = len(revisions) + self.count_pending_changesets = len(changesets) @api.multi def write(self, values): - if self.env.context.get('__no_revision'): + if self.env.context.get('__no_changeset'): return super(ResPartner, self).write(values) else: - revision_model = self.env['res.partner.revision'] + changeset_model = self.env['res.partner.changeset'] for record in self: - local_values = revision_model.add_revision(record, values) + local_values = changeset_model.add_changeset(record, values) super(ResPartner, record).write(local_values) return True - def _search_count_pending_revisions(self, operator, value): + def _search_count_pending_changesets(self, operator, value): if operator not in ('=', '!=', '<', '<=', '>', '>=', 'in', 'not in'): return [] query = ("SELECT p.id " "FROM res_partner p " - "INNER JOIN res_partner_revision r ON r.partner_id = p.id " + "INNER JOIN res_partner_changeset r ON r.partner_id = p.id " "WHERE r.state = 'draft' " "GROUP BY p.id " "HAVING COUNT(r.id) %s %%s ") % operator diff --git a/partner_revision/models/res_partner_revision.py b/partner_revision/models/res_partner_revision.py index 4c94ce3c7..ef0cc5d8e 100644 --- a/partner_revision/models/res_partner_revision.py +++ b/partner_revision/models/res_partner_revision.py @@ -27,13 +27,13 @@ from openerp import models, fields, api, exceptions, _ from openerp.osv.orm import setup_modifiers # sentinel object to be sure that no empty value was passed to -# ResPartnerRevisionChange._value_for_revision +# ResPartnerChangesetChange._value_for_changeset _NO_VALUE = object() -class ResPartnerRevision(models.Model): - _name = 'res.partner.revision' - _description = 'Partner Revision' +class ResPartnerChangeset(models.Model): + _name = 'res.partner.changeset' + _description = 'Partner Changeset' _order = 'date desc' _rec_name = 'date' @@ -42,8 +42,8 @@ class ResPartnerRevision(models.Model): select=True, required=True, readonly=True) - change_ids = fields.One2many(comodel_name='res.partner.revision.change', - inverse_name='revision_id', + change_ids = fields.One2many(comodel_name='res.partner.changeset.change', + inverse_name='changeset_id', string='Changes', readonly=True) date = fields.Datetime(default=fields.Datetime.now, @@ -76,14 +76,14 @@ class ResPartnerRevision(models.Model): self.mapped('change_ids').cancel() @api.multi - def add_revision(self, record, values): - """ Add a revision on a partner + def add_changeset(self, record, values): + """ Add a changeset on a partner By default, when a partner is modified by a user or by the - system, the changes are applied and a validated revision is + system, the changes are applied and a validated changeset is created. Callers which want to delegate the write of some - fields to the revision must explicitly ask for it by providing a - key ``__revision_rules`` in the environment's context. + fields to the changeset must explicitly ask for it by providing a + key ``__changeset_rules`` in the environment's context. Should be called before the execution of ``write`` on the record so we can keep track of the existing value and also because the @@ -98,10 +98,10 @@ class ResPartnerRevision(models.Model): """ record.ensure_one() - change_model = self.env['res.partner.revision.change'] + change_model = self.env['res.partner.changeset.change'] write_values = values.copy() changes = [] - rules = self.env['revision.field.rule'].get_rules(record._model._name) + rules = self.env['changeset.field.rule'].get_rules(record._model._name) for field in values: rule = rules.get(field) if not rule: @@ -110,14 +110,14 @@ class ResPartnerRevision(models.Model): if not change_model._has_field_changed(record, field, values[field]): continue - change, pop_value = change_model._prepare_revision_change( + change, pop_value = change_model._prepare_changeset_change( record, rule, field, values[field] ) if pop_value: write_values.pop(field) changes.append(change) if changes: - self.env['res.partner.revision'].create({ + self.env['res.partner.changeset'].create({ 'partner_id': record.id, 'change_ids': [(0, 0, vals) for vals in changes], 'date': fields.Datetime.now(), @@ -125,8 +125,8 @@ class ResPartnerRevision(models.Model): return write_values -class ResPartnerRevisionChange(models.Model): - """ Store the change of one field for one revision on one partner +class ResPartnerChangesetChange(models.Model): + """ Store the change of one field for one changeset on one partner This model is composed of 3 sets of fields: @@ -142,7 +142,7 @@ class ResPartnerRevisionChange(models.Model): the partner until the change is either applied either canceled, past that it shows the 'old' value. The reason behind this is that the values may change on a partner between - the moment when the revision is created and when it is applied. + the moment when the changeset is created and when it is applied. On the views, we show the origin fields which represent the actual partner values or the old values and we show the new fields. @@ -152,15 +152,15 @@ class ResPartnerRevisionChange(models.Model): displayed on the form view so we benefit from their widgets. """ - _name = 'res.partner.revision.change' - _description = 'Partner Revision Change' + _name = 'res.partner.changeset.change' + _description = 'Partner Changeset Change' _rec_name = 'field_id' - revision_id = fields.Many2one(comodel_name='res.partner.revision', - required=True, - string='Revision', - ondelete='cascade', - readonly=True) + changeset_id = fields.Many2one(comodel_name='res.partner.changeset', + required=True, + string='Changeset', + ondelete='cascade', + readonly=True) field_id = fields.Many2one(comodel_name='ir.model.fields', string='Field', required=True, @@ -209,7 +209,7 @@ class ResPartnerRevisionChange(models.Model): ) # Fields storing the previous partner's values (saved when the - # revision is applied) + # changeset is applied) old_value_char = fields.Char(string='Old', readonly=True) old_value_date = fields.Date(string='Old', @@ -288,11 +288,11 @@ class ResPartnerRevisionChange(models.Model): _new_value_fields) @api.one - @api.depends('revision_id.partner_id.*') + @api.depends('changeset_id.partner_id.*') def _compute_origin_values(self): field_name = self.get_field_for_type(self.field_id, 'origin') if self.state == 'draft': - value = self.revision_id.partner_id[self.field_id.name] + value = self.changeset_id.partner_id[self.field_id.name] else: old_field = self.get_field_for_type(self.field_id, 'old') value = self[old_field] @@ -334,8 +334,8 @@ class ResPartnerRevisionChange(models.Model): """ Copy the value of the partner to the 'old' field """ for change in self: # copy the existing partner's value for the history - old_value_for_write = self._value_for_revision( - change.revision_id.partner_id, + old_value_for_write = self._value_for_changeset( + change.changeset_id.partner_id, change.field_id.name ) old_field_name = self.get_field_for_type(change.field_id, 'old') @@ -343,16 +343,16 @@ class ResPartnerRevisionChange(models.Model): @api.multi def apply(self): - """ Apply the change on the revision's partner + """ Apply the change on the changeset's partner It is optimized thus that it makes only one write on the partner - per revision if many changes are applied at once. + per changeset if many changes are applied at once. """ changes_ok = self.browse() - key = attrgetter('revision_id') - for revision, changes in groupby(self.sorted(key=key), key=key): + key = attrgetter('changeset_id') + for changeset, changes in groupby(self.sorted(key=key), key=key): values = {} - partner = revision.partner_id + partner = changeset.partner_id for change in changes: if change.state in ('cancel', 'done'): continue @@ -370,22 +370,22 @@ class ResPartnerRevisionChange(models.Model): if not values: continue - previous_revisions = self.env['res.partner.revision'].search( - [('date', '<', revision.date), + previous_changesets = self.env['res.partner.changeset'].search( + [('date', '<', changeset.date), ('state', '=', 'draft'), - ('partner_id', '=', revision.partner_id.id), + ('partner_id', '=', changeset.partner_id.id), ], limit=1, ) - if previous_revisions: + if previous_changesets: raise exceptions.Warning( _('This change cannot be applied because a previous ' - 'revision for the same partner is pending.\n' - 'Apply all the anterior revisions before applying ' + 'changeset for the same partner is pending.\n' + 'Apply all the anterior changesets before applying ' 'this one.') ) - partner.with_context(__no_revision=True).write(values) + partner.with_context(__no_changeset=True).write(values) changes_ok.write({'state': 'done'}) @@ -411,8 +411,8 @@ class ResPartnerRevisionChange(models.Model): return model_field_def.convert_to_write(value) @api.model - def _value_for_revision(self, record, field_name, value=_NO_VALUE): - """ Return a value from the record ready to write in a revision field + def _value_for_changeset(self, record, field_name, value=_NO_VALUE): + """ Return a value from the record ready to write in a changeset field :param record: modified record :param field_name: name of the modified field @@ -431,23 +431,23 @@ class ResPartnerRevisionChange(models.Model): return value @api.multi - def _prepare_revision_change(self, record, rule, field_name, value): - """ Prepare data for a revision change + def _prepare_changeset_change(self, record, rule, field_name, value): + """ Prepare data for a changeset change - It returns a dict of the values to write on the revision change + It returns a dict of the values to write on the changeset change and a boolean that indicates if the value should be popped out of the values to write on the model. :returns: dict of values, boolean """ new_field_name = self.get_field_for_type(rule.field_id, 'new') - new_value = self._value_for_revision(record, field_name, value=value) + new_value = self._value_for_changeset(record, field_name, value=value) change = { new_field_name: new_value, 'field_id': rule.field_id.id, } pop_value = False - if (not self.env.context.get('__revision_rules') or + if (not self.env.context.get('__changeset_rules') or rule.action == 'auto'): change['state'] = 'done' elif rule.action == 'validate': @@ -462,16 +462,16 @@ class ResPartnerRevisionChange(models.Model): # button, but since we short circuit the 'apply', we # directly set the 'old' value here old_field_name = self.get_field_for_type(rule.field_id, 'old') - # get values ready to write as expected by the revision + # get values ready to write as expected by the changeset # (for instance, a many2one is written in a reference # field) - origin_value = self._value_for_revision(record, field_name) + origin_value = self._value_for_changeset(record, field_name) change[old_field_name] = origin_value return change, pop_value def fields_view_get(self, *args, **kwargs): - _super = super(ResPartnerRevisionChange, self) + _super = super(ResPartnerChangesetChange, self) result = _super.fields_view_get(*args, **kwargs) if result['type'] != 'form': return diff --git a/partner_revision/models/revision_field_rule.py b/partner_revision/models/revision_field_rule.py index 91cff6e9d..31eda92e8 100644 --- a/partner_revision/models/revision_field_rule.py +++ b/partner_revision/models/revision_field_rule.py @@ -23,9 +23,9 @@ from openerp import models, fields, api from openerp.tools.cache import ormcache -class RevisionFieldRule(models.Model): - _name = 'revision.field.rule' - _description = 'Revision Field Rules' +class ChangesetFieldRule(models.Model): + _name = 'changeset.field.rule' + _description = 'Changeset Field Rules' _rec_name = 'field_id' model_id = fields.Many2one(comodel_name='ir.model', @@ -62,18 +62,18 @@ class RevisionFieldRule(models.Model): @api.model def create(self, vals): - record = super(RevisionFieldRule, self).create(vals) + record = super(ChangesetFieldRule, self).create(vals) self.clear_caches() return record @api.multi def write(self, vals): - result = super(RevisionFieldRule, self).write(vals) + result = super(ChangesetFieldRule, self).write(vals) self.clear_caches() return result @api.multi def unlink(self): - result = super(RevisionFieldRule, self).unlink() + result = super(ChangesetFieldRule, self).unlink() self.clear_caches() return result diff --git a/partner_revision/security/ir.model.access.csv b/partner_revision/security/ir.model.access.csv index 5b2f6743f..7d1346de2 100644 --- a/partner_revision/security/ir.model.access.csv +++ b/partner_revision/security/ir.model.access.csv @@ -1,10 +1,10 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_view_revision_field_rule_partner_manager,revision field rules for partner managers,model_revision_field_rule,base.group_partner_manager,1,0,0,0 -access_view_revision_field_rule_user,revision field rules for revision users,model_revision_field_rule,group_revision_user,1,0,0,0 -access_view_revision_field_rule_manager,revision field rules for revision managers,model_revision_field_rule,group_revision_user,1,1,1,1 -access_view_res_partner_revision_partner_manager,revision for partner managers,model_res_partner_revision,base.group_partner_manager,1,0,1,0 -access_view_res_partner_revision_change_partner_manager,revision change for partner managers,model_res_partner_revision_change,base.group_partner_manager,1,0,1,0 -access_view_res_partner_revision_user,revision for revision users,model_res_partner_revision,group_revision_user,1,1,1,0 -access_view_res_partner_revision_change_user,revision change for revision users,model_res_partner_revision_change,group_revision_user,1,1,1,0 -access_view_res_partner_revision_manager,revision for revision managers,model_res_partner_revision,group_revision_manager,1,1,1,1 -access_view_res_partner_revision_change_manager,revision change for revision managers,model_res_partner_revision_change,group_revision_manager,1,1,1,1 +access_view_changeset_field_rule_partner_manager,changeset field rules for partner managers,model_changeset_field_rule,base.group_partner_manager,1,0,0,0 +access_view_changeset_field_rule_user,changeset field rules for changeset users,model_changeset_field_rule,group_changeset_user,1,0,0,0 +access_view_changeset_field_rule_manager,changeset field rules for changeset managers,model_changeset_field_rule,group_changeset_user,1,1,1,1 +access_view_res_partner_changeset_partner_manager,changeset for partner managers,model_res_partner_changeset,base.group_partner_manager,1,0,1,0 +access_view_res_partner_changeset_change_partner_manager,changeset change for partner managers,model_res_partner_changeset_change,base.group_partner_manager,1,0,1,0 +access_view_res_partner_changeset_user,changeset for changeset users,model_res_partner_changeset,group_changeset_user,1,1,1,0 +access_view_res_partner_changeset_change_user,changeset change for changeset users,model_res_partner_changeset_change,group_changeset_user,1,1,1,0 +access_view_res_partner_changeset_manager,changeset for changeset managers,model_res_partner_changeset,group_changeset_manager,1,1,1,1 +access_view_res_partner_changeset_change_manager,changeset change for changeset managers,model_res_partner_changeset_change,group_changeset_manager,1,1,1,1 diff --git a/partner_revision/security/security.xml b/partner_revision/security/security.xml index 91f33fe1a..23103d09a 100644 --- a/partner_revision/security/security.xml +++ b/partner_revision/security/security.xml @@ -2,16 +2,16 @@ - - Revision Configuration - The user will have an access to the configuration of the revision rules. + + Changeset Configuration + The user will have an access to the configuration of the changeset rules. - - Revisions Validations - The user will be able to apply or reject revisions. - + + Changesets Validations + The user will be able to apply or reject changesets. + diff --git a/partner_revision/tests/common.py b/partner_revision/tests/common.py index 7e9f90cbe..eca72af32 100644 --- a/partner_revision/tests/common.py +++ b/partner_revision/tests/common.py @@ -20,27 +20,27 @@ # -class RevisionMixin(object): +class ChangesetMixin(object): - def assert_revision(self, partner, expected_changes): - """ Check if a revision has been created according to expected values + def assert_changeset(self, partner, expected_changes): + """ Check if a changeset has been created according to expected values - The partner should have no prior revision than the one created in the - test (so it has exactly 1 revision). + The partner should have no prior changeset than the one created in the + test (so it has exactly 1 changeset). The expected changes are tuples with (field, origin_value, new_value, state) - :param partner: record of partner having a revision + :param partner: record of partner having a changeset :param expected_changes: contains tuples with the changes :type expected_changes: list(tuple)) """ - revision = self.env['res.partner.revision'].search( + changeset = self.env['res.partner.changeset'].search( [('partner_id', '=', partner.id)], ) - self.assertEqual(len(revision), 1, - "1 revision expected, got %s" % (revision,)) - changes = revision.change_ids + self.assertEqual(len(changeset), 1, + "1 changeset expected, got %s" % (changeset,)) + changes = changeset.change_ids missing = [] for expected_change in expected_changes: for change in changes: @@ -67,15 +67,15 @@ class RevisionMixin(object): if message: raise AssertionError('Changes do not match\n\n:%s' % message) - def _create_revision(self, partner, changes): - """ Create a revision and its associated changes + def _create_changeset(self, partner, changes): + """ Create a changeset and its associated changes :param partner: 'res.partner' record :param changes: list of changes [(field, new value, state)] - :returns: 'res.partner.revision' record + :returns: 'res.partner.changeset' record """ - RevisionChange = self.env['res.partner.revision.change'] - get_field = RevisionChange.get_field_for_type + ChangesetChange = self.env['res.partner.changeset.change'] + get_field = ChangesetChange.get_field_for_type change_values = [] for field, value, state in changes: change = { @@ -90,4 +90,4 @@ class RevisionMixin(object): 'partner_id': partner.id, 'change_ids': change_values, } - return self.env['res.partner.revision'].create(values) + return self.env['res.partner.changeset'].create(values) diff --git a/partner_revision/tests/test_revision_field_type.py b/partner_revision/tests/test_revision_field_type.py index f25f7488b..66e570075 100644 --- a/partner_revision/tests/test_revision_field_type.py +++ b/partner_revision/tests/test_revision_field_type.py @@ -20,14 +20,14 @@ # from openerp.tests import common -from .common import RevisionMixin +from .common import ChangesetMixin -class TestRevisionFieldType(RevisionMixin, common.TransactionCase): - """ Check that revision changes are stored expectingly to their types """ +class TestChangesetFieldType(ChangesetMixin, common.TransactionCase): + """ Check that changeset changes are stored expectingly to their types """ def _setup_rules(self): - RevisionFieldRule = self.env['revision.field.rule'] + ChangesetFieldRule = self.env['changeset.field.rule'] partner_model_id = self.env.ref('base.model_res_partner').id fields = (('char', 'ref'), ('text', 'comment'), @@ -50,217 +50,217 @@ class TestRevisionFieldType(RevisionMixin, common.TransactionCase): # set attribute such as 'self.field_char' is a # ir.model.fields record of the field res_partner.ref setattr(self, attr_name, field_record) - RevisionFieldRule.create({ + ChangesetFieldRule.create({ 'model_id': partner_model_id, 'field_id': field_record.id, 'action': 'validate', }) def setUp(self): - super(TestRevisionFieldType, self).setUp() + super(TestChangesetFieldType, self).setUp() self._setup_rules() self.partner = self.env['res.partner'].create({ 'name': 'Original Name', 'street': 'Original Street', }) - def test_new_revision_char(self): - """ Add a new revision on a Char field """ - self.partner.with_context(__revision_rules=True).write({ + def test_new_changeset_char(self): + """ Add a new changeset on a Char field """ + self.partner.with_context(__changeset_rules=True).write({ self.field_char.name: 'New value', }) - self.assert_revision( + self.assert_changeset( self.partner, [(self.field_char, self.partner[self.field_char.name], 'New value', 'draft'), ] ) - def test_new_revision_text(self): - """ Add a new revision on a Text field """ - self.partner.with_context(__revision_rules=True).write({ + def test_new_changeset_text(self): + """ Add a new changeset on a Text field """ + self.partner.with_context(__changeset_rules=True).write({ self.field_text.name: 'New comment\non 2 lines', }) - self.assert_revision( + self.assert_changeset( self.partner, [(self.field_text, self.partner[self.field_text.name], 'New comment\non 2 lines', 'draft'), ] ) - def test_new_revision_boolean(self): - """ Add a new revision on a Boolean field """ - # ensure the revision has to change the value - self.partner.with_context(__no_revision=True).write({ + def test_new_changeset_boolean(self): + """ Add a new changeset on a Boolean field """ + # ensure the changeset has to change the value + self.partner.with_context(__no_changeset=True).write({ self.field_boolean.name: False, }) - self.partner.with_context(__revision_rules=True).write({ + self.partner.with_context(__changeset_rules=True).write({ self.field_boolean.name: True, }) - self.assert_revision( + self.assert_changeset( self.partner, [(self.field_boolean, self.partner[self.field_boolean.name], True, 'draft'), ] ) - def test_new_revision_date(self): - """ Add a new revision on a Date field """ - self.partner.with_context(__revision_rules=True).write({ + def test_new_changeset_date(self): + """ Add a new changeset on a Date field """ + self.partner.with_context(__changeset_rules=True).write({ self.field_date.name: '2015-09-15', }) - self.assert_revision( + self.assert_changeset( self.partner, [(self.field_date, self.partner[self.field_date.name], '2015-09-15', 'draft'), ] ) - def test_new_revision_integer(self): - """ Add a new revision on a Integer field """ - self.partner.with_context(__revision_rules=True).write({ + def test_new_changeset_integer(self): + """ Add a new changeset on a Integer field """ + self.partner.with_context(__changeset_rules=True).write({ self.field_integer.name: 42, }) - self.assert_revision( + self.assert_changeset( self.partner, [(self.field_integer, self.partner[self.field_integer.name], 42, 'draft'), ] ) - def test_new_revision_float(self): - """ Add a new revision on a Float field """ - self.partner.with_context(__revision_rules=True).write({ + def test_new_changeset_float(self): + """ Add a new changeset on a Float field """ + self.partner.with_context(__changeset_rules=True).write({ self.field_float.name: 3.1415, }) - self.assert_revision( + self.assert_changeset( self.partner, [(self.field_float, self.partner[self.field_float.name], 3.1415, 'draft'), ] ) - def test_new_revision_selection(self): - """ Add a new revision on a Selection field """ - self.partner.with_context(__revision_rules=True).write({ + def test_new_changeset_selection(self): + """ Add a new changeset on a Selection field """ + self.partner.with_context(__changeset_rules=True).write({ self.field_selection.name: 'delivery', }) - self.assert_revision( + self.assert_changeset( self.partner, [(self.field_selection, self.partner[self.field_selection.name], 'delivery', 'draft'), ] ) - def test_new_revision_many2one(self): - """ Add a new revision on a Many2one field """ - self.partner.with_context(__no_revision=True).write({ + def test_new_changeset_many2one(self): + """ Add a new changeset on a Many2one field """ + self.partner.with_context(__no_changeset=True).write({ self.field_many2one.name: self.env.ref('base.fr').id, }) - self.partner.with_context(__revision_rules=True).write({ + self.partner.with_context(__changeset_rules=True).write({ self.field_many2one.name: self.env.ref('base.ch').id, }) - self.assert_revision( + self.assert_changeset( self.partner, [(self.field_many2one, self.partner[self.field_many2one.name], self.env.ref('base.ch'), 'draft'), ] ) - def test_new_revision_many2many(self): - """ Add a new revision on a Many2many field is not supported """ + def test_new_changeset_many2many(self): + """ Add a new changeset on a Many2many field is not supported """ with self.assertRaises(NotImplementedError): - self.partner.with_context(__revision_rules=True).write({ + self.partner.with_context(__changeset_rules=True).write({ self.field_many2many.name: [self.env.ref('base.ch').id], }) - def test_new_revision_one2many(self): - """ Add a new revision on a One2many field is not supported """ + def test_new_changeset_one2many(self): + """ Add a new changeset on a One2many field is not supported """ with self.assertRaises(NotImplementedError): - self.partner.with_context(__revision_rules=True).write({ + self.partner.with_context(__changeset_rules=True).write({ self.field_one2many.name: [self.env.ref('base.user_root').id], }) - def test_new_revision_binary(self): - """ Add a new revision on a Binary field is not supported """ + def test_new_changeset_binary(self): + """ Add a new changeset on a Binary field is not supported """ with self.assertRaises(NotImplementedError): - self.partner.with_context(__revision_rules=True).write({ + self.partner.with_context(__changeset_rules=True).write({ self.field_binary.name: '', }) def test_apply_char(self): """ Apply a change on a Char field """ changes = [(self.field_char, 'New Ref', 'draft')] - revision = self._create_revision(self.partner, changes) - revision.change_ids.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.change_ids.apply() self.assertEqual(self.partner[self.field_char.name], 'New Ref') def test_apply_text(self): """ Apply a change on a Text field """ changes = [(self.field_text, 'New comment\non 2 lines', 'draft')] - revision = self._create_revision(self.partner, changes) - revision.change_ids.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.change_ids.apply() self.assertEqual(self.partner[self.field_text.name], 'New comment\non 2 lines') def test_apply_boolean(self): """ Apply a change on a Boolean field """ - # ensure the revision has to change the value + # ensure the changeset has to change the value self.partner.write({self.field_boolean.name: False}) changes = [(self.field_boolean, True, 'draft')] - revision = self._create_revision(self.partner, changes) - revision.change_ids.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.change_ids.apply() self.assertEqual(self.partner[self.field_boolean.name], True) changes = [(self.field_boolean, False, 'draft')] - revision = self._create_revision(self.partner, changes) - revision.change_ids.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.change_ids.apply() self.assertEqual(self.partner[self.field_boolean.name], False) def test_apply_date(self): """ Apply a change on a Date field """ changes = [(self.field_date, '2015-09-15', 'draft')] - revision = self._create_revision(self.partner, changes) - revision.change_ids.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.change_ids.apply() self.assertAlmostEqual(self.partner[self.field_date.name], '2015-09-15') def test_apply_integer(self): """ Apply a change on a Integer field """ changes = [(self.field_integer, 42, 'draft')] - revision = self._create_revision(self.partner, changes) - revision.change_ids.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.change_ids.apply() self.assertAlmostEqual(self.partner[self.field_integer.name], 42) def test_apply_float(self): """ Apply a change on a Float field """ changes = [(self.field_float, 52.47, 'draft')] - revision = self._create_revision(self.partner, changes) - revision.change_ids.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.change_ids.apply() self.assertAlmostEqual(self.partner[self.field_float.name], 52.47) def test_apply_selection(self): """ Apply a change on a Selection field """ changes = [(self.field_selection, 'delivery', 'draft')] - revision = self._create_revision(self.partner, changes) - revision.change_ids.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.change_ids.apply() self.assertAlmostEqual(self.partner[self.field_selection.name], 'delivery') def test_apply_many2one(self): """ Apply a change on a Many2one field """ - self.partner.with_context(__no_revision=True).write({ + self.partner.with_context(__no_changeset=True).write({ self.field_many2one.name: self.env.ref('base.fr').id, }) changes = [(self.field_many2one, 'res.country,%d' % self.env.ref('base.ch').id, 'draft')] - revision = self._create_revision(self.partner, changes) - revision.change_ids.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.change_ids.apply() self.assertEqual(self.partner[self.field_many2one.name], self.env.ref('base.ch')) @@ -270,7 +270,7 @@ class TestRevisionFieldType(RevisionMixin, common.TransactionCase): self.env.ref('base.ch').id, 'draft')] with self.assertRaises(NotImplementedError): - self._create_revision(self.partner, changes) + self._create_changeset(self.partner, changes) def test_apply_one2many(self): """ Apply a change on a One2many field is not supported """ @@ -280,10 +280,10 @@ class TestRevisionFieldType(RevisionMixin, common.TransactionCase): ], 'draft')] with self.assertRaises(NotImplementedError): - self._create_revision(self.partner, changes) + self._create_changeset(self.partner, changes) def test_apply_binary(self): """ Apply a change on a Binary field is not supported """ changes = [(self.field_one2many, '', 'draft')] with self.assertRaises(NotImplementedError): - self._create_revision(self.partner, changes) + self._create_changeset(self.partner, changes) diff --git a/partner_revision/tests/test_revision_flow.py b/partner_revision/tests/test_revision_flow.py index 23eb87d62..8ad997706 100644 --- a/partner_revision/tests/test_revision_flow.py +++ b/partner_revision/tests/test_revision_flow.py @@ -23,50 +23,50 @@ from datetime import datetime, timedelta from openerp import fields, exceptions from openerp.tests import common -from .common import RevisionMixin +from .common import ChangesetMixin -class TestRevisionFlow(RevisionMixin, common.TransactionCase): - """ Check how revision are generated and applied based on the rules. +class TestChangesetFlow(ChangesetMixin, common.TransactionCase): + """ Check how changeset are generated and applied based on the rules. We do not really care about the types of the fields in this test suite, so we only use 'char' fields. We have to ensure that the - general revision flows work as expected, that is: + general changeset flows work as expected, that is: - * create a 'done' revision when a manual/system write is made on partner - * create a revision according to the revision rules when the key - '__revision_rules' is passed in the context - * apply a revision change writes the value on the partner - * apply a whole revision writes all the changes' values on the partner + * create a 'done' changeset when a manual/system write is made on partner + * create a changeset according to the changeset rules when the key + '__changeset_rules' is passed in the context + * apply a changeset change writes the value on the partner + * apply a whole changeset writes all the changes' values on the partner * changes in state 'cancel' or 'done' do not write on the partner - * when all the changes are either 'cancel' or 'done', the revision + * when all the changes are either 'cancel' or 'done', the changeset becomes 'done' """ def _setup_rules(self): - RevisionFieldRule = self.env['revision.field.rule'] + ChangesetFieldRule = self.env['changeset.field.rule'] partner_model_id = self.env.ref('base.model_res_partner').id self.field_name = self.env.ref('base.field_res_partner_name') self.field_street = self.env.ref('base.field_res_partner_street') self.field_street2 = self.env.ref('base.field_res_partner_street2') - RevisionFieldRule.create({ + ChangesetFieldRule.create({ 'model_id': partner_model_id, 'field_id': self.field_name.id, 'action': 'auto', }) - RevisionFieldRule.create({ + ChangesetFieldRule.create({ 'model_id': partner_model_id, 'field_id': self.field_street.id, 'action': 'validate', }) - RevisionFieldRule.create({ + ChangesetFieldRule.create({ 'model_id': partner_model_id, 'field_id': self.field_street2.id, 'action': 'never', }) def setUp(self): - super(TestRevisionFlow, self).setUp() + super(TestChangesetFlow, self).setUp() self._setup_rules() self.partner = self.env['res.partner'].create({ 'name': 'X', @@ -74,18 +74,18 @@ class TestRevisionFlow(RevisionMixin, common.TransactionCase): 'street2': 'street2 X', }) - def test_new_revision(self): - """ Add a new revision on a partner + def test_new_changeset(self): + """ Add a new changeset on a partner - A new revision is created when we write on a partner with - ``__revision_rules`` in the context. + A new changeset is created when we write on a partner with + ``__changeset_rules`` in the context. """ - self.partner.with_context(__revision_rules=True).write({ + self.partner.with_context(__changeset_rules=True).write({ 'name': 'Y', 'street': 'street Y', 'street2': 'street2 Y', }) - self.assert_revision( + self.assert_changeset( self.partner, [(self.field_name, 'X', 'Y', 'done'), (self.field_street, 'street X', 'street Y', 'draft'), @@ -96,12 +96,12 @@ class TestRevisionFlow(RevisionMixin, common.TransactionCase): self.assertEqual(self.partner.street, 'street X') self.assertEqual(self.partner.street2, 'street2 X') - def test_new_revision_empty_value(self): - """ Create a revision change that empty a value """ - self.partner.with_context(__revision_rules=True).write({ + def test_new_changeset_empty_value(self): + """ Create a changeset change that empty a value """ + self.partner.with_context(__changeset_rules=True).write({ 'street': False, }) - self.assert_revision( + self.assert_changeset( self.partner, [(self.field_street, 'street X', False, 'draft')] ) @@ -109,14 +109,14 @@ class TestRevisionFlow(RevisionMixin, common.TransactionCase): def test_manual_edition(self): """ A manual edition of a partner should always be applied - But should create a 'done' revision + But should create a 'done' changeset """ self.partner.write({ 'name': 'Y', 'street': 'street Y', 'street2': 'street2 Y', }) - self.assert_revision( + self.assert_changeset( self.partner, [(self.field_name, 'X', 'Y', 'done'), (self.field_street, 'street X', 'street Y', 'done'), @@ -128,22 +128,22 @@ class TestRevisionFlow(RevisionMixin, common.TransactionCase): self.assertEqual(self.partner.street2, 'street2 Y') def test_apply_change(self): - """ Apply a revision change on a partner """ + """ Apply a changeset change on a partner """ changes = [ (self.field_name, 'Y', 'draft'), ] - revision = self._create_revision(self.partner, changes) - revision.change_ids.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.change_ids.apply() self.assertEqual(self.partner.name, 'Y') - self.assertEqual(revision.change_ids.state, 'done') + self.assertEqual(changeset.change_ids.state, 'done') def test_apply_done_change(self): """ Done changes do not apply (already applied) """ changes = [ (self.field_name, 'Y', 'done'), ] - revision = self._create_revision(self.partner, changes) - revision.change_ids.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.change_ids.apply() self.assertEqual(self.partner.name, 'X') def test_apply_cancel_change(self): @@ -151,8 +151,8 @@ class TestRevisionFlow(RevisionMixin, common.TransactionCase): changes = [ (self.field_name, 'Y', 'cancel'), ] - revision = self._create_revision(self.partner, changes) - revision.change_ids.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.change_ids.apply() self.assertEqual(self.partner.name, 'X') def test_apply_empty_value(self): @@ -160,8 +160,8 @@ class TestRevisionFlow(RevisionMixin, common.TransactionCase): changes = [ (self.field_street, False, 'draft'), ] - revision = self._create_revision(self.partner, changes) - revision.change_ids.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.change_ids.apply() self.assertFalse(self.partner.street) def test_apply_change_loop(self): @@ -171,83 +171,83 @@ class TestRevisionFlow(RevisionMixin, common.TransactionCase): (self.field_street, 'street Y', 'draft'), (self.field_street2, 'street2 Y', 'draft'), ] - revision = self._create_revision(self.partner, changes) - revision.change_ids.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.change_ids.apply() self.assertEqual(self.partner.name, 'Y') self.assertEqual(self.partner.street, 'street Y') self.assertEqual(self.partner.street2, 'street2 Y') def test_apply(self): - """ Apply a full revision on a partner """ + """ Apply a full changeset on a partner """ changes = [ (self.field_name, 'Y', 'draft'), (self.field_street, 'street Y', 'draft'), (self.field_street2, 'street2 Y', 'draft'), ] - revision = self._create_revision(self.partner, changes) - revision.apply() + changeset = self._create_changeset(self.partner, changes) + changeset.apply() self.assertEqual(self.partner.name, 'Y') self.assertEqual(self.partner.street, 'street Y') self.assertEqual(self.partner.street2, 'street2 Y') - def test_revision_state_on_done(self): - """ Check that revision state becomes done when changes are done """ + def test_changeset_state_on_done(self): + """ Check that changeset state becomes done when changes are done """ changes = [(self.field_name, 'Y', 'draft')] - revision = self._create_revision(self.partner, changes) - self.assertEqual(revision.state, 'draft') - revision.change_ids.apply() - self.assertEqual(revision.state, 'done') + changeset = self._create_changeset(self.partner, changes) + self.assertEqual(changeset.state, 'draft') + changeset.change_ids.apply() + self.assertEqual(changeset.state, 'done') - def test_revision_state_on_cancel(self): + def test_changeset_state_on_cancel(self): """ Check that rev. state becomes done when changes are canceled """ changes = [(self.field_name, 'Y', 'draft')] - revision = self._create_revision(self.partner, changes) - self.assertEqual(revision.state, 'draft') - revision.change_ids.cancel() - self.assertEqual(revision.state, 'done') + changeset = self._create_changeset(self.partner, changes) + self.assertEqual(changeset.state, 'draft') + changeset.change_ids.cancel() + self.assertEqual(changeset.state, 'done') - def test_revision_state(self): - """ Check that revision state becomes done with multiple changes """ + def test_changeset_state(self): + """ Check that changeset state becomes done with multiple changes """ changes = [ (self.field_name, 'Y', 'draft'), (self.field_street, 'street Y', 'draft'), (self.field_street2, 'street2 Y', 'draft'), ] - revision = self._create_revision(self.partner, changes) - self.assertEqual(revision.state, 'draft') - revision.apply() - self.assertEqual(revision.state, 'done') + changeset = self._create_changeset(self.partner, changes) + self.assertEqual(changeset.state, 'draft') + changeset.apply() + self.assertEqual(changeset.state, 'done') - def test_apply_revision_with_other_pending(self): - """ Error when applying when previous pending revisions exist """ + def test_apply_changeset_with_other_pending(self): + """ Error when applying when previous pending changesets exist """ changes = [(self.field_name, 'Y', 'draft')] - old_revision = self._create_revision(self.partner, changes) - # if the date is the same, both revision can be applied + old_changeset = self._create_changeset(self.partner, changes) + # if the date is the same, both changeset can be applied to_string = fields.Datetime.to_string - old_revision.date = to_string(datetime.now() - timedelta(days=1)) + old_changeset.date = to_string(datetime.now() - timedelta(days=1)) changes = [(self.field_name, 'Z', 'draft')] - revision = self._create_revision(self.partner, changes) + changeset = self._create_changeset(self.partner, changes) with self.assertRaises(exceptions.Warning): - revision.change_ids.apply() + changeset.change_ids.apply() - def test_apply_different_revisions(self): - """ Apply different revisions at once """ + def test_apply_different_changesets(self): + """ Apply different changesets at once """ partner2 = self.env['res.partner'].create({'name': 'P2'}) changes = [ (self.field_name, 'Y', 'draft'), (self.field_street, 'street Y', 'draft'), (self.field_street2, 'street2 Y', 'draft'), ] - revision = self._create_revision(self.partner, changes) - revision2 = self._create_revision(partner2, changes) - self.assertEqual(revision.state, 'draft') - self.assertEqual(revision2.state, 'draft') - (revision + revision2).apply() + changeset = self._create_changeset(self.partner, changes) + changeset2 = self._create_changeset(partner2, changes) + self.assertEqual(changeset.state, 'draft') + self.assertEqual(changeset2.state, 'draft') + (changeset + changeset2).apply() self.assertEqual(self.partner.name, 'Y') self.assertEqual(self.partner.street, 'street Y') self.assertEqual(self.partner.street2, 'street2 Y') self.assertEqual(partner2.name, 'Y') self.assertEqual(partner2.street, 'street Y') self.assertEqual(partner2.street2, 'street2 Y') - self.assertEqual(revision.state, 'done') - self.assertEqual(revision2.state, 'done') + self.assertEqual(changeset.state, 'done') + self.assertEqual(changeset2.state, 'done') diff --git a/partner_revision/tests/test_revision_origin.py b/partner_revision/tests/test_revision_origin.py index 05bcece4d..2eb822707 100644 --- a/partner_revision/tests/test_revision_origin.py +++ b/partner_revision/tests/test_revision_origin.py @@ -20,10 +20,10 @@ # from openerp.tests import common -from .common import RevisionMixin +from .common import ChangesetMixin -class TestRevisionOrigin(RevisionMixin, common.TransactionCase): +class TestChangesetOrigin(ChangesetMixin, common.TransactionCase): """ Check that origin - old fields are stored as expected. 'origin' fields dynamically read fields from the partner when the state @@ -33,17 +33,17 @@ class TestRevisionOrigin(RevisionMixin, common.TransactionCase): """ def _setup_rules(self): - RevisionFieldRule = self.env['revision.field.rule'] + ChangesetFieldRule = self.env['changeset.field.rule'] partner_model_id = self.env.ref('base.model_res_partner').id self.field_name = self.env.ref('base.field_res_partner_name') - RevisionFieldRule.create({ + ChangesetFieldRule.create({ 'model_id': partner_model_id, 'field_id': self.field_name.id, 'action': 'validate', }) def setUp(self): - super(TestRevisionOrigin, self).setUp() + super(TestChangesetOrigin, self).setUp() self._setup_rules() self.partner = self.env['res.partner'].create({ 'name': 'X', @@ -54,11 +54,11 @@ class TestRevisionOrigin(RevisionMixin, common.TransactionCase): According to the state of the change. """ - self.partner.with_context(__revision_rules=True).write({ + self.partner.with_context(__changeset_rules=True).write({ 'name': 'Y', }) - revision = self.partner.revision_ids - change = revision.change_ids + changeset = self.partner.changeset_ids + change = changeset.change_ids self.assertEqual(self.partner.name, 'X') self.assertEqual(change.origin_value_char, 'X') self.assertEqual(change.origin_value_display, 'X') @@ -77,11 +77,11 @@ class TestRevisionOrigin(RevisionMixin, common.TransactionCase): According to the state of the change. """ - self.partner.with_context(__revision_rules=True).write({ + self.partner.with_context(__changeset_rules=True).write({ 'name': 'Y', }) - revision = self.partner.revision_ids - change = revision.change_ids + changeset = self.partner.changeset_ids + change = changeset.change_ids self.assertEqual(self.partner.name, 'X') self.assertEqual(change.origin_value_char, 'X') self.assertEqual(change.origin_value_display, 'X') @@ -97,11 +97,11 @@ class TestRevisionOrigin(RevisionMixin, common.TransactionCase): def test_old_field_of_change_with_apply(self): """ Old field is stored when the change is applied """ - self.partner.with_context(__revision_rules=True).write({ + self.partner.with_context(__changeset_rules=True).write({ 'name': 'Y', }) - revision = self.partner.revision_ids - change = revision.change_ids + changeset = self.partner.changeset_ids + change = changeset.change_ids self.assertEqual(self.partner.name, 'X') self.assertFalse(change.old_value_char) self.partner.write({'name': 'A'}) @@ -113,11 +113,11 @@ class TestRevisionOrigin(RevisionMixin, common.TransactionCase): def test_old_field_of_change_with_cancel(self): """ Old field is stored when the change is canceled """ - self.partner.with_context(__revision_rules=True).write({ + self.partner.with_context(__changeset_rules=True).write({ 'name': 'Y', }) - revision = self.partner.revision_ids - change = revision.change_ids + changeset = self.partner.changeset_ids + change = changeset.change_ids self.assertEqual(self.partner.name, 'X') self.assertFalse(change.old_value_char) self.partner.write({'name': 'A'}) diff --git a/partner_revision/views/menu.xml b/partner_revision/views/menu.xml index 1dd90902f..f13bf6dd6 100644 --- a/partner_revision/views/menu.xml +++ b/partner_revision/views/menu.xml @@ -1,9 +1,9 @@ - diff --git a/partner_revision/views/res_partner_revision_views.xml b/partner_revision/views/res_partner_revision_views.xml index d4df72cec..d871bc851 100644 --- a/partner_revision/views/res_partner_revision_views.xml +++ b/partner_revision/views/res_partner_revision_views.xml @@ -1,11 +1,11 @@ - - res.partner.revision.tree - res.partner.revision + + res.partner.changeset.tree + res.partner.changeset - + @@ -13,11 +13,11 @@ - - res.partner.revision.form - res.partner.revision + + res.partner.changeset.form + res.partner.changeset - +
- + - + @@ -64,11 +64,11 @@
- - res.partner.revision.change.form - res.partner.revision.change + + res.partner.changeset.change.form + res.partner.changeset.change - +
@@ -31,9 +31,9 @@ - + diff --git a/partner_revision/views/revision_field_rule_views.xml b/partner_revision/views/revision_field_rule_views.xml index 5690fcb0e..f0bbe0c3e 100644 --- a/partner_revision/views/revision_field_rule_views.xml +++ b/partner_revision/views/revision_field_rule_views.xml @@ -1,11 +1,11 @@ - - revision.field.rule.tree - revision.field.rule + + changeset.field.rule.tree + changeset.field.rule - + @@ -13,12 +13,12 @@ - - revision.field.rule.form - revision.field.rule + + changeset.field.rule.form + changeset.field.rule - - + + - - revision.field.rule.search - revision.field.rule + + changeset.field.rule.search + changeset.field.rule - + @@ -44,19 +44,20 @@ - - Revision Fields Rules + + Changeset Fields Rules ir.actions.act_window - revision.field.rule + changeset.field.rule form tree,form - + - + action="action_changeset_field_rule_view"/> From 563b8ff4f0dfb7d2512d29c58744c183d3cb3668 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Fri, 18 Sep 2015 16:45:20 +0200 Subject: [PATCH 21/42] Rename addon according to the new term: partner_changeset --- {partner_revision => partner_changeset}/README.rst | 0 {partner_revision => partner_changeset}/__init__.py | 0 {partner_revision => partner_changeset}/__openerp__.py | 4 ++-- partner_changeset/models/__init__.py | 5 +++++ .../models/changeset_field_rule.py | 0 .../models/res_partner.py | 0 .../models/res_partner_changeset.py | 0 .../security/ir.model.access.csv | 0 .../security/security.xml | 0 partner_changeset/tests/__init__.py | 5 +++++ {partner_revision => partner_changeset}/tests/common.py | 0 .../tests/test_changeset_field_type.py | 0 .../tests/test_changeset_flow.py | 0 .../tests/test_changeset_origin.py | 0 .../views/changeset_field_rule_views.xml | 0 {partner_revision => partner_changeset}/views/menu.xml | 0 .../views/res_partner_changeset_views.xml | 0 .../views/res_partner_views.xml | 4 ++-- partner_revision/models/__init__.py | 5 ----- partner_revision/tests/__init__.py | 5 ----- 20 files changed, 14 insertions(+), 14 deletions(-) rename {partner_revision => partner_changeset}/README.rst (100%) rename {partner_revision => partner_changeset}/__init__.py (100%) rename {partner_revision => partner_changeset}/__openerp__.py (92%) create mode 100644 partner_changeset/models/__init__.py rename partner_revision/models/revision_field_rule.py => partner_changeset/models/changeset_field_rule.py (100%) rename {partner_revision => partner_changeset}/models/res_partner.py (100%) rename partner_revision/models/res_partner_revision.py => partner_changeset/models/res_partner_changeset.py (100%) rename {partner_revision => partner_changeset}/security/ir.model.access.csv (100%) rename {partner_revision => partner_changeset}/security/security.xml (100%) create mode 100644 partner_changeset/tests/__init__.py rename {partner_revision => partner_changeset}/tests/common.py (100%) rename partner_revision/tests/test_revision_field_type.py => partner_changeset/tests/test_changeset_field_type.py (100%) rename partner_revision/tests/test_revision_flow.py => partner_changeset/tests/test_changeset_flow.py (100%) rename partner_revision/tests/test_revision_origin.py => partner_changeset/tests/test_changeset_origin.py (100%) rename partner_revision/views/revision_field_rule_views.xml => partner_changeset/views/changeset_field_rule_views.xml (100%) rename {partner_revision => partner_changeset}/views/menu.xml (100%) rename partner_revision/views/res_partner_revision_views.xml => partner_changeset/views/res_partner_changeset_views.xml (100%) rename {partner_revision => partner_changeset}/views/res_partner_views.xml (89%) delete mode 100644 partner_revision/models/__init__.py delete mode 100644 partner_revision/tests/__init__.py diff --git a/partner_revision/README.rst b/partner_changeset/README.rst similarity index 100% rename from partner_revision/README.rst rename to partner_changeset/README.rst diff --git a/partner_revision/__init__.py b/partner_changeset/__init__.py similarity index 100% rename from partner_revision/__init__.py rename to partner_changeset/__init__.py diff --git a/partner_revision/__openerp__.py b/partner_changeset/__openerp__.py similarity index 92% rename from partner_revision/__openerp__.py rename to partner_changeset/__openerp__.py index 8665bd91c..af00c9169 100644 --- a/partner_revision/__openerp__.py +++ b/partner_changeset/__openerp__.py @@ -30,8 +30,8 @@ 'data': ['security/security.xml', 'security/ir.model.access.csv', 'views/menu.xml', - 'views/res_partner_revision_views.xml', - 'views/revision_field_rule_views.xml', + 'views/res_partner_changeset_views.xml', + 'views/changeset_field_rule_views.xml', 'views/res_partner_views.xml', ], 'test': [], diff --git a/partner_changeset/models/__init__.py b/partner_changeset/models/__init__.py new file mode 100644 index 000000000..bb2a129f9 --- /dev/null +++ b/partner_changeset/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from . import res_partner +from . import res_partner_changeset +from . import changeset_field_rule diff --git a/partner_revision/models/revision_field_rule.py b/partner_changeset/models/changeset_field_rule.py similarity index 100% rename from partner_revision/models/revision_field_rule.py rename to partner_changeset/models/changeset_field_rule.py diff --git a/partner_revision/models/res_partner.py b/partner_changeset/models/res_partner.py similarity index 100% rename from partner_revision/models/res_partner.py rename to partner_changeset/models/res_partner.py diff --git a/partner_revision/models/res_partner_revision.py b/partner_changeset/models/res_partner_changeset.py similarity index 100% rename from partner_revision/models/res_partner_revision.py rename to partner_changeset/models/res_partner_changeset.py diff --git a/partner_revision/security/ir.model.access.csv b/partner_changeset/security/ir.model.access.csv similarity index 100% rename from partner_revision/security/ir.model.access.csv rename to partner_changeset/security/ir.model.access.csv diff --git a/partner_revision/security/security.xml b/partner_changeset/security/security.xml similarity index 100% rename from partner_revision/security/security.xml rename to partner_changeset/security/security.xml diff --git a/partner_changeset/tests/__init__.py b/partner_changeset/tests/__init__.py new file mode 100644 index 000000000..3ff760e02 --- /dev/null +++ b/partner_changeset/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from . import test_changeset_flow +from . import test_changeset_field_type +from . import test_changeset_origin diff --git a/partner_revision/tests/common.py b/partner_changeset/tests/common.py similarity index 100% rename from partner_revision/tests/common.py rename to partner_changeset/tests/common.py diff --git a/partner_revision/tests/test_revision_field_type.py b/partner_changeset/tests/test_changeset_field_type.py similarity index 100% rename from partner_revision/tests/test_revision_field_type.py rename to partner_changeset/tests/test_changeset_field_type.py diff --git a/partner_revision/tests/test_revision_flow.py b/partner_changeset/tests/test_changeset_flow.py similarity index 100% rename from partner_revision/tests/test_revision_flow.py rename to partner_changeset/tests/test_changeset_flow.py diff --git a/partner_revision/tests/test_revision_origin.py b/partner_changeset/tests/test_changeset_origin.py similarity index 100% rename from partner_revision/tests/test_revision_origin.py rename to partner_changeset/tests/test_changeset_origin.py diff --git a/partner_revision/views/revision_field_rule_views.xml b/partner_changeset/views/changeset_field_rule_views.xml similarity index 100% rename from partner_revision/views/revision_field_rule_views.xml rename to partner_changeset/views/changeset_field_rule_views.xml diff --git a/partner_revision/views/menu.xml b/partner_changeset/views/menu.xml similarity index 100% rename from partner_revision/views/menu.xml rename to partner_changeset/views/menu.xml diff --git a/partner_revision/views/res_partner_revision_views.xml b/partner_changeset/views/res_partner_changeset_views.xml similarity index 100% rename from partner_revision/views/res_partner_revision_views.xml rename to partner_changeset/views/res_partner_changeset_views.xml diff --git a/partner_revision/views/res_partner_views.xml b/partner_changeset/views/res_partner_views.xml similarity index 89% rename from partner_revision/views/res_partner_views.xml rename to partner_changeset/views/res_partner_views.xml index d090efaa3..a2f490f86 100644 --- a/partner_revision/views/res_partner_views.xml +++ b/partner_changeset/views/res_partner_views.xml @@ -7,12 +7,12 @@ res.partner - + From 028139b8ff217819442a8be2ef8b0b909f6149bb Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 1 Dec 2015 14:16:01 +0100 Subject: [PATCH 41/42] Add screenshots --- partner_changeset/README.rst | 13 +++++++++++++ partner_changeset/static/src/img/changeset.png | Bin 0 -> 86707 bytes partner_changeset/static/src/img/rules.png | Bin 0 -> 61527 bytes 3 files changed, 13 insertions(+) create mode 100644 partner_changeset/static/src/img/changeset.png create mode 100644 partner_changeset/static/src/img/rules.png diff --git a/partner_changeset/README.rst b/partner_changeset/README.rst index aa923088b..8fe23d0b6 100644 --- a/partner_changeset/README.rst +++ b/partner_changeset/README.rst @@ -95,6 +95,7 @@ Custom source rules in your addon Addons wanting to create changeset with their own rules should pass the following keys in the context when they write on the partner: + * ``__changeset_rules_source_model``: name of the model which asks for the change * ``__changeset_rules_source_id``: id of the record which asks for the @@ -108,6 +109,18 @@ The source is used for the application of the rules, allowing to have a different rule for a different source. It is also stored on the changeset for information. +Screenshot: +----------- + +* Configuration of rules + + .. image:: partner_changeset/static/src/img/rules.png + +* Changeset waiting for validation + + .. image:: partner_changeset/static/src/img/changeset.png + + .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot :target: https://runbot.odoo-community.org/runbot/134/8.0 diff --git a/partner_changeset/static/src/img/changeset.png b/partner_changeset/static/src/img/changeset.png new file mode 100644 index 0000000000000000000000000000000000000000..c117da412011601a8739dcf9ce8935a6a51d599f GIT binary patch literal 86707 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 Z-=b2|=7s3ZrQXXa8A(NnVlktS{|B)hiedl& literal 0 HcmV?d00001 diff --git a/partner_changeset/static/src/img/rules.png b/partner_changeset/static/src/img/rules.png new file mode 100644 index 0000000000000000000000000000000000000000..37b9626f00b2c64ed499c009f30e01f63f5d0438 GIT binary patch literal 61527 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 Date: Thu, 10 Dec 2015 08:56:26 +0100 Subject: [PATCH 42/42] Do not create a changeset when both sides are empty But have a different type (e.g. False and '') --- partner_changeset/models/res_partner_changeset.py | 5 ++++- partner_changeset/tests/test_changeset_field_type.py | 2 +- partner_changeset/tests/test_changeset_flow.py | 12 ++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/partner_changeset/models/res_partner_changeset.py b/partner_changeset/models/res_partner_changeset.py index 8b74005b0..ef84c167d 100644 --- a/partner_changeset/models/res_partner_changeset.py +++ b/partner_changeset/models/res_partner_changeset.py @@ -421,7 +421,10 @@ class ResPartnerChangesetChange(models.Model): @api.model def _has_field_changed(self, record, field, value): field_def = record._fields[field] - return field_def.convert_to_write(record[field]) != value + current_value = field_def.convert_to_write(record[field]) + if not (current_value or value): + return False + return current_value != value @api.multi def _convert_value_for_write(self, value): diff --git a/partner_changeset/tests/test_changeset_field_type.py b/partner_changeset/tests/test_changeset_field_type.py index 89cb7c4f2..9354e881e 100644 --- a/partner_changeset/tests/test_changeset_field_type.py +++ b/partner_changeset/tests/test_changeset_field_type.py @@ -177,7 +177,7 @@ class TestChangesetFieldType(ChangesetMixin, common.TransactionCase): """ Add a new changeset on a Binary field is not supported """ with self.assertRaises(NotImplementedError): self.partner.write({ - self.field_binary.name: '', + self.field_binary.name: 'xyz', }) def test_apply_char(self): diff --git a/partner_changeset/tests/test_changeset_flow.py b/partner_changeset/tests/test_changeset_flow.py index f11d28896..a78b7b8e2 100644 --- a/partner_changeset/tests/test_changeset_flow.py +++ b/partner_changeset/tests/test_changeset_flow.py @@ -87,6 +87,18 @@ class TestChangesetFlow(ChangesetMixin, common.TransactionCase): [(self.field_street, 'street X', False, 'draft')] ) + def test_no_changeset_empty_value_both_sides(self): + """ No changeset created when both sides have an empty value """ + # we have to ensure that even if we write '' to a False field, we won't + # write a changeset + self.partner.with_context(__no_changeset=True).write({ + 'street': False, + }) + self.partner.write({ + 'street': '', + }) + self.assertFalse(self.partner.changeset_ids) + def test_apply_change(self): """ Apply a changeset change on a partner """ changes = [