Files
odoo_source/addons/calendar/models/calendar_attendee.py
T
Thibault Delavallée 3d5b012501 [REF] mail, various: cleanup usage of options in render mixin
Due to recent improvements (Jinja -> Qweb, safe rendering) rendering API
supports several ways of giving options to the rendering process. Those
have mainly two usage

  * ``preserve_comments`` : keep comments in rendered HTML, used notably
    in mass mailing or digest to keep browser-specific comments;
  * ``post_process`` : perform a post processing on rendered HTML, used notably
    to process local links and add tracking to shortened links;

All those are now given directly inside an optional ``options`` parameter
given to ``_render_field`` and its sub-method ``_render_template``.

They can also be defined as field level, using ``render_options`` field
parameter. Some fields are updated

  * composer mixin body (which impacts all inheriting models, notably
    mail composer, survey and elearning invite wizards as well as appraisal
    and appraisal feedback): post processing is now always done by default.
    It was already done manually in calls that can be simplified;
  * mail template body_html: post processing is now always done by default. It
    was already done manually in calls that can be simplified;
  * mailing body_html and preview are now post processed by default. It was
    already the case in testing wizard. Preview is updated to avoid post
    processing of links, as it was before. Remaining use case is the sending
    which uses the mail composer, therefore was already post processed;

Finally some 'compute_lang' are explicitly added in _render_field calls in
order to be explicit on what we want, instead of being unsure.

Task-2710804 (Mail: Clean MailThread API)
Prepares Task-2088884 (Mail: Use editable computed stored fields in composer)

Part-of: odoo/odoo#106072
2022-11-21 19:55:13 +01:00

164 lines
7.1 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import uuid
import base64
import logging
from collections import defaultdict
from odoo import api, fields, models, _
from odoo.addons.base.models.res_partner import _tz_get
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class Attendee(models.Model):
""" Calendar Attendee Information """
_name = 'calendar.attendee'
_rec_name = 'common_name'
_description = 'Calendar Attendee Information'
_order = 'create_date ASC'
def _default_access_token(self):
return uuid.uuid4().hex
STATE_SELECTION = [
('needsAction', 'Needs Action'),
('tentative', 'Uncertain'),
('declined', 'Declined'),
('accepted', 'Accepted'),
]
# event
event_id = fields.Many2one('calendar.event', 'Meeting linked', required=True, ondelete='cascade')
recurrence_id = fields.Many2one('calendar.recurrence', related='event_id.recurrence_id')
# attendee
partner_id = fields.Many2one('res.partner', 'Attendee', required=True, readonly=True)
email = fields.Char('Email', related='partner_id.email')
phone = fields.Char('Phone', related='partner_id.phone')
common_name = fields.Char('Common name', compute='_compute_common_name', store=True)
access_token = fields.Char('Invitation Token', default=_default_access_token)
mail_tz = fields.Selection(_tz_get, compute='_compute_mail_tz', help='Timezone used for displaying time in the mail template')
# state
state = fields.Selection(STATE_SELECTION, string='Status', readonly=True, default='needsAction')
availability = fields.Selection(
[('free', 'Available'), ('busy', 'Busy')], 'Available/Busy', readonly=True)
@api.depends('partner_id', 'partner_id.name', 'email')
def _compute_common_name(self):
for attendee in self:
attendee.common_name = attendee.partner_id.name or attendee.email
def _compute_mail_tz(self):
for attendee in self:
attendee.mail_tz = attendee.partner_id.tz
@api.model_create_multi
def create(self, vals_list):
for values in vals_list:
# by default, if no state is given for the attendee corresponding to the current user
# that means he's the event organizer so we can set his state to "accepted"
if 'state' not in values and values.get('partner_id') == self.env.user.partner_id.id:
values['state'] = 'accepted'
if not values.get("email") and values.get("common_name"):
common_nameval = values.get("common_name").split(':')
email = [x for x in common_nameval if '@' in x]
values['email'] = email[0] if email else ''
values['common_name'] = values.get("common_name")
attendees = super().create(vals_list)
attendees._subscribe_partner()
return attendees
def unlink(self):
self._unsubscribe_partner()
return super().unlink()
@api.returns('self', lambda value: value.id)
def copy(self, default=None):
raise UserError(_('You cannot duplicate a calendar attendee.'))
def _subscribe_partner(self):
mapped_followers = defaultdict(lambda: self.env['calendar.event'])
for event in self.event_id:
partners = (event.attendee_ids & self).partner_id - event.message_partner_ids
# current user is automatically added as followers, don't add it twice.
partners -= self.env.user.partner_id
mapped_followers[partners] |= event
for partners, events in mapped_followers.items():
events.message_subscribe(partner_ids=partners.ids)
def _unsubscribe_partner(self):
for event in self.event_id:
partners = (event.attendee_ids & self).partner_id & event.message_partner_ids
event.message_unsubscribe(partner_ids=partners.ids)
def _send_mail_to_attendees(self, mail_template, force_send=False):
""" Send mail for event invitation to event attendees.
:param mail_template: a mail.template record
:param force_send: if set to True, the mail(s) will be sent immediately (instead of the next queue processing)
"""
if isinstance(mail_template, str):
raise ValueError('Template should be a template record, not an XML ID anymore.')
if self.env['ir.config_parameter'].sudo().get_param('calendar.block_mail') or self._context.get("no_mail_to_attendees"):
return False
if not mail_template:
_logger.warning("No template passed to %s notification process. Skipped.", self)
return False
# get ics file for all meetings
ics_files = self.mapped('event_id')._get_ics_file()
for attendee in self:
if attendee.email and attendee.partner_id != self.env.user.partner_id:
event_id = attendee.event_id.id
ics_file = ics_files.get(event_id)
attachment_values = []
if ics_file:
attachment_values = [
(0, 0, {'name': 'invitation.ics',
'mimetype': 'text/calendar',
'datas': base64.b64encode(ics_file)})
]
body = mail_template._render_field(
'body_html',
attendee.ids,
compute_lang=True)[attendee.id]
subject = mail_template._render_field(
'subject',
attendee.ids,
compute_lang=True)[attendee.id]
attendee.event_id.with_context(no_document=True).message_notify(
email_from=attendee.event_id.user_id.email_formatted or self.env.user.email_formatted,
author_id=attendee.event_id.user_id.partner_id.id or self.env.user.partner_id.id,
body=body,
subject=subject,
partner_ids=attendee.partner_id.ids,
email_layout_xmlid='mail.mail_notification_light',
attachment_ids=attachment_values,
force_send=force_send)
def do_tentative(self):
""" Makes event invitation as Tentative. """
return self.write({'state': 'tentative'})
def do_accept(self):
""" Marks event invitation as Accepted. """
for attendee in self:
attendee.event_id.message_post(
author_id=attendee.partner_id.id,
body=_("%s has accepted the invitation") % (attendee.common_name),
subtype_xmlid="calendar.subtype_invitation",
)
return self.write({'state': 'accepted'})
def do_decline(self):
""" Marks event invitation as Declined. """
for attendee in self:
attendee.event_id.message_post(
author_id=attendee.partner_id.id,
body=_("%s has declined the invitation") % (attendee.common_name),
subtype_xmlid="calendar.subtype_invitation",
)
return self.write({'state': 'declined'})