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.

255 lines
8.2 KiB

  1. # Copyright 2017-2018 Camptocamp - Simone Orsi
  2. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
  3. from odoo import fields, models, api, exceptions, _
  4. import logging
  5. logger = logging.getLogger('[mail_digest]')
  6. class MailDigest(models.Model):
  7. _name = 'mail.digest'
  8. _description = 'Mail digest'
  9. _order = 'create_date desc'
  10. name = fields.Char(
  11. string="Name",
  12. compute="_compute_name",
  13. readonly=True,
  14. )
  15. user_id = fields.Many2one(
  16. string='User',
  17. comodel_name='res.users',
  18. readonly=True,
  19. required=True,
  20. ondelete='cascade',
  21. )
  22. frequency = fields.Selection(
  23. related='user_id.digest_frequency',
  24. readonly=True,
  25. )
  26. message_ids = fields.Many2many(
  27. comodel_name='mail.message',
  28. string='Messages'
  29. )
  30. mail_id = fields.Many2one(
  31. 'mail.mail',
  32. 'Mail',
  33. ondelete='set null',
  34. )
  35. state = fields.Selection(related='mail_id.state', readonly=True)
  36. # To my future self: never ever change this field to `template_id`.
  37. # When creating digest records within the context of mail composer
  38. # (and possibly other contexts) you'll have a `default_template_id`
  39. # key in the context which is going to override our safe default.
  40. # This is going to break email generation because the template
  41. # will be completely wrong. Lesson learned :)
  42. digest_template_id = fields.Many2one(
  43. 'ir.ui.view',
  44. 'Qweb mail template',
  45. ondelete='set null',
  46. default=lambda self: self._default_digest_template_id(),
  47. domain=[('type', '=', 'qweb')],
  48. )
  49. def _default_digest_template_id(self):
  50. """Retrieve default template to render digest."""
  51. return self.env.ref('mail_digest.default_digest_tmpl',
  52. raise_if_not_found=False)
  53. @api.multi
  54. @api.depends("user_id", "user_id.digest_frequency")
  55. def _compute_name(self):
  56. for rec in self:
  57. rec.name = '{} - {}'.format(
  58. rec.user_id.name, rec._get_subject())
  59. @api.model
  60. def create_or_update(self, partners, message):
  61. """Create or update digest.
  62. :param partners: recipients as `res.partner` browse list
  63. :param message: `mail.message` to include in digest
  64. """
  65. for partner in partners:
  66. digest = self._get_or_create_by_user(partner.real_user_id)
  67. digest.message_ids |= message
  68. return True
  69. @api.model
  70. def _get_by_user(self, user):
  71. """Retrieve digest record for given user.
  72. :param user: `res.users` browse record
  73. By default we lookup for pending digest without notification yet.
  74. """
  75. domain = [
  76. ('user_id', '=', user.id),
  77. ]
  78. return self.search(domain, limit=1)
  79. @api.model
  80. def _get_or_create_by_user(self, user):
  81. """Retrieve digest record or create it by user.
  82. :param user: `res.users` record to create/get digest for
  83. """
  84. existing = self._get_by_user(user)
  85. if existing:
  86. return existing
  87. values = {'user_id': user.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. # TODO: shall we make this configurable at digest or global level?
  114. # Maybe you have a website but
  115. # your digest msgs are not related to it at all or partially.
  116. ws = None
  117. try:
  118. ws = self.env['website'].get_current_website()
  119. name = ws.name
  120. except RuntimeError:
  121. # RuntimeError: object unbound -> no website request.
  122. # Fallback to default website if any.
  123. ws = self.env['website'].search([], limit=1)
  124. if ws:
  125. name = ws.name
  126. return name
  127. @api.multi
  128. def _get_subject(self):
  129. """Build the full subject for digest's mail."""
  130. # TODO: shall we move this to computed field?
  131. self.ensure_one()
  132. subject = '[{}] '.format(self._get_site_name())
  133. if self.user_id.digest_frequency == 'daily':
  134. subject += _('Daily update')
  135. elif self.user_id.digest_frequency == 'weekly':
  136. subject += _('Weekly update')
  137. return subject
  138. @api.multi
  139. def _get_template_values(self):
  140. """Collect variables to render digest's template."""
  141. self.ensure_one()
  142. subject = self._get_subject()
  143. template_values = {
  144. 'digest': self,
  145. 'subject': subject,
  146. 'grouped_messages': self._message_group_by(),
  147. 'base_url':
  148. self.env['ir.config_parameter'].get_param('web.base.url'),
  149. }
  150. return template_values
  151. @api.multi
  152. def _get_email_values(self, template=None):
  153. """Collect variables to create digest's mail message."""
  154. self.ensure_one()
  155. template = template or self.digest_template_id
  156. if not template:
  157. raise exceptions.UserError(_(
  158. 'You must pass a template or set one on the digest record.'
  159. ))
  160. subject = self._get_subject()
  161. template_values = self._get_template_values()
  162. values = {
  163. 'email_from': self.env.user.company_id.email,
  164. 'recipient_ids': [(4, self.user_id.partner_id.id)],
  165. 'subject': subject,
  166. 'body_html': template.with_context(
  167. **self._template_context()
  168. ).render(template_values),
  169. }
  170. return values
  171. def _create_mail_context(self):
  172. """Inject context vars.
  173. By default we make sure that digest's email
  174. will have only digest's user among recipients.
  175. """
  176. return {
  177. 'notify_only_recipients': True,
  178. }
  179. @api.multi
  180. def _template_context(self):
  181. """Rendering context for digest's template.
  182. By default we enforce user's language.
  183. """
  184. self.ensure_one()
  185. return {
  186. 'lang': self.user_id.lang,
  187. }
  188. @api.multi
  189. def create_email(self, template=None):
  190. """Create `mail.message` records for current digests.
  191. :param template: qweb template instance to override default digest one.
  192. """
  193. mail_model = self.env['mail.mail'].with_context(
  194. **self._create_mail_context())
  195. created = []
  196. for item in self:
  197. if not item.message_ids:
  198. # useless to create a mail for a digest w/ messages
  199. # messages could be deleted by admin for instance.
  200. continue
  201. values = item.with_context(
  202. **item._template_context()
  203. )._get_email_values(template=template)
  204. item.mail_id = mail_model.create(values)
  205. created.append(item.id)
  206. if created:
  207. logger.info('Create email for digest IDS=%s', str(created))
  208. return created
  209. @api.multi
  210. def action_create_email(self):
  211. return self.create_email()
  212. @api.model
  213. def process(self, frequency='daily', domain=None):
  214. """Process existing digest records to create emails via cron.
  215. :param frequency: lookup digest records by users' `digest_frequency`
  216. :param domain: pass custom domain to lookup only specific digests
  217. """
  218. if not domain:
  219. domain = [
  220. ('mail_id', '=', False),
  221. ('user_id.digest_frequency', '=', frequency),
  222. ]
  223. self.search(domain).create_email()