summaryrefslogtreecommitdiff
path: root/addons/google_calendar/models/calendar.py
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/google_calendar/models/calendar.py
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/google_calendar/models/calendar.py')
-rw-r--r--addons/google_calendar/models/calendar.py235
1 files changed, 235 insertions, 0 deletions
diff --git a/addons/google_calendar/models/calendar.py b/addons/google_calendar/models/calendar.py
new file mode 100644
index 00000000..0079c9c0
--- /dev/null
+++ b/addons/google_calendar/models/calendar.py
@@ -0,0 +1,235 @@
+# -*- 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, tools, _
+
+
+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 not event.google_id and 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_create_multi
+ def create(self, vals_list):
+ return super().create([
+ dict(vals, need_sync=False) if vals.get('recurrence_id') or 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)
+ res = super().write(values)
+ if recurrence_update_setting in ('all_events',) and len(self) == 1 and values.keys() & self._get_google_synced_fields():
+ self.recurrence_id.need_sync = True
+ return res
+
+ 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 len(google_attendees) == 0 and 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 = {tools.email_normalize(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.user.partner_id if attendee.get('self') else 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 tools.email_normalize(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 not self.active:
+ values['status'] = 'cancelled'
+ if self.user_id and self.user_id != self.env.user:
+ values['extendedProperties']['shared']['%s_owner_id' % self.env.cr.dbname] = self.user_id.id
+ elif not self.user_id:
+ # We don't store the real owner identity (mail)
+ # We can't store on the shared properties in that case without getting a 403
+ # If several odoo users are attendees but the owner is not in odoo, the event will be duplicated on odoo database
+ # if we are not the owner, we should change the post values to avoid errors because we don't have enough rights
+ # See https://developers.google.com/calendar/concepts/sharing
+ keep_keys = ['id', 'attendees', 'start', 'end', 'reminders']
+ values = {key: val for key, val in values.items() if key in keep_keys}
+ # values['extendedProperties']['private] should be used if the owner is not an odoo user
+ values['extendedProperties'] = {
+ 'private': {
+ '%s_odoo_id' % self.env.cr.dbname: self.id,
+ },
+ }
+ 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'
+
+ def _notify_attendees(self):
+ # filter events before notifying attendees through calendar_alarm_manager
+ need_notifs = self.filtered(lambda event: event.alarm_ids and event.stop >= fields.Datetime.now())
+ partners = need_notifs.partner_ids
+ if partners:
+ self.env['calendar.alarm_manager']._notify_next_alarm(partners.ids)