testing via the interface seems to be working, though some parameters are not set to sync yet.

This commit is contained in:
Marc Durepos 2024-05-23 16:03:55 -04:00
parent 9d2ea9a8fa
commit 957de3221c
3 changed files with 100 additions and 44 deletions

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import calendar_event from . import calendar_event
from . import res_users from . import res_users
from . import caldav_calendar

View file

@ -1,9 +0,0 @@
from odoo import models, fields
class CaldavCalendar(models.Model):
_name = 'caldav.calendar'
_description = 'CalDAV Calendar'
user_id = fields.Many2one('res.users', string='User', required=True, ondelete='cascade')
name = fields.Char('Name', required=True)
url = fields.Char('URL', required=True)

View file

@ -1,7 +1,9 @@
import uuid
from odoo import models, api, fields from odoo import models, api, fields
import caldav import caldav
from caldav.elements import dav, cdav
import logging import logging
from datetime import datetime
from icalendar import Calendar, Event
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -12,18 +14,35 @@ class CalendarEvent(models.Model):
@api.model @api.model
def create(self, vals): def create(self, vals):
if not vals.get('caldav_uid'):
vals['caldav_uid'] = str(uuid.uuid4())
event = super(CalendarEvent, self).create(vals) event = super(CalendarEvent, self).create(vals)
event.sync_to_caldav() if not self.env.context.get('caldav_no_sync'):
try:
_logger.debug(f"Creating event {event.name} in CalDAV")
event.sync_create_to_caldav()
except Exception as e:
_logger.error(f"Failed to create event in CalDAV server: {e}")
return event return event
def write(self, vals): def write(self, vals):
res = super(CalendarEvent, self).write(vals) res = super(CalendarEvent, self).write(vals)
self.sync_to_caldav() if not self.env.context.get('caldav_no_sync'):
try:
_logger.debug(f"Updating event {self.name} in CalDAV")
self.with_context(caldav_no_sync=True).sync_update_to_caldav()
except Exception as e:
_logger.error(f"Failed to update event in CalDAV server: {e}")
return res return res
def unlink(self): def unlink(self):
for event in self: if not self.env.context.get('caldav_no_sync'):
event.remove_from_caldav() for event in self:
try:
_logger.debug(f"Removing event {event.name} from CalDAV")
event.with_context(caldav_no_sync=True).sync_remove_from_caldav()
except Exception as e:
_logger.error(f"Failed to delete event from CalDAV server: {e}")
return super(CalendarEvent, self).unlink() return super(CalendarEvent, self).unlink()
def _is_caldav_enabled(self): def _is_caldav_enabled(self):
@ -38,45 +57,68 @@ class CalendarEvent(models.Model):
password=user.caldav_password password=user.caldav_password
) )
def sync_to_caldav(self): def sync_create_to_caldav(self):
if not self._is_caldav_enabled(): if not self._is_caldav_enabled():
return return
client = self._get_caldav_client() client = self._get_caldav_client()
calendar = client.calendar(self.env.user.caldav_calendar_url) calendar = client.calendar(url=self.env.user.caldav_calendar_url)
for event in self: for event in self:
ical_event = event._get_icalendar() ical_event = event._get_icalendar()
if event.caldav_uid: try:
caldav_event = calendar.event_by_uid(event.caldav_uid) _logger.debug(f"Creating new CalDAV event for {event.name}")
caldav_event.save(ical_event)
else:
caldav_event = calendar.add_event(ical_event) caldav_event = calendar.add_event(ical_event)
event.caldav_uid = caldav_event.id caldav_uid = caldav_event.vobject_instance.vevent.uid.value
_logger.debug(f"New CalDAV UID: {caldav_uid}")
event.with_context(caldav_no_sync=True).write({'caldav_uid': caldav_uid})
except Exception as e:
_logger.error(f"Failed to sync event to CalDAV server: {e}")
def remove_from_caldav(self): def sync_update_to_caldav(self):
if not self._is_caldav_enabled(): if not self._is_caldav_enabled():
return return
client = self._get_caldav_client() client = self._get_caldav_client()
calendar = client.calendar(self.env.user.caldav_calendar_url) calendar = client.calendar(url=self.env.user.caldav_calendar_url)
for event in self:
ical_event = event._get_icalendar()
try:
_logger.debug(f"Updating existing CalDAV event {event.caldav_uid}")
calendar.save_event(ical=ical_event)
except Exception as e:
_logger.error(f"Failed to sync event to CalDAV server: {e}")
def sync_remove_from_caldav(self):
if not self._is_caldav_enabled():
return
client = self._get_caldav_client()
calendar = client.calendar(url=self.env.user.caldav_calendar_url)
for event in self: for event in self:
if event.caldav_uid: if event.caldav_uid:
caldav_event = calendar.event_by_uid(event.caldav_uid) try:
caldav_event.delete() _logger.debug(f"Removing CalDAV event {event.caldav_uid}")
caldav_event = calendar.event_by_uid(event.caldav_uid)
caldav_event.delete()
except caldav.error.NotFoundError:
_logger.warning(f"CalDAV event {event.caldav_uid} not found on server.")
except Exception as e:
_logger.error(f"Failed to remove event from CalDAV server: {e}")
def _get_icalendar(self): def _get_icalendar(self):
from icalendar import Calendar, Event
calendar = Calendar() calendar = Calendar()
calendar.add('prodid', '-//Odoo//mxm.dk//') calendar.add('prodid', '-//Odoo//mxm.dk//')
calendar.add('version', '2.0') calendar.add('version', '2.0')
for event in self: for event in self:
ical_event = Event() ical_event = Event()
ical_event.add('uid', event.caldav_uid or '') ical_event.add('uid', event.caldav_uid)
ical_event.add('dtstamp', event.write_date) ical_event.add('dtstamp', event.write_date.replace(tzinfo=None))
ical_event.add('dtstart', event.start) ical_event.add('dtstart', event.start.replace(tzinfo=None))
ical_event.add('dtend', event.stop) ical_event.add('dtend', event.stop.replace(tzinfo=None))
ical_event.add('summary', event.name) if event.name:
ical_event.add('description', event.description) ical_event.add('summary', event.name)
ical_event.add('location', event.location) if event.description:
ical_event.add('description', event.description)
if event.location:
ical_event.add('location', event.location)
calendar.add_component(ical_event) calendar.add_component(ical_event)
return calendar.to_ical() return calendar.to_ical()
@ -94,32 +136,56 @@ class CalendarEvent(models.Model):
if not self._is_caldav_enabled(): if not self._is_caldav_enabled():
return return
client = self._get_caldav_client() client = self._get_caldav_client()
calendar = client.calendar(self.env.user.caldav_calendar_url) calendar = client.calendar(url=self.env.user.caldav_calendar_url)
events = calendar.events() events = calendar.events()
caldav_uids = set()
_logger.info(f"Polling CalDAV server for user {self.env.user.name}")
for caldav_event in events: for caldav_event in events:
ical_event = caldav_event.icalendar_instance ical_event = caldav_event.icalendar_instance
self.sync_event_from_ical(ical_event) self.sync_event_from_ical(ical_event)
for component in ical_event.subcomponents:
if isinstance(component, Event):
uid = str(component.get('uid'))
caldav_uids.add(uid)
_logger.info(f"CalDAV UIDs fetched: {caldav_uids}")
# Remove Odoo events that no longer exist on the CalDAV server
odoo_events = self.search([('caldav_uid', '!=', False)])
for event in odoo_events:
if event.caldav_uid not in caldav_uids:
_logger.info(f"Deleting orphan event {event.name} with UID {event.caldav_uid}")
event.with_context(caldav_no_sync=True).unlink()
def sync_event_from_ical(self, ical_event): def sync_event_from_ical(self, ical_event):
from icalendar import Event
for component in ical_event.subcomponents: for component in ical_event.subcomponents:
if isinstance(component, Event): if isinstance(component, Event):
uid = str(component.get('uid')) uid = str(component.get('uid'))
event = self.search([('caldav_uid', '=', uid)], limit=1) event = self.search([('caldav_uid', '=', uid)], limit=1)
start = component.decoded('dtstart')
stop = component.decoded('dtend')
if isinstance(start, datetime):
start = start.replace(tzinfo=None)
if isinstance(stop, datetime):
stop = stop.replace(tzinfo=None)
if not event: if not event:
self.create({ _logger.info(f"Creating new event {str(component.get('summary'))} with UID {uid}")
self.with_context({'caldav_no_sync': True}).create({
'name': str(component.get('summary')), 'name': str(component.get('summary')),
'start': component.decoded('dtstart'), 'start': start,
'stop': component.decoded('dtend'), 'stop': stop,
'description': str(component.get('description')), 'description': str(component.get('description')),
'location': str(component.get('location')), 'location': str(component.get('location')),
'caldav_uid': uid, 'caldav_uid': uid,
}) })
else: else:
event.write({ _logger.info(f"Updating existing event {event.name} with UID {uid}")
event.with_context({'caldav_no_sync': True}).write({
'name': str(component.get('summary')), 'name': str(component.get('summary')),
'start': component.decoded('dtstart'), 'start': start,
'stop': component.decoded('dtend'), 'stop': stop,
'description': str(component.get('description')), 'description': str(component.get('description')),
'location': str(component.get('location')), 'location': str(component.get('location')),
}) })