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.

275 lines
9.1 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, tools, _
  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. sanitize_msg_body = fields.Boolean(
  50. string='Sanitize message body',
  51. help='Collected messages can have different styles applied '
  52. 'on each element. If this flag is enabled (default) '
  53. 'each message content will be sanitized '
  54. 'before generating the email.',
  55. default=True,
  56. )
  57. def _default_digest_template_id(self):
  58. """Retrieve default template to render digest."""
  59. return self.env.ref('mail_digest.default_digest_tmpl',
  60. raise_if_not_found=False)
  61. @api.multi
  62. @api.depends("user_id", "user_id.digest_frequency")
  63. def _compute_name(self):
  64. for rec in self:
  65. rec.name = '{} - {}'.format(
  66. rec.user_id.name, rec._get_subject())
  67. @api.model
  68. def create_or_update(self, partners, message):
  69. """Create or update digest.
  70. :param partners: recipients as `res.partner` browse list
  71. :param message: `mail.message` to include in digest
  72. """
  73. for partner in partners:
  74. digest = self._get_or_create_by_user(partner.real_user_id)
  75. digest.message_ids |= message
  76. return True
  77. @api.model
  78. def _get_by_user(self, user):
  79. """Retrieve digest record for given user.
  80. :param user: `res.users` browse record
  81. By default we lookup for pending digest without notification yet.
  82. """
  83. domain = [
  84. ('user_id', '=', user.id),
  85. ]
  86. return self.search(domain, limit=1)
  87. @api.model
  88. def _get_or_create_by_user(self, user):
  89. """Retrieve digest record or create it by user.
  90. :param user: `res.users` record to create/get digest for
  91. """
  92. existing = self._get_by_user(user)
  93. if existing:
  94. return existing
  95. values = {'user_id': user.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. @api.model
  114. def message_body(self, msg, strip_style=True):
  115. """Return body message prepared for email content.
  116. Message's body can contains styles and other stuff
  117. that can screw the look and feel of digests' mails.
  118. Here we sanitize it if `sanitize_msg_body` is set on the digest.
  119. """
  120. if not self.sanitize_msg_body:
  121. return msg.body
  122. return tools.html_sanitize(msg.body or '', strip_style=strip_style)
  123. def _get_site_name(self):
  124. """Retrieve site name for meaningful mail subject.
  125. If you run a website we get website's name
  126. otherwise we default to current user's company name.
  127. """
  128. # default to company
  129. name = self.env.user.company_id.name
  130. if 'website' in self.env:
  131. # TODO: shall we make this configurable at digest or global level?
  132. # Maybe you have a website but
  133. # your digest msgs are not related to it at all or partially.
  134. ws = None
  135. try:
  136. ws = self.env['website'].get_current_website()
  137. name = ws.name
  138. except RuntimeError:
  139. # RuntimeError: object unbound -> no website request.
  140. # Fallback to default website if any.
  141. ws = self.env['website'].search([], limit=1)
  142. if ws:
  143. name = ws.name
  144. return name
  145. @api.multi
  146. def _get_subject(self):
  147. """Build the full subject for digest's mail."""
  148. # TODO: shall we move this to computed field?
  149. self.ensure_one()
  150. subject = '[{}] '.format(self._get_site_name())
  151. if self.user_id.digest_frequency == 'daily':
  152. subject += _('Daily update')
  153. elif self.user_id.digest_frequency == 'weekly':
  154. subject += _('Weekly update')
  155. return subject
  156. @api.multi
  157. def _get_template_values(self):
  158. """Collect variables to render digest's template."""
  159. self.ensure_one()
  160. subject = self._get_subject()
  161. template_values = {
  162. 'digest': self,
  163. 'subject': subject,
  164. 'grouped_messages': self._message_group_by(),
  165. 'base_url':
  166. self.env['ir.config_parameter'].get_param('web.base.url'),
  167. }
  168. return template_values
  169. @api.multi
  170. def _get_email_values(self, template=None):
  171. """Collect variables to create digest's mail message."""
  172. self.ensure_one()
  173. template = template or self.digest_template_id
  174. if not template:
  175. raise exceptions.UserError(_(
  176. 'You must pass a template or set one on the digest record.'
  177. ))
  178. subject = self._get_subject()
  179. template_values = self._get_template_values()
  180. values = {
  181. 'email_from': self.env.user.company_id.email,
  182. 'recipient_ids': [(4, self.user_id.partner_id.id)],
  183. 'subject': subject,
  184. 'body_html': template.with_context(
  185. **self._template_context()
  186. ).render(template_values),
  187. }
  188. return values
  189. def _create_mail_context(self):
  190. """Inject context vars.
  191. By default we make sure that digest's email
  192. will have only digest's user among recipients.
  193. """
  194. return {
  195. 'notify_only_recipients': True,
  196. }
  197. @api.multi
  198. def _template_context(self):
  199. """Rendering context for digest's template.
  200. By default we enforce user's language.
  201. """
  202. self.ensure_one()
  203. return {
  204. 'lang': self.user_id.lang,
  205. }
  206. @api.multi
  207. def create_email(self, template=None):
  208. """Create `mail.message` records for current digests.
  209. :param template: qweb template instance to override default digest one.
  210. """
  211. mail_model = self.env['mail.mail'].with_context(
  212. **self._create_mail_context())
  213. created = []
  214. for item in self:
  215. if not item.message_ids:
  216. # useless to create a mail for a digest w/ messages
  217. # messages could be deleted by admin for instance.
  218. continue
  219. values = item.with_context(
  220. **item._template_context()
  221. )._get_email_values(template=template)
  222. item.mail_id = mail_model.create(values)
  223. created.append(item.id)
  224. if created:
  225. logger.info('Create email for digest IDS=%s', str(created))
  226. return created
  227. @api.multi
  228. def action_create_email(self):
  229. return self.create_email()
  230. @api.model
  231. def process(self, frequency='daily', domain=None):
  232. """Process existing digest records to create emails via cron.
  233. :param frequency: lookup digest records by users' `digest_frequency`
  234. :param domain: pass custom domain to lookup only specific digests
  235. """
  236. if not domain:
  237. domain = [
  238. ('mail_id', '=', False),
  239. ('user_id.digest_frequency', '=', frequency),
  240. ]
  241. self.search(domain).create_email()