calendar_attendee.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. import uuid
  4. import base64
  5. import logging
  6. from collections import defaultdict
  7. from odoo import api, fields, models, _
  8. from odoo.addons.base.models.res_partner import _tz_get
  9. from odoo.exceptions import UserError
  10. _logger = logging.getLogger(__name__)
  11. class Attendee(models.Model):
  12. """ Calendar Attendee Information """
  13. _name = 'calendar.attendee'
  14. _rec_name = 'common_name'
  15. _description = 'Calendar Attendee Information'
  16. _order = 'create_date ASC'
  17. def _default_access_token(self):
  18. return uuid.uuid4().hex
  19. STATE_SELECTION = [
  20. ('needsAction', 'Needs Action'),
  21. ('tentative', 'Uncertain'),
  22. ('declined', 'Declined'),
  23. ('accepted', 'Accepted'),
  24. ]
  25. # event
  26. event_id = fields.Many2one('calendar.event', 'Meeting linked', required=True, ondelete='cascade')
  27. recurrence_id = fields.Many2one('calendar.recurrence', related='event_id.recurrence_id')
  28. # attendee
  29. partner_id = fields.Many2one('res.partner', 'Attendee', required=True, readonly=True)
  30. email = fields.Char('Email', related='partner_id.email')
  31. phone = fields.Char('Phone', related='partner_id.phone')
  32. common_name = fields.Char('Common name', compute='_compute_common_name', store=True)
  33. access_token = fields.Char('Invitation Token', default=_default_access_token)
  34. mail_tz = fields.Selection(_tz_get, compute='_compute_mail_tz', help='Timezone used for displaying time in the mail template')
  35. # state
  36. state = fields.Selection(STATE_SELECTION, string='Status', readonly=True, default='needsAction')
  37. availability = fields.Selection(
  38. [('free', 'Available'), ('busy', 'Busy')], 'Available/Busy', readonly=True)
  39. @api.depends('partner_id', 'partner_id.name', 'email')
  40. def _compute_common_name(self):
  41. for attendee in self:
  42. attendee.common_name = attendee.partner_id.name or attendee.email
  43. def _compute_mail_tz(self):
  44. for attendee in self:
  45. attendee.mail_tz = attendee.partner_id.tz
  46. @api.model_create_multi
  47. def create(self, vals_list):
  48. for values in vals_list:
  49. # by default, if no state is given for the attendee corresponding to the current user
  50. # that means he's the event organizer so we can set his state to "accepted"
  51. if 'state' not in values and values.get('partner_id') == self.env.user.partner_id.id:
  52. values['state'] = 'accepted'
  53. if not values.get("email") and values.get("common_name"):
  54. common_nameval = values.get("common_name").split(':')
  55. email = [x for x in common_nameval if '@' in x]
  56. values['email'] = email[0] if email else ''
  57. values['common_name'] = values.get("common_name")
  58. attendees = super().create(vals_list)
  59. attendees._subscribe_partner()
  60. return attendees
  61. def unlink(self):
  62. self._unsubscribe_partner()
  63. return super().unlink()
  64. @api.returns('self', lambda value: value.id)
  65. def copy(self, default=None):
  66. raise UserError(_('You cannot duplicate a calendar attendee.'))
  67. def _subscribe_partner(self):
  68. mapped_followers = defaultdict(lambda: self.env['calendar.event'])
  69. for event in self.event_id:
  70. partners = (event.attendee_ids & self).partner_id - event.message_partner_ids
  71. # current user is automatically added as followers, don't add it twice.
  72. partners -= self.env.user.partner_id
  73. mapped_followers[partners] |= event
  74. for partners, events in mapped_followers.items():
  75. events.message_subscribe(partner_ids=partners.ids)
  76. def _unsubscribe_partner(self):
  77. for event in self.event_id:
  78. partners = (event.attendee_ids & self).partner_id & event.message_partner_ids
  79. event.message_unsubscribe(partner_ids=partners.ids)
  80. def _send_mail_to_attendees(self, mail_template, force_send=False):
  81. """ Send mail for event invitation to event attendees.
  82. :param mail_template: a mail.template record
  83. :param force_send: if set to True, the mail(s) will be sent immediately (instead of the next queue processing)
  84. """
  85. if isinstance(mail_template, str):
  86. raise ValueError('Template should be a template record, not an XML ID anymore.')
  87. if self.env['ir.config_parameter'].sudo().get_param('calendar.block_mail') or self._context.get("no_mail_to_attendees"):
  88. return False
  89. if not mail_template:
  90. _logger.warning("No template passed to %s notification process. Skipped.", self)
  91. return False
  92. # get ics file for all meetings
  93. ics_files = self.mapped('event_id')._get_ics_file()
  94. for attendee in self:
  95. if attendee.email and attendee.partner_id != self.env.user.partner_id:
  96. event_id = attendee.event_id.id
  97. ics_file = ics_files.get(event_id)
  98. attachment_values = []
  99. if ics_file:
  100. attachment_values = [
  101. (0, 0, {'name': 'invitation.ics',
  102. 'mimetype': 'text/calendar',
  103. 'datas': base64.b64encode(ics_file)})
  104. ]
  105. body = mail_template._render_field(
  106. 'body_html',
  107. attendee.ids,
  108. compute_lang=True,
  109. post_process=True)[attendee.id]
  110. subject = mail_template._render_field(
  111. 'subject',
  112. attendee.ids,
  113. compute_lang=True)[attendee.id]
  114. attendee.event_id.with_context(no_document=True).sudo().message_notify(
  115. email_from=attendee.event_id.user_id.email_formatted or self.env.user.email_formatted,
  116. author_id=attendee.event_id.user_id.partner_id.id or self.env.user.partner_id.id,
  117. body=body,
  118. subject=subject,
  119. partner_ids=attendee.partner_id.ids,
  120. email_layout_xmlid='mail.mail_notification_light',
  121. attachment_ids=attachment_values,
  122. force_send=force_send)
  123. def do_tentative(self):
  124. """ Makes event invitation as Tentative. """
  125. return self.write({'state': 'tentative'})
  126. def do_accept(self):
  127. """ Marks event invitation as Accepted. """
  128. for attendee in self:
  129. attendee.event_id.message_post(
  130. author_id=attendee.partner_id.id,
  131. body=_("%s has accepted the invitation") % (attendee.common_name),
  132. subtype_xmlid="calendar.subtype_invitation",
  133. )
  134. return self.write({'state': 'accepted'})
  135. def do_decline(self):
  136. """ Marks event invitation as Declined. """
  137. for attendee in self:
  138. attendee.event_id.message_post(
  139. author_id=attendee.partner_id.id,
  140. body=_("%s has declined the invitation") % (attendee.common_name),
  141. subtype_xmlid="calendar.subtype_invitation",
  142. )
  143. return self.write({'state': 'declined'})