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.

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