diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/google_calendar/models/calendar.py | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/google_calendar/models/calendar.py')
| -rw-r--r-- | addons/google_calendar/models/calendar.py | 235 |
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) |
