Add videoconference sync and fix some bugs:
- Properly synchronize the timezone for users with timezones set in Odoo - Properly synchronize attendees, i.e. remove the organizer from the attendees list on the CalDAV server and re-add them on the event attendees in Odoo (to show up in the calendar view).
This commit is contained in:
parent
4781b33123
commit
137789cd79
2 changed files with 39 additions and 14 deletions
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
{
|
||||
'name': 'CalDAV Synchronization',
|
||||
'version': '17.0.0.5.2',
|
||||
'version': '17.0.0.5.3',
|
||||
'license': 'LGPL-3',
|
||||
'category': 'Productivity',
|
||||
'summary': 'Synchronize Odoo Calendar Events with CalDAV Servers',
|
||||
|
|
@ -20,6 +20,9 @@
|
|||
'author': 'Bemade Inc.',
|
||||
'website': 'https://www.bemade.org',
|
||||
'depends': ['base', 'calendar'],
|
||||
'external_dependencies': {
|
||||
'python': ['caldav', 'icalendar', 'bs4'],
|
||||
},
|
||||
'images': ['static/description/images/main_screenshot.png'],
|
||||
'data': [
|
||||
'views/res_users_views.xml',
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ import uuid
|
|||
from odoo import models, api, fields
|
||||
import caldav
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from icalendar import Calendar, Event, vCalAddress, Alarm, vText, vWeekday
|
||||
from datetime import datetime, timezone
|
||||
from icalendar import Calendar, Event, vCalAddress, vText, vWeekday
|
||||
from bs4 import BeautifulSoup
|
||||
from datetime import timedelta
|
||||
import dateutil
|
||||
import re
|
||||
from pytz import timezone, utc
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -143,16 +144,32 @@ class CalendarEvent(models.Model):
|
|||
calendar.add('version', '2.0')
|
||||
|
||||
for event in self:
|
||||
user_tz = timezone('UTC')
|
||||
if event.user_id.tz:
|
||||
user_tz = timezone(event.user_id.tz)
|
||||
ical_event = Event()
|
||||
ical_event.add('uid', event.caldav_uid)
|
||||
ical_event.add('dtstamp', event.write_date.replace(tzinfo=None))
|
||||
ical_event.add('dtstamp', utc.localize(event.write_date).astimezone(user_tz))
|
||||
if event.name:
|
||||
ical_event.add('summary', event.name)
|
||||
if event.description:
|
||||
ical_event.add('description', event.description)
|
||||
if event.description and self._html_to_text(event.description):
|
||||
ical_event.add('description', self._html_to_text(event.description))
|
||||
if event.location:
|
||||
ical_event.add('location', event.location)
|
||||
|
||||
if event.videocall_location:
|
||||
ical_event.add('CONFERENCE', event.videocall_location)
|
||||
for partner in event.partner_ids:
|
||||
if partner == event.user_id.partner_id:
|
||||
continue
|
||||
attendee = vCalAddress(f'MAILTO:{partner.email}')
|
||||
attendee.params['cn'] = vText(partner.name)
|
||||
attendee_record = self.env['calendar.attendee'].search([('event_id', '=', event.id), ('partner_id', '=', partner.id)], limit=1)
|
||||
if attendee_record:
|
||||
attendee.params['partstat'] = vText(self._map_attendee_status(attendee_record.state))
|
||||
ical_event.add('attendee', attendee, encode=0)
|
||||
organizer = vCalAddress(f"MAILTO:{event.user_id.email}")
|
||||
organizer.params['cn'] = event.user_id.name
|
||||
ical_event.add('organizer', organizer)
|
||||
# Add RRULE if the event is recurrent
|
||||
if event.recurrency and event.recurrence_id:
|
||||
rrule = event.recurrence_id._get_rrule()
|
||||
|
|
@ -160,8 +177,8 @@ class CalendarEvent(models.Model):
|
|||
ical_event.add('rrule', rrule_dict)
|
||||
|
||||
# Add DTSTART and DTEND
|
||||
ical_event.add('dtstart', event.start.replace(tzinfo=None))
|
||||
ical_event.add('dtend', event.stop.replace(tzinfo=None))
|
||||
ical_event.add('dtstart', utc.localize(event.start).astimezone(user_tz))
|
||||
ical_event.add('dtend', utc.localize(event.stop).astimezone(user_tz))
|
||||
|
||||
calendar.add_component(ical_event)
|
||||
|
||||
|
|
@ -269,7 +286,6 @@ class CalendarEvent(models.Model):
|
|||
|
||||
def sync_event_from_ical(self, ical_event):
|
||||
email_regex = re.compile(r'[a-z0-9\.\-+_]+@[a-z0-9\.\-+_]+\.[a-z]+')
|
||||
now = datetime.now()
|
||||
current_user_email = self.env.user.email.lower()
|
||||
|
||||
for component in ical_event.subcomponents:
|
||||
|
|
@ -300,16 +316,18 @@ class CalendarEvent(models.Model):
|
|||
existing_instance = self._get_existing_instance(uid, recurrence_id)
|
||||
start = component.decoded('dtstart')
|
||||
if isinstance(start, datetime):
|
||||
start = start.replace(tzinfo=None)
|
||||
start = start.astimezone(utc).replace(tzinfo=None)
|
||||
end = component.decoded('dtend')
|
||||
if isinstance(end, datetime):
|
||||
end = end.replace(tzinfo=None)
|
||||
end = end.astimezone(utc).replace(tzinfo=None)
|
||||
|
||||
values = {
|
||||
'name': str(component.get('summary')),
|
||||
'start': start,
|
||||
'stop': end,
|
||||
'description': str(component.get('description')),
|
||||
'location': str(component.get('location')),
|
||||
'description': self._extract_component_text(component, 'description'),
|
||||
'location': self._extract_component_text(component, 'location'),
|
||||
'videocall_location': self._extract_component_text(component, 'conference'),
|
||||
'caldav_uid': uid,
|
||||
'partner_ids': [(6, 0, attendee_ids.ids)],
|
||||
}
|
||||
|
|
@ -328,6 +346,10 @@ class CalendarEvent(models.Model):
|
|||
})
|
||||
existing_instance.with_context({'caldav_no_sync': True}).write(values)
|
||||
|
||||
@staticmethod
|
||||
def _extract_component_text(component, subcomponent_name):
|
||||
text = str(component.get(subcomponent_name))
|
||||
text = text if text != 'None' else ''
|
||||
|
||||
@staticmethod
|
||||
def _html_to_text(html):
|
||||
|
|
|
|||
Loading…
Reference in a new issue