caldav_sync: moving a single event in a recurrence is synchronizing well

This commit is contained in:
Marc Durepos 2024-11-06 16:06:43 -05:00
parent 14a8b6acf0
commit 0e07d42825
2 changed files with 52 additions and 44 deletions

View file

@ -7,8 +7,7 @@ from odoo.addons.calendar.models.calendar_recurrence import MAX_RECURRENT_EVENT
import caldav import caldav
import logging import logging
from datetime import datetime, date from datetime import datetime, date
from icalendar import vCalAddress, vText, vDatetime, vRecur, Event from icalendar import vCalAddress, vText, vDatetime, vRecur, Event, vInt
from bs4 import BeautifulSoup
import re import re
from pytz import timezone, utc from pytz import timezone, utc
from typing import List, Dict, TypeVar, Optional from typing import List, Dict, TypeVar, Optional
@ -71,6 +70,8 @@ class CalendarEvent(models.Model):
# when applying a recurrence. # when applying a recurrence.
caldav_recurrence_id = fields.Datetime( caldav_recurrence_id = fields.Datetime(
string="CalDAV Recurrence ID", string="CalDAV Recurrence ID",
compute="_compute_caldav_recurrence_id",
store=True,
) )
caldav_user_ids = fields.Many2many( caldav_user_ids = fields.Many2many(
comodel_name="res.users", comodel_name="res.users",
@ -83,6 +84,23 @@ class CalendarEvent(models.Model):
#### Field Computation Methods #### #### Field Computation Methods ####
################################### ###################################
@api.depends("recurrence_id.dtstart")
def _compute_caldav_recurrence_id(self):
for event in self:
if event.recurrence_id:
time = event.recurrence_id.dtstart.time()
date = event.start.date()
event.caldav_recurrence_id = datetime(
date.year,
date.month,
date.day,
time.hour,
time.minute,
time.second,
)
else:
event.caldav_recurrence_id = False
@api.depends("name", "description", "partner_ids", "location", "videocall_location") @api.depends("name", "description", "partner_ids", "location", "videocall_location")
def _compute_differs_from_base_event(self): def _compute_differs_from_base_event(self):
base_events = self.filtered("is_base_event") base_events = self.filtered("is_base_event")
@ -145,8 +163,8 @@ class CalendarEvent(models.Model):
:return: The timezone-aware datetime that uniquely identifies this :return: The timezone-aware datetime that uniquely identifies this
event in the recurrence chain. event in the recurrence chain.
""" """
user_tz = timezone(self.user_id.tz) event_tz = timezone(self.event_tz)
recurrence_id = utc.localize(self.caldav_recurrence_id).astimezone(user_tz) recurrence_id = utc.localize(self.caldav_recurrence_id).astimezone(event_tz)
return recurrence_id return recurrence_id
################################################################# #################################################################
@ -171,11 +189,11 @@ class CalendarEvent(models.Model):
client = user._get_caldav_client() client = user._get_caldav_client()
calendar = client.calendar(url=user.caldav_calendar_url) calendar = client.calendar(url=user.caldav_calendar_url)
# try: # try:
_logger.debug(f"Creating new CalDAV event for {event.name}") _logger.info(f"Creating new CalDAV event for {event.name}")
caldav_events = event._create_in_icalendar(calendar) caldav_events = event._create_in_icalendar(calendar)
for caldav_event in caldav_events: for caldav_event in caldav_events:
caldav_uid = caldav_event.vobject_instance.vevent.uid.value caldav_uid = caldav_event.vobject_instance.vevent.uid.value
_logger.debug(f"New CalDAV UID: {caldav_uid}") _logger.info(f"New CalDAV UID: {caldav_uid}")
event.with_context(caldav_no_sync=True).write( event.with_context(caldav_no_sync=True).write(
{"caldav_uid": caldav_uid} {"caldav_uid": caldav_uid}
) )
@ -195,9 +213,7 @@ class CalendarEvent(models.Model):
to_sync = self._to_sync() to_sync = self._to_sync()
if to_sync and not self.env.context.get("caldav_no_sync"): if to_sync and not self.env.context.get("caldav_no_sync"):
for rec in to_sync: for rec in to_sync:
_logger.debug( _logger.info(f"Updating event {rec.name} at {rec.start} in " f"CalDAV.")
f"Updating event {rec.name} in CalDAV. " f"{rec.fields_get()}"
)
rec._sync_write_to_caldav() rec._sync_write_to_caldav()
return res return res
@ -211,19 +227,32 @@ class CalendarEvent(models.Model):
try: try:
event = self._find_in_caldav_calendar(calendar) event = self._find_in_caldav_calendar(calendar)
except caldav.error.NotFoundError: except caldav.error.NotFoundError:
_logger.debug( _logger.info(
f"CalDAV event {self.caldav_uid} not found on server during update." f"CalDAV event {self.caldav_uid} not found on server during update."
f" Creating it instead." f" Creating it instead."
) )
if event: if event:
event.icalendar_component.update(ical_event_data)
event.save() event.save()
elif self.recurrence_id and not self.follow_recurrence: elif self.recurrence_id and not self.is_base_event:
base_event = calendar.event_by_uid(self.caldav_uid) base_event = self._get_caldav_base_event_by_uid(
calendar, self.caldav_uid
)
base_event.icalendar_instance.add_component(Event(**ical_event_data)) base_event.icalendar_instance.add_component(Event(**ical_event_data))
if self.caldav_recurrence_id != self.start:
tz = timezone(self.event_tz)
base_event.save() base_event.save()
else: else:
calendar.save_event(**ical_event_data) calendar.add_event(**ical_event_data)
def _get_caldav_base_event_by_uid(
self, calendar: caldav.Calendar, uid: str
) -> Optional[CalendarEvent]:
for event in calendar.events():
comp = event.icalendar_component
event_uid = self._extract_component_text(comp, "uid")
event_recurrence_id = self._extract_component_text(comp, "recurrence-id")
if event_uid == uid and not event_recurrence_id:
return event
def unlink(self): def unlink(self):
if not self.env.context.get("caldav_no_sync"): if not self.env.context.get("caldav_no_sync"):
@ -231,7 +260,7 @@ class CalendarEvent(models.Model):
try: try:
# TODO: fix the attempt to sync all events ... _to_sync is # TODO: fix the attempt to sync all events ... _to_sync is
# not working here # not working here
_logger.debug(f"Removing event {rec.name} from CalDAV") _logger.info(f"Removing event {rec.name} from CalDAV")
rec._sync_unlink_to_caldav() rec._sync_unlink_to_caldav()
except Exception as e: except Exception as e:
_logger.error(f"Failed to delete event from CalDAV server: {e}") _logger.error(f"Failed to delete event from CalDAV server: {e}")
@ -243,7 +272,7 @@ class CalendarEvent(models.Model):
client = user._get_caldav_client() client = user._get_caldav_client()
calendar = client.calendar(url=user.caldav_calendar_url) calendar = client.calendar(url=user.caldav_calendar_url)
try: try:
_logger.debug(f"Removing CalDAV event {self.caldav_uid}") _logger.info(f"Removing CalDAV event {self.caldav_uid}")
ical_event = self._find_in_caldav_calendar(calendar) ical_event = self._find_in_caldav_calendar(calendar)
if ical_event: if ical_event:
ical_event.delete() ical_event.delete()
@ -304,17 +333,15 @@ class CalendarEvent(models.Model):
def _add_event_dates(self, event_data: Dict) -> None: def _add_event_dates(self, event_data: Dict) -> None:
"""Add pertinent dates to event data, based on self.""" """Add pertinent dates to event data, based on self."""
user_tz = timezone("UTC") event_tz = timezone(self.event_tz)
if self.user_id.tz:
user_tz = timezone(self.user_id.tz)
event_data["last-modified"] = vDatetime( event_data["last-modified"] = vDatetime(
utc.localize(self.write_date).astimezone(user_tz) utc.localize(self.write_date).astimezone(event_tz)
) )
event_data["created"] = vDatetime( event_data["created"] = vDatetime(
utc.localize(self.create_date).astimezone(user_tz) utc.localize(self.create_date).astimezone(event_tz)
) )
event_data["dtstart"] = vDatetime(utc.localize(self.start).astimezone(user_tz)) event_data["dtstart"] = vDatetime(utc.localize(self.start).astimezone(event_tz))
event_data["dtend"] = vDatetime(utc.localize(self.stop).astimezone(user_tz)) event_data["dtend"] = vDatetime(utc.localize(self.stop).astimezone(event_tz))
return event_data return event_data
def _add_event_recurrence_id(self, event_data: Dict) -> None: def _add_event_recurrence_id(self, event_data: Dict) -> None:
@ -378,7 +405,7 @@ class CalendarEvent(models.Model):
CalDAV server. The caldav_uid that unites events of a series needs to be CalDAV server. The caldav_uid that unites events of a series needs to be
separated, making a new uid for each base event (and its recurrences).""" separated, making a new uid for each base event (and its recurrences)."""
# Start by deleting the old calendar events since we will need to remake them # Start by deleting the old calendar events since we will need to remake them
_logger.debug( _logger.info(
f"Events {self} with CalDAV UIDs " f"Events {self} with CalDAV UIDs "
f"{self.mapped('caldav_uid')} are being " f"{self.mapped('caldav_uid')} are being "
f"detached." f"detached."
@ -407,7 +434,7 @@ class CalendarEvent(models.Model):
recurrence_events.write({"caldav_uid": new_uid}) recurrence_events.write({"caldav_uid": new_uid})
def _break_recurrence(self, future=True): def _break_recurrence(self, future=True):
_logger.debug( _logger.info(
f"Recurrence breaking for events {self} with CalDAV " f"Recurrence breaking for events {self} with CalDAV "
f"UIDs {self.mapped('caldav_uid')}." f"UIDs {self.mapped('caldav_uid')}."
) )

View file

@ -5,25 +5,6 @@ from pytz import timezone, utc
class RecurrenceRule(models.Model): class RecurrenceRule(models.Model):
_inherit = "calendar.recurrence" _inherit = "calendar.recurrence"
def _recompute_event_caldav_recurrence_ids(self):
def _get_recurrence_id(event):
return event.start.astimezone(utc).replace(tzinfo=None)
for event in self.calendar_event_ids:
event.caldav_recurrence_id = _get_recurrence_id(event)
def _apply_recurrence(
self,
specific_values_creation=None,
no_send_edit=False,
generic_values_creation=None,
):
detached_events = super()._apply_recurrence(
specific_values_creation, no_send_edit, generic_values_creation
)
self._recompute_event_caldav_recurrence_ids()
return detached_events
def _detach_events(self, events): def _detach_events(self, events):
events = super()._detach_events(events) events = super()._detach_events(events)
events.with_context(dont_notify=True)._post_recurrence_detach() events.with_context(dont_notify=True)._post_recurrence_detach()