You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

201 lines
6.7 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2018 Tecnativa - Jairo Llopis
  3. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
  4. import hashlib
  5. import hmac
  6. from odoo import api, fields, models
  7. class PrivacyConsent(models.Model):
  8. _name = 'privacy.consent'
  9. _description = "Consent of data processing"
  10. _inherit = "mail.thread"
  11. _rec_name = "partner_id"
  12. _sql_constraints = [
  13. ("unique_partner_activity", "UNIQUE(partner_id, activity_id)",
  14. "Duplicated partner in this data processing activity"),
  15. ]
  16. active = fields.Boolean(
  17. default=True,
  18. index=True,
  19. )
  20. accepted = fields.Boolean(
  21. track_visibility="onchange",
  22. help="Indicates current acceptance status, which can come from "
  23. "subject's last answer, or from the default specified in the "
  24. "related data processing activity.",
  25. )
  26. last_metadata = fields.Text(
  27. readonly=True,
  28. track_visibility="onchange",
  29. help="Metadata from the last acceptance or rejection by the subject",
  30. )
  31. partner_id = fields.Many2one(
  32. "res.partner",
  33. "Subject",
  34. required=True,
  35. readonly=True,
  36. track_visibility="onchange",
  37. help="Subject asked for consent.",
  38. )
  39. activity_id = fields.Many2one(
  40. "privacy.activity",
  41. "Activity",
  42. readonly=True,
  43. required=True,
  44. track_visibility="onchange",
  45. )
  46. state = fields.Selection(
  47. selection=[
  48. ("draft", "Draft"),
  49. ("sent", "Awaiting response"),
  50. ("answered", "Answered"),
  51. ],
  52. default="draft",
  53. readonly=True,
  54. required=True,
  55. track_visibility="onchange",
  56. )
  57. def _track_subtype(self, init_values):
  58. """Return specific subtypes."""
  59. if self.env.context.get("subject_answering"):
  60. return "privacy_consent.mt_consent_acceptance_changed"
  61. if "activity_id" in init_values or "partner_id" in init_values:
  62. return "privacy_consent.mt_consent_consent_new"
  63. if "state" in init_values:
  64. return "privacy_consent.mt_consent_state_changed"
  65. return super(PrivacyConsent, self)._track_subtype(init_values)
  66. def _token(self):
  67. """Secret token to publicly authenticate this record."""
  68. secret = self.env["ir.config_parameter"].sudo().get_param(
  69. "database.secret")
  70. params = "{}-{}-{}-{}".format(
  71. self.env.cr.dbname,
  72. self.id,
  73. self.partner_id.id,
  74. self.activity_id.id,
  75. )
  76. return hmac.new(
  77. secret.encode('utf-8'),
  78. params.encode('utf-8'),
  79. hashlib.sha512,
  80. ).hexdigest()
  81. def _url(self, accept):
  82. """Tokenized URL to let subject decide consent.
  83. :param bool accept:
  84. Indicates if you want the acceptance URL, or the rejection one.
  85. """
  86. return "/privacy/consent/{}/{}/{}?db={}".format(
  87. "accept" if accept else "reject",
  88. self.id,
  89. self._token(),
  90. self.env.cr.dbname,
  91. )
  92. def _send_consent_notification(self):
  93. """Send email notification to subject."""
  94. consents_by_template = {}
  95. for one in self.with_context(tpl_force_default_to=True,
  96. mail_notify_user_signature=False,
  97. mail_auto_subscribe_no_notify=True,
  98. mark_consent_sent=True):
  99. # Group consents by template, to send in batch where possible
  100. template_id = one.activity_id.consent_template_id.id
  101. consents_by_template.setdefault(template_id, one)
  102. consents_by_template[template_id] |= one
  103. # Send emails
  104. for template_id, consents in consents_by_template.items():
  105. consents.message_post_with_template(
  106. template_id,
  107. # This mode always sends email, regardless of partner's
  108. # notification preferences; we use it here because it's very
  109. # likely that we are asking authorisation to send emails
  110. composition_mode="mass_mail",
  111. )
  112. def _run_action(self):
  113. """Execute server action defined in data processing activity."""
  114. for one in self:
  115. # Always skip draft consents
  116. if one.state == "draft":
  117. continue
  118. action = one.activity_id.server_action_id.with_context(
  119. active_id=one.id,
  120. active_ids=one.ids,
  121. active_model=one._name,
  122. )
  123. action.run()
  124. @api.model
  125. def create(self, vals):
  126. """Run server action on create."""
  127. result = super(PrivacyConsent, self).create(vals)
  128. # Sync the default acceptance status
  129. result.sudo()._run_action()
  130. return result
  131. def write(self, vals):
  132. """Run server action on update."""
  133. result = super(PrivacyConsent, self).write(vals)
  134. self._run_action()
  135. return result
  136. def message_get_suggested_recipients(self):
  137. result = super(PrivacyConsent, self) \
  138. .message_get_suggested_recipients()
  139. reason = self._fields["partner_id"].string
  140. for one in self:
  141. one._message_add_suggested_recipient(
  142. result,
  143. partner=one.partner_id,
  144. reason=reason,
  145. )
  146. return result
  147. def action_manual_ask(self):
  148. """Let user manually ask for consent."""
  149. return {
  150. "context": {
  151. "default_composition_mode": "mass_mail",
  152. "default_model": self._name,
  153. "default_res_id": self.id,
  154. "default_template_id": self.activity_id.consent_template_id.id,
  155. "default_use_template": True,
  156. "mark_consent_sent": True,
  157. "tpl_force_default_to": True,
  158. },
  159. "force_email": True,
  160. "res_model": "mail.compose.message",
  161. "target": "new",
  162. "type": "ir.actions.act_window",
  163. "view_mode": "form",
  164. }
  165. def action_auto_ask(self):
  166. """Automatically ask for consent."""
  167. templated = self.filtered("activity_id.consent_template_id")
  168. automated = templated.filtered(
  169. lambda one: one.activity_id.consent_required == "auto")
  170. automated._send_consent_notification()
  171. def action_answer(self, answer, metadata=False):
  172. """Process answer.
  173. :param bool answer:
  174. Did the subject accept?
  175. :param str metadata:
  176. Metadata from last user acceptance or rejection request.
  177. """
  178. self.write({
  179. "state": "answered",
  180. "accepted": answer,
  181. "last_metadata": metadata,
  182. })