Files
odoo_source/addons/google_calendar/models/calendar.py
T
Aurélien (avd) 36bc5dcb60 [Fix] google_calendar: fix owner attendee being deleted
Currently when creating a calendar.event with google sync switched
on and without adding another attendee, the event is immediatly
removed from the calendar view. This is because the user_id is filtered out
of the attendee_ids before sending the values to google calendar. Then, at
the next sync, google response attendees list does not contain the user_id
so Odoo turns that into a [(3, id)] command. Thus, the corresponding event
disappear from the current calendar view even though it is still in the DB.

This fix is essentially the same as the one implemented in the
microsoft_calendar addons available from 14.0 onwards. It adds the
calendar owner if the event organizer is not null. This way, even
though the owner is not sent as an attendee, it is not removed
after google's response.

closes odoo/odoo#63190

X-original-commit: 04aa586a53c2b78cf6646d96d29cbd1e8969de3f
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
2020-12-10 16:08:42 +00:00

219 lines
9.3 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import pytz
from dateutil.parser import parse
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
class Meeting(models.Model):
_name = 'calendar.event'
_inherit = ['calendar.event', 'google.calendar.sync']
google_id = fields.Char(
'Google Calendar Event Id', compute='_compute_google_id', store=True, readonly=False)
@api.depends('recurrence_id.google_id')
def _compute_google_id(self):
# google ids of recurring events are built from the recurrence id and the
# original starting time in the recurrence.
# The `start` field does not appear in the dependencies on purpose!
# Event if the event is moved, the google_id remains the same.
for event in self:
google_recurrence_id = event.recurrence_id._get_event_google_id(event)
if google_recurrence_id:
event.google_id = google_recurrence_id
elif not event.google_id:
event.google_id = False
@api.model
def _get_google_synced_fields(self):
return {'name', 'description', 'allday', 'start', 'date_end', 'stop',
'attendee_ids', 'alarm_ids', 'location', 'privacy', 'active'}
@api.model
def _restart_google_sync(self):
self.env['calendar.event'].search(self._get_sync_domain()).write({
'need_sync': True,
})
@api.model_create_multi
def create(self, vals_list):
return super().create([
dict(vals, need_sync=False) if vals.get('recurrency') else vals
for vals in vals_list
])
def write(self, values):
recurrence_update_setting = values.get('recurrence_update')
if recurrence_update_setting in ('all_events', 'future_events') and len(self) == 1:
values = dict(values, need_sync=False)
return super().write(values)
def _get_sync_domain(self):
return [('partner_ids.user_ids', 'in', self.env.user.id)]
@api.model
def _odoo_values(self, google_event, default_reminders=()):
if google_event.is_cancelled():
return {'active': False}
alarm_commands = self._odoo_reminders_commands(google_event.reminders.get('overrides') or default_reminders)
attendee_commands, partner_commands = self._odoo_attendee_commands(google_event)
values = {
'name': google_event.summary or _("(No title)"),
'description': google_event.description,
'location': google_event.location,
'user_id': google_event.owner(self.env).id,
'privacy': google_event.visibility or self.default_get(['privacy'])['privacy'],
'attendee_ids': attendee_commands,
'partner_ids': partner_commands,
'alarm_ids': alarm_commands,
'recurrency': google_event.is_recurrent()
}
if not google_event.is_recurrence():
values['google_id'] = google_event.id
if google_event.start.get('dateTime'):
# starting from python3.7, use the new [datetime, date].fromisoformat method
start = parse(google_event.start.get('dateTime')).astimezone(pytz.utc).replace(tzinfo=None)
stop = parse(google_event.end.get('dateTime')).astimezone(pytz.utc).replace(tzinfo=None)
values['allday'] = False
else:
start = parse(google_event.start.get('date'))
stop = parse(google_event.end.get('date')) - relativedelta(days=1)
values['allday'] = True
values['start'] = start
values['stop'] = stop
return values
@api.model
def _odoo_attendee_commands(self, google_event):
attendee_commands = []
partner_commands = []
google_attendees = google_event.attendees or []
if google_event.organizer and google_event.organizer.get('self', False):
user = google_event.owner(self.env)
google_attendees += [{
'email': user.partner_id.email,
'status': {'response': 'accepted'},
}]
emails = [a.get('email') for a in google_attendees]
existing_attendees = self.env['calendar.attendee']
if google_event.exists(self.env):
existing_attendees = self.browse(google_event.odoo_id(self.env)).attendee_ids
attendees_by_emails = {a.email: a for a in existing_attendees}
for attendee in google_attendees:
email = attendee.get('email')
if email in attendees_by_emails:
# Update existing attendees
attendee_commands += [(1, attendees_by_emails[email].id, {'state': attendee.get('responseStatus')})]
else:
# Create new attendees
partner = self.env['res.partner'].find_or_create(attendee.get('email'))
attendee_commands += [(0, 0, {'state': attendee.get('responseStatus'), 'partner_id': partner.id})]
partner_commands += [(4, partner.id)]
if attendee.get('displayName') and not partner.name:
partner.name = attendee.get('displayName')
for odoo_attendee in attendees_by_emails.values():
# Remove old attendees
if odoo_attendee.email not in emails:
attendee_commands += [(2, odoo_attendee.id)]
partner_commands += [(3, odoo_attendee.partner_id.id)]
return attendee_commands, partner_commands
@api.model
def _odoo_reminders_commands(self, reminders=()):
commands = []
for reminder in reminders:
alarm_type = 'email' if reminder.get('method') == 'email' else 'notification'
alarm_type_label = _("Email") if alarm_type == 'email' else _("Notification")
minutes = reminder.get('minutes', 0)
alarm = self.env['calendar.alarm'].search([
('alarm_type', '=', alarm_type),
('duration_minutes', '=', minutes)
], limit=1)
if alarm:
commands += [(4, alarm.id)]
else:
if minutes % (60*24) == 0:
interval = 'days'
duration = minutes / 60 / 24
name = _(
"%(reminder_type)s - %(duration)s Days",
reminder_type=alarm_type_label,
duration=duration,
)
elif minutes % 60 == 0:
interval = 'hours'
duration = minutes / 60
name = _(
"%(reminder_type)s - %(duration)s Hours",
reminder_type=alarm_type_label,
duration=duration,
)
else:
interval = 'minutes'
duration = minutes
name = _(
"%(reminder_type)s - %(duration)s Minutes",
reminder_type=alarm_type_label,
duration=duration,
)
commands += [(0, 0, {'duration': duration, 'interval': interval, 'name': name, 'alarm_type': alarm_type})]
return commands
def _google_values(self):
if self.allday:
start = {'date': self.start_date.isoformat()}
end = {'date': (self.stop_date + relativedelta(days=1)).isoformat()}
else:
start = {'dateTime': pytz.utc.localize(self.start).isoformat()}
end = {'dateTime': pytz.utc.localize(self.stop).isoformat()}
reminders = [{
'method': "email" if alarm.alarm_type == "email" else "popup",
'minutes': alarm.duration_minutes
} for alarm in self.alarm_ids]
attendee_ids = self.attendee_ids.filtered(lambda a: a.partner_id not in self.user_id.partner_id)
values = {
'id': self.google_id,
'start': start,
'end': end,
'summary': self.name,
'description': self.description or '',
'location': self.location or '',
'guestsCanModify': True,
'organizer': {'email': self.user_id.email, 'self': self.user_id == self.env.user},
'attendees': [{'email': attendee.email, 'responseStatus': attendee.state} for attendee in self.attendee_ids],
'extendedProperties': {
'shared': {
'%s_odoo_id' % self.env.cr.dbname: self.id,
},
},
'reminders': {
'overrides': reminders,
'useDefault': False,
}
}
if self.privacy:
values['visibility'] = self.privacy
if self.user_id != self.env.user:
values['extendedProperties']['shared']['%s_owner_id' % self.env.cr.dbname] = self.user_id.id
if not self.active:
values['status'] = 'cancelled'
return values
def _cancel(self):
# only owner can delete => others refuse the event
user = self.env.user
my_cancelled_records = self.filtered(lambda e: e.user_id == user)
super(Meeting, my_cancelled_records)._cancel()
attendees = (self - my_cancelled_records).attendee_ids.filtered(lambda a: a.partner_id == user.partner_id)
attendees.state = 'declined'