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.

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