From 83eb6367de94fc461187f8b7a970f7c0682ede18 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 8 Jul 2019 12:46:42 +0100 Subject: [PATCH] [MIG] privacy_consent: Migrate to v12 - Partner's `opt_out` no longer exists. Using `mail.blacklist` now. - Tests updated to support that change. - Test workarounds removed. - Duplicated-field-name-in-model warning removed. - Use create multi where possible. --- privacy_consent/README.rst | 10 +-- privacy_consent/__manifest__.py | 2 +- privacy_consent/data/ir_actions_server.xml | 19 ++++- .../migrations/12.0.1.0.0/pre-migrate.py | 10 +++ privacy_consent/models/mail_mail.py | 10 ++- privacy_consent/models/mail_template.py | 2 +- privacy_consent/models/privacy_activity.py | 11 +-- privacy_consent/models/privacy_consent.py | 13 +-- privacy_consent/static/description/index.html | 6 +- privacy_consent/tests/test_consent.py | 80 +++++++------------ 10 files changed, 88 insertions(+), 75 deletions(-) create mode 100644 privacy_consent/migrations/12.0.1.0.0/pre-migrate.py diff --git a/privacy_consent/README.rst b/privacy_consent/README.rst index d5c56b8..feb717a 100644 --- a/privacy_consent/README.rst +++ b/privacy_consent/README.rst @@ -14,13 +14,13 @@ Privacy - Consent :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fdata--protection-lightgray.png?logo=github - :target: https://github.com/OCA/data-protection/tree/11.0/privacy_consent + :target: https://github.com/OCA/data-protection/tree/12.0/privacy_consent :alt: OCA/data-protection .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/data-protection-11-0/data-protection-11-0-privacy_consent + :target: https://translation.odoo-community.org/projects/data-protection-12-0/data-protection-12-0-privacy_consent :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/263/11.0 + :target: https://runbot.odoo-community.org/runbot/263/12.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -136,7 +136,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -173,6 +173,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/data-protection `_ project on GitHub. +This module is part of the `OCA/data-protection `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/privacy_consent/__manifest__.py b/privacy_consent/__manifest__.py index a9d388b..1d6eb22 100644 --- a/privacy_consent/__manifest__.py +++ b/privacy_consent/__manifest__.py @@ -4,7 +4,7 @@ "name": "Privacy - Consent", "summary": "Allow people to explicitly accept or reject inclusion " "in some activity, GDPR compliant", - "version": "11.0.1.0.0", + "version": "12.0.1.0.0", "development_status": "Production/Stable", "category": "Privacy", "website": "https://github.com/OCA/management-activity", diff --git a/privacy_consent/data/ir_actions_server.xml b/privacy_consent/data/ir_actions_server.xml index 4e7bc4d..d305284 100644 --- a/privacy_consent/data/ir_actions_server.xml +++ b/privacy_consent/data/ir_actions_server.xml @@ -5,12 +5,25 @@ - - Update partner's opt out + + Sync partner's email blacklist status code - records.mapped('partner_id').write({'opt_out': not record.accepted}) + + for consent in records: + email = consent.partner_id.email + # Skip records without email, although highly improbable + if not email: + continue + # Choose method to sync acceptance and blacklisting + if consent.accepted: + method = env["mail.blacklist"]._remove + else: + method = env["mail.blacklist"]._add + # Apply user desire + method(email) + diff --git a/privacy_consent/migrations/12.0.1.0.0/pre-migrate.py b/privacy_consent/migrations/12.0.1.0.0/pre-migrate.py new file mode 100644 index 0000000..f5f19ce --- /dev/null +++ b/privacy_consent/migrations/12.0.1.0.0/pre-migrate.py @@ -0,0 +1,10 @@ +# Copyright 2019 Tecnativa - Jairo Llopis +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from openupgradelib.openupgrade import rename_xmlids + + +def migrate(cr, version): + """Use a better xmlid for the provided server action.""" + rename_xmlids(cr, [ + ("privacy_consent.update_opt_out", "privacy_consent.sync_blacklist"), + ]) diff --git a/privacy_consent/models/mail_mail.py b/privacy_consent/models/mail_mail.py index cdb0a95..fb079c5 100644 --- a/privacy_consent/models/mail_mail.py +++ b/privacy_consent/models/mail_mail.py @@ -7,8 +7,10 @@ from odoo import models class MailMail(models.Model): _inherit = "mail.mail" - def _postprocess_sent_message(self, mail_sent=True): + def _postprocess_sent_message(self, success_pids, failure_reason=False, + failure_type=None): """Write consent status after sending message.""" + mail_sent = not failure_type if mail_sent: # Get all mails sent to consents consent_mails = self.filtered( @@ -23,7 +25,11 @@ class MailMail(models.Model): consents.write({ "state": "sent", }) - return super(MailMail, self)._postprocess_sent_message(mail_sent) + return super()._postprocess_sent_message( + success_pids=success_pids, + failure_reason=False, + failure_type=None, + ) def send_get_mail_body(self, partner=None): """Replace privacy consent magic links. diff --git a/privacy_consent/models/mail_template.py b/privacy_consent/models/mail_template.py index 9ddb38b..9e8f786 100644 --- a/privacy_consent/models/mail_template.py +++ b/privacy_consent/models/mail_template.py @@ -13,7 +13,7 @@ class MailTemplate(models.Model): @api.constrains("body_html", "model") def _check_consent_links_in_body_html(self): """Body for ``privacy.consent`` templates needs placeholder links.""" - links = [u"//a[@href='/privacy/consent/{}/']".format(action) + links = ["//a[@href='/privacy/consent/{}/']".format(action) for action in ("accept", "reject")] for one in self: if one.model != "privacy.consent": diff --git a/privacy_consent/models/privacy_activity.py b/privacy_consent/models/privacy_activity.py index 3f280b5..019ac3c 100644 --- a/privacy_consent/models/privacy_activity.py +++ b/privacy_consent/models/privacy_activity.py @@ -24,7 +24,7 @@ class PrivacyActivity(models.Model): "Consents", ) consent_count = fields.Integer( - "Consents", + "Consents count", compute="_compute_consent_count", ) consent_required = fields.Selection( @@ -115,7 +115,7 @@ class PrivacyActivity(models.Model): def action_new_consents(self): """Generate new consent requests.""" - consents = self.env["privacy.consent"] + consents_vals = [] # Skip activitys where consent is not required for one in self.with_context(active_test=False) \ .filtered("consent_required"): @@ -123,14 +123,15 @@ class PrivacyActivity(models.Model): ("id", "not in", one.mapped("consent_ids.partner_id").ids), ("email", "!=", False), ] + safe_eval(one.subject_domain) - # Create missing consent requests + # Store values for creating missing consent requests for missing in self.env["res.partner"].search(domain): - consents |= consents.create({ + consents_vals.append({ "partner_id": missing.id, "accepted": one.default_consent, "activity_id": one.id, }) - # Send consent request emails for automatic activitys + # Create and send consent request emails for automatic activitys + consents = self.env["privacy.consent"].create(consents_vals) consents.action_auto_ask() # Redirect user to new consent requests generated return { diff --git a/privacy_consent/models/privacy_consent.py b/privacy_consent/models/privacy_consent.py index f94ee7f..71e77a8 100644 --- a/privacy_consent/models/privacy_consent.py +++ b/privacy_consent/models/privacy_consent.py @@ -118,14 +118,15 @@ class PrivacyConsent(models.Model): ) action.run() - @api.model - def create(self, vals): + @api.model_create_multi + def create(self, vals_list): """Run server action on create.""" - result = super(PrivacyConsent, - self.with_context(mail_create_nolog=True)).create(vals) + super_ = super(PrivacyConsent, + self.with_context(mail_create_nolog=True)) + results = super_.create(vals_list) # Sync the default acceptance status - result.sudo()._run_action() - return result + results.sudo()._run_action() + return results def write(self, vals): """Run server action on update.""" diff --git a/privacy_consent/static/description/index.html b/privacy_consent/static/description/index.html index 7e69756..4fdba41 100644 --- a/privacy_consent/static/description/index.html +++ b/privacy_consent/static/description/index.html @@ -367,7 +367,7 @@ ul.auto-toc { !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Production/Stable License: AGPL-3 OCA/data-protection Translate me on Weblate Try me on Runbot

+

Production/Stable License: AGPL-3 OCA/data-protection Translate me on Weblate Try me on Runbot

This module allows the user to define a set of subjects (partners) affected by any data processing activity, and establish a process to ask them for consent to include them in that activity.

@@ -484,7 +484,7 @@ and the request state.

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

@@ -516,7 +516,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

-

This module is part of the OCA/data-protection project on GitHub.

+

This module is part of the OCA/data-protection project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/privacy_consent/tests/test_consent.py b/privacy_consent/tests/test_consent.py index 8de1e56..994f08b 100644 --- a/privacy_consent/tests/test_consent.py +++ b/privacy_consent/tests/test_consent.py @@ -1,8 +1,6 @@ # Copyright 2018 Tecnativa - Jairo Llopis # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from contextlib import contextmanager - from odoo.exceptions import ValidationError from odoo.tests.common import HttpCase @@ -10,15 +8,10 @@ from odoo.tests.common import HttpCase class ActivityCase(HttpCase): def setUp(self): super(ActivityCase, self).setUp() - # HACK https://github.com/odoo/odoo/issues/12237 - # TODO Remove hack in v12 - self._oldenv = self.env - self.env = self._oldenv(self.cursor()) - # HACK end self.cron = self.env.ref("privacy_consent.cron_auto_consent") self.cron_mail_queue = self.env.ref( "mail.ir_cron_mail_scheduler_action") - self.update_opt_out = self.env.ref("privacy_consent.update_opt_out") + self.sync_blacklist = self.env.ref("privacy_consent.sync_blacklist") self.mt_consent_consent_new = self.env.ref( "privacy_consent.mt_consent_consent_new") self.mt_consent_acceptance_changed = self.env.ref( @@ -30,25 +23,22 @@ class ActivityCase(HttpCase): self.partners += self.partners.create({ "name": "consent-partner-0", "email": "partner0@example.com", - "notify_email": "none", - "opt_out": False, }) self.partners += self.partners.create({ "name": "consent-partner-1", "email": "partner1@example.com", - "notify_email": "always", - "opt_out": True, }) self.partners += self.partners.create({ "name": "consent-partner-2", "email": "partner2@example.com", - "opt_out": False, }) # Partner without email, on purpose self.partners += self.partners.create({ "name": "consent-partner-3", - "opt_out": True, }) + # Blacklist some partners + self.blacklists = self.env["mail.blacklist"] + self.blacklists += self.blacklists._add("partner1@example.com") # Activity without consent self.activity_noconsent = self.env["privacy.activity"].create({ "name": "activity_noconsent", @@ -62,7 +52,7 @@ class ActivityCase(HttpCase): "subject_domain": repr([("id", "in", self.partners.ids)]), "consent_required": "auto", "default_consent": True, - "server_action_id": self.update_opt_out.id, + "server_action_id": self.sync_blacklist.id, }) # Activity with manual consent, skipping partner 0 self.activity_manual = self.env["privacy.activity"].create({ @@ -72,23 +62,9 @@ class ActivityCase(HttpCase): "subject_domain": repr([("id", "in", self.partners[1:].ids)]), "consent_required": "manual", "default_consent": False, - "server_action_id": self.update_opt_out.id, + "server_action_id": self.sync_blacklist.id, }) - # HACK https://github.com/odoo/odoo/issues/12237 - # TODO Remove hack in v12 - def tearDown(self): - self.env = self._oldenv - super(ActivityCase, self).tearDown() - - # HACK https://github.com/odoo/odoo/issues/12237 - # TODO Remove hack in v12 - @contextmanager - def release_cr(self): - self.env.cr.release() - yield - self.env.cr.acquire() - def check_activity_auto_properly_sent(self): """Check emails sent by ``self.activity_auto``.""" consents = self.env["privacy.consent"].search([ @@ -122,8 +98,8 @@ class ActivityCase(HttpCase): messages[0].subtype_id, self.mt_consent_state_changed, ) - # Partner's opt_out should be synced with default consent - self.assertFalse(consent.partner_id.opt_out) + # Partner's is_blacklisted should be synced with default consent + self.assertFalse(consent.partner_id.is_blacklisted) def test_default_template(self): """We have a good mail template by default.""" @@ -164,12 +140,17 @@ class ActivityCase(HttpCase): def test_generate_manually(self): """Manually-generated consents work as expected.""" - self.partners.write({"opt_out": False}) + for partner in self.partners: + if partner.email: + self.blacklists._remove(partner.email) result = self.activity_manual.action_new_consents() self.assertEqual(result["res_model"], "privacy.consent") consents = self.env[result["res_model"]].search(result["domain"]) self.assertEqual(consents.mapped("state"), ["draft"] * 2) - self.assertEqual(consents.mapped("partner_id.opt_out"), [False] * 2) + self.assertEqual( + consents.mapped("partner_id.is_blacklisted"), + [False] * 2, + ) self.assertEqual(consents.mapped("accepted"), [False] * 2) self.assertEqual(consents.mapped("last_metadata"), [False] * 2) # Check sent mails @@ -192,7 +173,10 @@ class ActivityCase(HttpCase): self.assertEqual(len(messages), 2) self.assertEqual(messages[0].subtype_id, self.mt_consent_state_changed) self.assertEqual(consents.mapped("state"), ["sent", "draft"]) - self.assertEqual(consents.mapped("partner_id.opt_out"), [True, False]) + self.assertEqual( + consents.mapped("partner_id.is_blacklisted"), + [False, False], + ) # Placeholder links should be logged self.assertTrue("/privacy/consent/accept/" in messages[1].body) self.assertTrue("/privacy/consent/reject/" in messages[1].body) @@ -202,32 +186,30 @@ class ActivityCase(HttpCase): self.assertNotIn(accept_url, messages[1].body) self.assertNotIn(reject_url, messages[1].body) # Visit tokenized accept URL - with self.release_cr(): - result = self.url_open(accept_url).text - self.assertIn("accepted", result) - self.assertIn(reject_url, result) - self.assertIn(self.activity_manual.name, result) - self.assertIn(self.activity_manual.description, result) + result = self.url_open(accept_url).text + self.assertIn("accepted", result) + self.assertIn(reject_url, result) + self.assertIn(self.activity_manual.name, result) + self.assertIn(self.activity_manual.description, result) consents.invalidate_cache() self.assertEqual(consents.mapped("accepted"), [True, False]) self.assertTrue(consents[0].last_metadata) - self.assertFalse(consents[0].partner_id.opt_out) + self.assertFalse(consents[0].partner_id.is_blacklisted) self.assertEqual(consents.mapped("state"), ["answered", "draft"]) self.assertEqual( consents[0].message_ids[0].subtype_id, self.mt_consent_acceptance_changed, ) # Visit tokenized reject URL - with self.release_cr(): - result = self.url_open(reject_url).text - self.assertIn("rejected", result) - self.assertIn(accept_url, result) - self.assertIn(self.activity_manual.name, result) - self.assertIn(self.activity_manual.description, result) + result = self.url_open(reject_url).text + self.assertIn("rejected", result) + self.assertIn(accept_url, result) + self.assertIn(self.activity_manual.name, result) + self.assertIn(self.activity_manual.description, result) consents.invalidate_cache() self.assertEqual(consents.mapped("accepted"), [False, False]) self.assertTrue(consents[0].last_metadata) - self.assertTrue(consents[0].partner_id.opt_out) + self.assertTrue(consents[0].partner_id.is_blacklisted) self.assertEqual(consents.mapped("state"), ["answered", "draft"]) self.assertEqual( consents[0].message_ids[0].subtype_id,