[IMP] calendar: improve main data templates
Purpose of this commit is to make templates easier to preview and edit as weel as to improve their content. This is achieved notably by lessening use of custom rendering context and trying to fix as much error-prone jinja statements. In this commit we also make ``calendar_template_meeting_reminder`` template looking more like other templates. There were some rendering differences that do not seem really wanted. ``calendar_template_meeting_reminder`` was also using an old ``force_event_id`` key not replaced by recurrent_id field on attendee. This commit improves few strings of the invitation mail being sent while booking an online appointment. Online appointment is computed based on ``appointment_type_id`` field being defined. Some tweaks in wording made it necessary to know if recipient is the responsible, customer or an added attendee on the event, leading to some tooling code added on event model. The main changes includes * for customer, remove the CTAs to accept / decline event as attendee are automatically set as accepted; * depending on recipient, improve wording of the mail accordingly and mention appointment type in the mail; Task ID-2199620 COM PR odoo/odoo#64925 ENT PR odoo/enterprise#15914 UPG PR odoo/upgrade#2282 Co-Authored-By: Krupal Oza <koz@odoo.com> Co-Authored-By: Thibault Delavallee <tde@odoo.com>
This commit is contained in:
committed by
Thibault Delavallée
co-authored by
Thibault Delavallee
parent
f742d0f72c
commit
bc9bc2686c
@@ -3,7 +3,7 @@
|
||||
|
||||
{
|
||||
'name': 'Calendar',
|
||||
'version': '1.0',
|
||||
'version': '1.1',
|
||||
'sequence': 165,
|
||||
'depends': ['base', 'mail'],
|
||||
'summary': "Schedule employees' meetings",
|
||||
|
||||
@@ -11,26 +11,46 @@
|
||||
<field name="lang">${object.partner_id.lang}</field>
|
||||
<field name="body_html" type="html">
|
||||
<div>
|
||||
% set colors = ctx.get('colors', {})
|
||||
% set recurrent = object.recurrence_id and not ctx['ignore_recurrence']
|
||||
% set colors = {'needsAction': 'grey', 'accepted': 'green', 'tentative': '#FFFF00', 'declined': 'red'}
|
||||
% set is_online = 'appointment_type_id' in object.event_id and object.event_id.appointment_type_id
|
||||
% set customer = object.event_id.find_partner_customer()
|
||||
% set target_responsible = object.partner_id == object.event_id.partner_id
|
||||
% set target_customer = object.partner_id == customer
|
||||
<p>
|
||||
Hello ${object.common_name},<br/><br/>
|
||||
${object.event_id.user_id.partner_id.name} invited you to the ${object.event_id.name} meeting of ${object.event_id.user_id.company_id.name}.
|
||||
|
||||
% if is_online and target_customer:
|
||||
Your appointment <strong>${object.event_id.appointment_type_id.name}</strong> with ${object.event_id.user_id.name} has been booked.
|
||||
|
||||
% elif is_online and target_responsible:
|
||||
% if customer
|
||||
${customer.name} scheduled the following appointment <strong>${object.event_id.appointment_type_id.name}</strong> with you.
|
||||
% else
|
||||
Your appointment <strong>${object.event_id.appointment_type_id.name}</strong> has been booked.
|
||||
% endif
|
||||
|
||||
% elif not target_responsible:
|
||||
${object.event_id.user_id.partner_id.name} invited you for the <strong>${object.event_id.name}</strong> meeting.
|
||||
|
||||
% else:
|
||||
Your meeting <strong>${object.event_id.name}</strong> has been booked.
|
||||
% endif
|
||||
</p>
|
||||
<div style="text-align: center; margin: 16px 0px 16px 0px;">
|
||||
% set target = 'recurrence' if recurrent else 'meeting'
|
||||
<a href="/calendar/${target}/accept?token=${object.access_token}&id=${object.event_id.id}"
|
||||
% if not is_online or object.state != 'accepted':
|
||||
<a href="/calendar/meeting/accept?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
Accept</a>
|
||||
<a href="/calendar/${target}/decline?token=${object.access_token}&id=${object.event_id.id}"
|
||||
<a href="/calendar/meeting/decline?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
Decline</a>
|
||||
% endif
|
||||
<a href="/calendar/meeting/view?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
View</a>
|
||||
${'Reschedule' if is_online and target_customer else 'View'}
|
||||
</a>
|
||||
</div>
|
||||
<table border="0" cellpadding="0" cellspacing="0"><tr>
|
||||
% if not recurrent:
|
||||
<td width="130px;">
|
||||
<div style="border-top-left-radius: 3px; border-top-right-radius: 3px; font-size: 12px; border-collapse: separate; text-align: center; font-weight: bold; color: #ffffff; min-height: 18px; background-color: #875A7B; border: 1px solid #875A7B;">
|
||||
${object.event_id.get_interval('dayname', tz=object.partner_id.tz if not object.event_id.allday else None)}
|
||||
@@ -46,24 +66,35 @@
|
||||
</div>
|
||||
</td>
|
||||
<td width="20px;"/>
|
||||
% endif
|
||||
<td style="padding-top: 5px;">
|
||||
<p><strong>Details of the event</strong></p>
|
||||
<ul>
|
||||
% if is_online:
|
||||
<li>Appointment Type: ${object.event_id.appointment_type_id.name}</li>
|
||||
% endif
|
||||
% if object.event_id.location:
|
||||
<li>Location: ${object.event_id.location}
|
||||
(<a target="_blank" href="http://maps.google.com/maps?oi=map&q=${object.event_id.location}">View Map</a>)
|
||||
</li>
|
||||
% endif
|
||||
% if object.event_id.description :
|
||||
<li>Description: ${object.event_id.description}</li>
|
||||
% endif
|
||||
% if recurrent:
|
||||
<li>When: ${object.recurrence_id.name}</li>
|
||||
% endif
|
||||
% if not object.event_id.allday and object.event_id.duration
|
||||
<li>Duration: ${('%dH%02d' % (object.event_id.duration,round(object.event_id.duration*60)%60))}</li>
|
||||
% endif
|
||||
% if not is_online and object.event_id.description :
|
||||
<li>Description: ${object.event_id.description}</li>
|
||||
% elif is_online and object.event_id.description:
|
||||
% set splitted_description = object.event_id.description_to_html_lines()
|
||||
<li>Description:
|
||||
<ul>
|
||||
% for description_line in splitted_description:
|
||||
<li>${description_line | safe}</li>
|
||||
% endfor
|
||||
</ul>
|
||||
</li>
|
||||
% endif
|
||||
<li>Attendees
|
||||
<ul>
|
||||
% for attendee in object.event_id.attendee_ids:
|
||||
@@ -105,18 +136,38 @@
|
||||
<field name="lang">${object.partner_id.lang}</field>
|
||||
<field name="body_html" type="html">
|
||||
<div>
|
||||
% set colors = ctx.get('colors', {})
|
||||
% set recurrent = object.recurrence_id and not ctx['ignore_recurrence']
|
||||
% set colors = {'needsAction': 'grey', 'accepted': 'green', 'tentative': '#FFFF00', 'declined': 'red'}
|
||||
% set is_online = 'appointment_type_id' in object.event_id and object.event_id.appointment_type_id
|
||||
% set customer = object.event_id.find_partner_customer()
|
||||
% set target_responsible = object.partner_id == object.event_id.partner_id
|
||||
% set target_customer = object.partner_id == customer
|
||||
<p>
|
||||
Hello ${object.common_name},<br/><br/>
|
||||
The date of the meeting has been updated. The meeting ${object.event_id.name} created by ${object.event_id.user_id.partner_id.name} is now scheduled for ${object.event_id.get_display_time_tz(tz=object.partner_id.tz)}.
|
||||
% if is_online and target_responsible:
|
||||
% if customer:
|
||||
The date of your appointment with ${customer.name} has been updated.
|
||||
% else
|
||||
Your appointment has been updated.
|
||||
% endif
|
||||
The appointment <strong>${object.event_id.appointment_type_id.name}</strong> is now scheduled for
|
||||
${object.event_id.get_display_time_tz(tz=object.partner_id.tz)}.
|
||||
|
||||
% elif is_online and target_customer:
|
||||
The date of your appointment with ${object.event_id.user_id.partner_id.name} has been updated.
|
||||
The appointment <strong>${object.event_id.appointment_type_id.name}</strong> is now scheduled for
|
||||
${object.event_id.get_display_time_tz(tz=object.partner_id.tz)}.
|
||||
|
||||
% else:
|
||||
The date of the meeting has been updated.
|
||||
The meeting <strong>${object.event_id.name}</strong> created by ${object.event_id.user_id.partner_id.name} is now scheduled for
|
||||
${object.event_id.get_display_time_tz(tz=object.partner_id.tz)}.
|
||||
% endif
|
||||
</p>
|
||||
<div style="text-align: center; margin: 16px 0px 16px 0px;">
|
||||
% set target = 'recurrence' if recurrent else 'meeting'
|
||||
<a href="/calendar/${target}/accept?token=${object.access_token}&id=${object.event_id.id}"
|
||||
<a href="/calendar/meeting/accept?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
Accept</a>
|
||||
<a href="/calendar/${target}/decline?token=${object.access_token}&id=${object.event_id.id}"
|
||||
<a href="/calendar/meeting/decline?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
Decline</a>
|
||||
<a href="/calendar/meeting/view?token=${object.access_token}&id=${object.event_id.id}"
|
||||
@@ -124,7 +175,6 @@
|
||||
View</a>
|
||||
</div>
|
||||
<table border="0" cellpadding="0" cellspacing="0"><tr>
|
||||
% if not recurrent:
|
||||
<td width="130px;">
|
||||
<div style="border-top-left-radius: 3px; border-top-right-radius: 3px; font-size: 12px; border-collapse: separate; text-align: center; font-weight: bold; color: #ffffff; min-height: 18px; background-color: #875A7B; border: 1px solid #875A7B;">
|
||||
${object.event_id.get_interval('dayname', tz=object.partner_id.tz if not object.event_id.allday else None)}
|
||||
@@ -140,7 +190,6 @@
|
||||
</div>
|
||||
</td>
|
||||
<td width="20px;"/>
|
||||
% endif
|
||||
<td style="padding-top: 5px;">
|
||||
<p><strong>Details of the event</strong></p>
|
||||
<ul>
|
||||
@@ -149,15 +198,24 @@
|
||||
(<a target="_blank" href="http://maps.google.com/maps?oi=map&q=${object.event_id.location}">View Map</a>)
|
||||
</li>
|
||||
% endif
|
||||
% if object.event_id.description :
|
||||
<li>Description: ${object.event_id.description}</li>
|
||||
% endif
|
||||
% if recurrent:
|
||||
<li>When: ${object.recurrence_id.name}</li>
|
||||
% endif
|
||||
% if not object.event_id.allday and object.event_id.duration
|
||||
<li>Duration: ${('%dH%02d' % (object.event_id.duration,round(object.event_id.duration*60)%60))}</li>
|
||||
% endif
|
||||
% if not is_online and object.event_id.description :
|
||||
<li>Description: ${object.event_id.description}</li>
|
||||
% elif is_online and object.event_id.description:
|
||||
% set splitted_description = object.event_id.description_to_html_lines()
|
||||
<li>Description:
|
||||
<ul>
|
||||
% for description_line in splitted_description:
|
||||
<li>${description_line | safe}</li>
|
||||
% endfor
|
||||
</ul>
|
||||
</li>
|
||||
% endif
|
||||
<li>Attendees
|
||||
<ul>
|
||||
% for attendee in object.event_id.attendee_ids:
|
||||
@@ -199,21 +257,18 @@
|
||||
<field name="lang">${object.partner_id.lang}</field>
|
||||
<field name="body_html" type="html">
|
||||
<div>
|
||||
% set colors = {'needsAction': 'grey', 'accepted': 'green', 'tentative': '#FFFF00', 'declined': 'red'}
|
||||
<!--
|
||||
In a recurring event case, the object.event_id is always the first event
|
||||
This makes the event date (and a lot of other information) incorrect
|
||||
-->
|
||||
% set event_id = ctx.get('force_event_id') or object.event_id
|
||||
% set colors = {'needsAction': 'grey', 'accepted': 'green', 'tentative': '#FFFF00', 'declined': 'red'}
|
||||
% set is_online = 'appointment_type_id' in object.event_id and object.event_id.appointment_type_id
|
||||
% set recurrent = object.recurrence_id and not ctx.get('calendar_template_ignore_recurrence')
|
||||
<p>
|
||||
Hello ${object.common_name},<br/><br/>
|
||||
This is a reminder for the below event :
|
||||
</p>
|
||||
<div style="text-align: center; margin: 16px 0px 16px 0px;">
|
||||
<a href="/calendar/meeting/accept?token=${object.access_token}&id=${object.event_id.id}"
|
||||
<a href="/calendar/${'recurrence' if recurrent else 'meeting'}/accept?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
Accept</a>
|
||||
<a href="/calendar/meeting/decline?token=${object.access_token}&id=${object.event_id.id}"
|
||||
<a href="/calendar/${'recurrence' if recurrent else 'meeting'}/decline?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
Decline</a>
|
||||
<a href="/calendar/meeting/view?token=${object.access_token}&id=${object.event_id.id}"
|
||||
@@ -223,16 +278,16 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0"><tr>
|
||||
<td width="130px;">
|
||||
<div style="border-top-left-radius: 3px; border-top-right-radius: 3px; font-size: 12px; border-collapse: separate; text-align: center; font-weight: bold; color: #ffffff; min-height: 18px; background-color: #875A7B; border: 1px solid #875A7B;">
|
||||
${event_id.get_interval('dayname', tz=object.partner_id.tz if not event_id.allday else None)}
|
||||
${object.event_id.get_interval('dayname', tz=object.partner_id.tz if not object.event_id.allday else None)}
|
||||
</div>
|
||||
<div style="font-size: 48px; min-height: auto; font-weight: bold; text-align: center; color: #5F5F5F; background-color: #F8F8F8; border: 1px solid #875A7B;">
|
||||
${event_id.get_interval('day', tz=object.partner_id.tz if not event_id.allday else None)}
|
||||
${object.event_id.get_interval('day', tz=object.partner_id.tz if not object.event_id.allday else None)}
|
||||
</div>
|
||||
<div style='font-size: 12px; text-align: center; font-weight: bold; color: #ffffff; background-color: #875A7B;'>
|
||||
${event_id.get_interval('month', tz=object.partner_id.tz if not event_id.allday else None)}
|
||||
${object.event_id.get_interval('month', tz=object.partner_id.tz if not object.event_id.allday else None)}
|
||||
</div>
|
||||
<div style="border-collapse: separate; color: #5F5F5F; text-align: center; font-size: 12px; border-bottom-right-radius: 3px; font-weight: bold; border: 1px solid #875A7B; border-bottom-left-radius: 3px;">
|
||||
${not event_id.allday and event_id.get_interval('time', tz=object.partner_id.tz) or ''}
|
||||
${not object.event_id.allday and object.event_id.get_interval('time', tz=object.partner_id.tz) or ''}
|
||||
</div>
|
||||
</td>
|
||||
<td width="20px;"/>
|
||||
@@ -244,11 +299,23 @@
|
||||
(<a target="_blank" href="http://maps.google.com/maps?oi=map&q=${object.event_id.location}">View Map</a>)
|
||||
</li>
|
||||
% endif
|
||||
% if object.event_id.description :
|
||||
<li>Description: ${object.event_id.description}</li>
|
||||
% if recurrent:
|
||||
<li>When: ${object.recurrence_id.name}</li>
|
||||
% endif
|
||||
% if not object.event_id.allday and object.event_id.duration
|
||||
<li>Duration: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}</li>
|
||||
<li>Duration: ${('%dH%02d' % (object.event_id.duration,round(object.event_id.duration*60)%60))}</li>
|
||||
% endif
|
||||
% if not is_online and object.event_id.description :
|
||||
<li>Description: ${object.event_id.description}</li>
|
||||
% elif is_online and object.event_id.description:
|
||||
% set splitted_description = object.event_id.description_to_html_lines()
|
||||
<li>Description:
|
||||
<ul>
|
||||
% for description_line in splitted_description:
|
||||
<li>${description_line | safe}</li>
|
||||
% endfor
|
||||
</ul>
|
||||
</li>
|
||||
% endif
|
||||
<li>Attendees
|
||||
<ul>
|
||||
|
||||
@@ -167,10 +167,9 @@ class AlarmManager(models.AbstractModel):
|
||||
# Executed via cron
|
||||
events = self._get_events_to_notify('email')
|
||||
attendees = events.attendee_ids.filtered(lambda a: a.state != 'declined')
|
||||
attendees._send_mail_to_attendees(
|
||||
attendees.with_context(calendar_template_ignore_recurrence=True)._send_mail_to_attendees(
|
||||
'calendar.calendar_template_meeting_reminder',
|
||||
force_send=True,
|
||||
ignore_recurrence=True,
|
||||
)
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -15,6 +15,7 @@ class Attendee(models.Model):
|
||||
_name = 'calendar.attendee'
|
||||
_rec_name = 'common_name'
|
||||
_description = 'Calendar Attendee Information'
|
||||
_order = 'create_date ASC'
|
||||
|
||||
def _default_access_token(self):
|
||||
return uuid.uuid4().hex
|
||||
@@ -80,18 +81,16 @@ class Attendee(models.Model):
|
||||
partners = (event.attendee_ids & self).partner_id & event.message_partner_ids
|
||||
event.message_unsubscribe(partner_ids=partners.ids)
|
||||
|
||||
def _send_mail_to_attendees(self, template_xmlid, force_send=False, ignore_recurrence=False):
|
||||
def _send_mail_to_attendees(self, template_xmlid, force_send=False):
|
||||
""" Send mail for event invitation to event attendees.
|
||||
:param template_xmlid: xml id of the email template to use to send the invitation
|
||||
:param force_send: if set to True, the mail(s) will be sent immediately (instead of the next queue processing)
|
||||
:param ignore_recurrence: ignore event recurrence
|
||||
"""
|
||||
res = False
|
||||
|
||||
if self.env['ir.config_parameter'].sudo().get_param('calendar.block_mail') or self._context.get("no_mail_to_attendees"):
|
||||
return res
|
||||
|
||||
calendar_view = self.env.ref('calendar.view_calendar_event_calendar')
|
||||
invitation_template = self.env.ref(template_xmlid, raise_if_not_found=False)
|
||||
if not invitation_template:
|
||||
_logger.warning("Template %s could not be found. %s not notified." % (template_xmlid, self))
|
||||
@@ -99,25 +98,8 @@ class Attendee(models.Model):
|
||||
# get ics file for all meetings
|
||||
ics_files = self.mapped('event_id')._get_ics_file()
|
||||
|
||||
# prepare rendering context for mail template
|
||||
colors = {
|
||||
'needsAction': 'grey',
|
||||
'accepted': 'green',
|
||||
'tentative': '#FFFF00',
|
||||
'declined': 'red'
|
||||
}
|
||||
rendering_context = dict(self._context)
|
||||
rendering_context.update({
|
||||
'colors': colors,
|
||||
'ignore_recurrence': ignore_recurrence,
|
||||
'action_id': self.env['ir.actions.act_window'].sudo().search([('view_id', '=', calendar_view.id)], limit=1).id,
|
||||
'dbname': self._cr.dbname,
|
||||
'base_url': self.env['ir.config_parameter'].sudo().get_param('web.base.url', default='http://localhost:8069'),
|
||||
})
|
||||
|
||||
for attendee in self:
|
||||
if attendee.email and attendee.partner_id != self.env.user.partner_id:
|
||||
# FIXME: is ics_file text or bytes?
|
||||
event_id = attendee.event_id.id
|
||||
ics_file = ics_files.get(event_id)
|
||||
|
||||
@@ -128,7 +110,7 @@ class Attendee(models.Model):
|
||||
'mimetype': 'text/calendar',
|
||||
'datas': base64.b64encode(ics_file)})
|
||||
]
|
||||
body = invitation_template.with_context(rendering_context)._render_field(
|
||||
body = invitation_template._render_field(
|
||||
'body_html',
|
||||
attendee.ids,
|
||||
compute_lang=True,
|
||||
|
||||
@@ -16,7 +16,7 @@ from odoo.addons.calendar.models.calendar_attendee import Attendee
|
||||
from odoo.addons.calendar.models.calendar_recurrence import weekday_to_field, RRULE_TYPE_SELECTION, END_TYPE_SELECTION, MONTH_BY_SELECTION, WEEKDAY_SELECTION, BYDAY_SELECTION
|
||||
from odoo.tools.translate import _
|
||||
from odoo.tools.misc import get_lang
|
||||
from odoo.tools import pycompat
|
||||
from odoo.tools import pycompat, html_escape
|
||||
from odoo.exceptions import UserError, ValidationError, AccessError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -287,7 +287,11 @@ class Meeting(models.Model):
|
||||
|
||||
def _compute_attendee(self):
|
||||
for meeting in self:
|
||||
attendee = meeting._find_my_attendee()
|
||||
attendee = next(
|
||||
(attendee for attendee in self.attendee_ids
|
||||
if attendee.partner_id == self.env.user.partner_id),
|
||||
self.env['calendar.attendee']
|
||||
)
|
||||
meeting.attendee_status = attendee.state if attendee else 'needsAction'
|
||||
|
||||
@api.constrains('start', 'stop', 'start_date', 'stop_date')
|
||||
@@ -477,7 +481,11 @@ class Meeting(models.Model):
|
||||
start_date = fields.Datetime.to_datetime(values.get('start'))
|
||||
# Only notify on future events
|
||||
if start_date and start_date >= fields.Datetime.now():
|
||||
(current_attendees & previous_attendees)._send_mail_to_attendees('calendar.calendar_template_meeting_changedate', ignore_recurrence=not update_recurrence)
|
||||
(current_attendees & previous_attendees).with_context(
|
||||
calendar_template_ignore_recurrence=not update_recurrence
|
||||
)._send_mail_to_attendees(
|
||||
'calendar.calendar_template_meeting_changedate'
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@@ -713,15 +721,13 @@ class Meeting(models.Model):
|
||||
return attendee.do_decline()
|
||||
return attendee.do_tentative()
|
||||
|
||||
def _find_my_attendee(self):
|
||||
""" Return the first attendee where the user connected has been invited
|
||||
from all the meeting_ids in parameters.
|
||||
"""
|
||||
def find_partner_customer(self):
|
||||
self.ensure_one()
|
||||
for attendee in self.attendee_ids:
|
||||
if self.env.user.partner_id == attendee.partner_id:
|
||||
return attendee
|
||||
return False
|
||||
return next(
|
||||
(attendee.partner_id for attendee in self.attendee_ids.sorted('create_date')
|
||||
if attendee.partner_id != self.user_id.partner_id),
|
||||
self.env['calendar.attendee']
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# TOOLS
|
||||
@@ -920,3 +926,56 @@ class Meeting(models.Model):
|
||||
'id', 'active', 'allday',
|
||||
'duration', 'user_id', 'interval',
|
||||
'count', 'rrule', 'recurrence_id', 'show_as', 'privacy'}
|
||||
|
||||
def description_to_html_lines(self):
|
||||
""" Description could contain some structure content, depending on
|
||||
its use. Notably appointment could add some structured data in it
|
||||
in addition to free text. Purpose of this method is to generate a
|
||||
simple html.
|
||||
|
||||
Input (self.description) example:
|
||||
Some free text salespeople added
|
||||
as multi line
|
||||
* Mobile: +320475000000
|
||||
* Email: my.email@test.example.com
|
||||
* SingleLine: answer
|
||||
* MultiLine:
|
||||
Answer1
|
||||
Answer2
|
||||
Answer3
|
||||
* Dropdown: answer
|
||||
* Radio: answer
|
||||
* Checkboxes: answer1, answer2
|
||||
Some free text salespeople added
|
||||
as multi line
|
||||
|
||||
Output: a list of items[
|
||||
'Some free text salespeople added<br />as multi line',
|
||||
'Mobile: +320475000000',
|
||||
'Email: my.email@test.example.com',
|
||||
'SingleLine: answer',
|
||||
'MultiLine:<br />Answer1<br />Answer2<br />Answer3',
|
||||
'Dropdown: answer',
|
||||
'Radio: answer',
|
||||
'Checkboxes: answer1, answer2',
|
||||
'Some free text salespeople added<br .>as multi line']
|
||||
|
||||
Each item of returned list is escaped so that only our intended <br />
|
||||
are html tags. It should therefore be safe.
|
||||
"""
|
||||
|
||||
final_lines = []
|
||||
parsed_lines = []
|
||||
for line in self.description.split('\n'):
|
||||
if not line.strip():
|
||||
continue
|
||||
# new line
|
||||
if line.startswith(' *'):
|
||||
if parsed_lines:
|
||||
final_lines.append('<br />'.join(html_escape(line) for line in parsed_lines))
|
||||
parsed_lines = [line.lstrip(' *')]
|
||||
else:
|
||||
parsed_lines.append(line)
|
||||
if parsed_lines:
|
||||
final_lines.append('<br />'.join(html_escape(line) for line in parsed_lines))
|
||||
return final_lines
|
||||
|
||||
@@ -9,11 +9,11 @@ class Attendee(models.Model):
|
||||
_name = 'calendar.attendee'
|
||||
_inherit = 'calendar.attendee'
|
||||
|
||||
def _send_mail_to_attendees(self, template_xmlid, force_send=False, ignore_recurrence=False):
|
||||
def _send_mail_to_attendees(self, template_xmlid, force_send=False):
|
||||
""" Override
|
||||
If not synced with Google, let Odoo in charge of sending emails
|
||||
Otherwise, nothing to do: Google will send them
|
||||
"""
|
||||
with google_calendar_token(self.env.user.sudo()) as token:
|
||||
if not token:
|
||||
super()._send_mail_to_attendees(template_xmlid, force_send, ignore_recurrence)
|
||||
super()._send_mail_to_attendees(template_xmlid, force_send)
|
||||
|
||||
@@ -10,11 +10,11 @@ class Attendee(models.Model):
|
||||
_name = 'calendar.attendee'
|
||||
_inherit = 'calendar.attendee'
|
||||
|
||||
def _send_mail_to_attendees(self, template_xmlid, force_send=False, ignore_recurrence=False):
|
||||
def _send_mail_to_attendees(self, template_xmlid, force_send=False):
|
||||
""" Override the super method
|
||||
If not synced with Microsoft Outlook, let Odoo in charge of sending emails
|
||||
Otherwise, Microsoft Outlook will send them
|
||||
"""
|
||||
with microsoft_calendar_token(self.env.user.sudo()) as token:
|
||||
if not token:
|
||||
super()._send_mail_to_attendees(template_xmlid, force_send, ignore_recurrence)
|
||||
super()._send_mail_to_attendees(template_xmlid, force_send)
|
||||
|
||||
Reference in New Issue
Block a user