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.

248 lines
7.9 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2017 Simone Orsi <simone.orsi@camptocamp.com>
  3. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
  4. from odoo import fields, models, api, exceptions, _
  5. import logging
  6. logger = logging.getLogger('[mail_digest]')
  7. class MailDigest(models.Model):
  8. _name = 'mail.digest'
  9. _description = 'Mail digest'
  10. _order = 'create_date desc'
  11. name = fields.Char(
  12. string="Name",
  13. compute="_compute_name",
  14. readonly=True,
  15. )
  16. partner_id = fields.Many2one(
  17. string='Partner',
  18. comodel_name='res.partner',
  19. readonly=True,
  20. required=True,
  21. ondelete='cascade',
  22. )
  23. frequency = fields.Selection(
  24. related='partner_id.notify_frequency',
  25. readonly=True,
  26. )
  27. message_ids = fields.Many2many(
  28. comodel_name='mail.message',
  29. string='Messages'
  30. )
  31. mail_id = fields.Many2one(
  32. 'mail.mail',
  33. 'Mail',
  34. ondelete='set null',
  35. )
  36. state = fields.Selection(related='mail_id.state')
  37. template_id = fields.Many2one(
  38. 'ir.ui.view',
  39. 'Qweb mail template',
  40. ondelete='set null',
  41. default=lambda self: self._default_template_id()
  42. )
  43. def _default_template_id(self):
  44. """Retrieve default template to render digest."""
  45. return self.env.ref('mail_digest.default_digest_tmpl',
  46. raise_if_not_found=False)
  47. @api.multi
  48. @api.depends("partner_id", "partner_id.notify_frequency")
  49. def _compute_name(self):
  50. for rec in self:
  51. rec.name = u'{} - {}'.format(
  52. rec.partner_id.name, rec._get_subject())
  53. @api.model
  54. def create_or_update(self, partners, message, subtype_id=None):
  55. """Create or update digest.
  56. :param partners: recipients as `res.partner` browse list
  57. :param message: `mail.message` to include in digest
  58. :param subtype_id: `mail.message.subtype` instance
  59. """
  60. subtype_id = subtype_id or message.subtype_id
  61. for partner in partners:
  62. digest = self._get_or_create_by_partner(partner, message)
  63. digest.message_ids |= message
  64. return True
  65. @api.model
  66. def _get_by_partner(self, partner, mail_id=False):
  67. """Retrieve digest record for given partner.
  68. :param partner: `res.partner` browse record
  69. :param mail_id: `mail.mail` record for further filtering.
  70. By default we lookup for pending digest without notification yet.
  71. """
  72. domain = [
  73. ('partner_id', '=', partner.id),
  74. ('mail_id', '=', mail_id),
  75. ]
  76. return self.search(domain, limit=1)
  77. @api.model
  78. def _get_or_create_by_partner(self, partner, message=None, mail_id=False):
  79. """Retrieve digest record or create it by partner.
  80. :param partner: `res.partner` record to create/get digest for
  81. :param message: `mail.message` to include in digest
  82. :param mail_id: `mail.mail` record to set on digest
  83. """
  84. existing = self._get_by_partner(partner, mail_id=mail_id)
  85. if existing:
  86. return existing
  87. values = {'partner_id': partner.id, }
  88. return self.create(values)
  89. @api.model
  90. def _message_group_by_key(self, msg):
  91. """Return the key to group messages by."""
  92. return msg.subtype_id.id
  93. @api.multi
  94. def _message_group_by(self):
  95. """Group digest messages.
  96. A digest can contain several messages.
  97. To display them in a nice and organized form in your emails
  98. we group them by subtype by default.
  99. """
  100. self.ensure_one()
  101. grouped = {}
  102. for msg in self.message_ids:
  103. grouped.setdefault(self._message_group_by_key(msg), []).append(msg)
  104. return grouped
  105. def _get_site_name(self):
  106. """Retrieve site name for meaningful mail subject.
  107. If you run a website we get website's name
  108. otherwise we default to current user's company name.
  109. """
  110. # default to company
  111. name = self.env.user.company_id.name
  112. if 'website' in self.env:
  113. try:
  114. ws = self.env['website'].get_current_website()
  115. name = ws.name
  116. except RuntimeError:
  117. # RuntimeError: object unbound -> no website request
  118. pass
  119. return name
  120. @api.multi
  121. def _get_subject(self):
  122. """Build the full subject for digest's mail."""
  123. # TODO: shall we move this to computed field?
  124. self.ensure_one()
  125. subject = u'[{}] '.format(self._get_site_name())
  126. if self.partner_id.notify_frequency == 'daily':
  127. subject += _('Daily update')
  128. elif self.partner_id.notify_frequency == 'weekly':
  129. subject += _('Weekly update')
  130. return subject
  131. @api.multi
  132. def _get_template_values(self):
  133. """Collect variables to render digest's template."""
  134. self.ensure_one()
  135. subject = self._get_subject()
  136. template_values = {
  137. 'digest': self,
  138. 'subject': subject,
  139. 'grouped_messages': self._message_group_by(),
  140. 'base_url':
  141. self.env['ir.config_parameter'].get_param('web.base.url'),
  142. }
  143. return template_values
  144. @api.multi
  145. def _get_email_values(self, template=None):
  146. """Collect variables to create digest's mail message."""
  147. self.ensure_one()
  148. template = template or self.template_id
  149. if not template:
  150. raise exceptions.UserError(_(
  151. 'You must pass a template or set one on the digest record.'
  152. ))
  153. subject = self._get_subject()
  154. template_values = self._get_template_values()
  155. values = {
  156. 'email_from': self.env.user.company_id.email,
  157. 'recipient_ids': [(4, self.partner_id.id)],
  158. 'subject': subject,
  159. 'body_html': template.with_context(
  160. **self._template_context()
  161. ).render(template_values),
  162. }
  163. return values
  164. def _create_mail_context(self):
  165. """Inject context vars.
  166. By default we make sure that digest's email
  167. will have only digest's partner among recipients.
  168. """
  169. return {
  170. 'notify_only_recipients': True,
  171. }
  172. @api.multi
  173. def _template_context(self):
  174. """Rendering context for digest's template.
  175. By default we enforce partner's language.
  176. """
  177. self.ensure_one()
  178. return {
  179. 'lang': self.partner_id.lang,
  180. }
  181. @api.multi
  182. def create_email(self, template=None):
  183. """Create `mail.message` records for current digests.
  184. :param template: qweb template instance to override default digest one.
  185. """
  186. mail_model = self.env['mail.mail'].with_context(
  187. **self._create_mail_context())
  188. created = []
  189. for item in self:
  190. if not item.message_ids:
  191. # useless to create a mail for a digest w/ messages
  192. # messages could be deleted by admin for instance.
  193. continue
  194. values = item.with_context(
  195. **item._template_context()
  196. )._get_email_values(template=template)
  197. item.mail_id = mail_model.create(values)
  198. created.append(item.id)
  199. if created:
  200. logger.info('Create email for digest IDS=%s', str(created))
  201. return created
  202. @api.multi
  203. def action_create_email(self):
  204. return self.create_email()
  205. @api.model
  206. def process(self, frequency='daily', domain=None):
  207. """Process existing digest records to create emails via cron.
  208. :param frequency: lookup digest records by partners' `notify_frequency`
  209. :param domain: pass custom domain to lookup only specific digests
  210. """
  211. if not domain:
  212. domain = [
  213. ('mail_id', '=', False),
  214. ('partner_id.notify_frequency', '=', frequency),
  215. ]
  216. self.search(domain).create_email()