From f0ca39b1aba76af822549564d2430c037fb90853 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 17 Sep 2015 08:56:11 +0200 Subject: [PATCH] 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 @@ - - + + +