caldav_sync: updating recurrences in the middle with future works, but it breaks singled out events before it (brings them back into the rrule chain)
This commit is contained in:
parent
421d609abd
commit
ce7b27cc65
2 changed files with 63 additions and 21 deletions
|
|
@ -64,16 +64,17 @@ def _extract_vcal_email(vcal_address):
|
||||||
class CalendarEvent(models.Model):
|
class CalendarEvent(models.Model):
|
||||||
_inherit = "calendar.event"
|
_inherit = "calendar.event"
|
||||||
|
|
||||||
caldav_uid = fields.Char(
|
# CalDAV UID is either unique per event or the same for all events in a recurring
|
||||||
string="CalDAV UID", compute="_compute_caldav_uid", store=True
|
# sequence. This field and caldav_recurrence_id are not calculated fields since
|
||||||
)
|
# the recalculation timing is hard to control and we want to make sure we sync
|
||||||
|
# some modifications before the UID changes with recurrence_id and other changes on
|
||||||
|
# the record.
|
||||||
|
caldav_uid = fields.Char(string="CalDAV UID", store=True)
|
||||||
# Recurrence ID in iCalendar is the date or datetime the event would have
|
# Recurrence ID in iCalendar is the date or datetime the event would have
|
||||||
# been at if it followed the sequence. It is set by calendar.recurrence
|
# been at if it followed the sequence. It is set by calendar.recurrence
|
||||||
# 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",
|
||||||
|
|
@ -86,17 +87,21 @@ class CalendarEvent(models.Model):
|
||||||
#### Field Computation Methods ####
|
#### Field Computation Methods ####
|
||||||
###################################
|
###################################
|
||||||
|
|
||||||
@api.depends("recurrence_id")
|
def _recompute_caldav_uid(self):
|
||||||
def _compute_caldav_uid(self):
|
|
||||||
for event in self:
|
for event in self:
|
||||||
|
_logger.info(f"Recomputing CalDAV UID from {event.caldav_uid}")
|
||||||
if event.recurrence_id:
|
if event.recurrence_id:
|
||||||
event.caldav_uid = event.recurrence_id.caldav_uid
|
event.caldav_uid = event.recurrence_id.caldav_uid
|
||||||
else:
|
else:
|
||||||
event.caldav_uid = uuid.uuid4()
|
event.caldav_uid = uuid.uuid4()
|
||||||
|
_logger.info(f"New CalDAV UID: {event.caldav_uid}")
|
||||||
|
|
||||||
@api.depends("recurrence_id.dtstart")
|
@api.depends("recurrence_id", "recurrence_id.dtstart")
|
||||||
def _compute_caldav_recurrence_id(self):
|
def _recompute_caldav_recurrence_id(self):
|
||||||
for event in self:
|
for event in self:
|
||||||
|
_logger.info(
|
||||||
|
f"Recomputing caldav_recurrence_id for event {event} at {event.start}"
|
||||||
|
)
|
||||||
if event.recurrence_id:
|
if event.recurrence_id:
|
||||||
time = event.recurrence_id.dtstart.time()
|
time = event.recurrence_id.dtstart.time()
|
||||||
date = event.start.date()
|
date = event.start.date()
|
||||||
|
|
@ -186,6 +191,9 @@ class CalendarEvent(models.Model):
|
||||||
events = super(
|
events = super(
|
||||||
CalendarEvent, self.with_context({"caldav_no_sync": True})
|
CalendarEvent, self.with_context({"caldav_no_sync": True})
|
||||||
).create(vals_list)
|
).create(vals_list)
|
||||||
|
ctx = {"caldav_no_sync": True}
|
||||||
|
events.with_context(ctx)._recompute_caldav_uid()
|
||||||
|
events.with_context(ctx)._recompute_caldav_recurrence_id()
|
||||||
if not self.env.context.get("caldav_no_sync"):
|
if not self.env.context.get("caldav_no_sync"):
|
||||||
events._to_sync()._sync_create_to_caldav()
|
events._to_sync()._sync_create_to_caldav()
|
||||||
return events
|
return events
|
||||||
|
|
@ -279,17 +287,29 @@ 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.info(f"Removing CalDAV event {self.caldav_uid}")
|
_logger.info(
|
||||||
ical_event = self._find_in_caldav_calendar(calendar)
|
f"Removing CalDAV event {self.caldav_uid}, Odoo ID {self.id} starting at {self.start}"
|
||||||
if ical_event:
|
)
|
||||||
ical_event.delete()
|
caldav_event = self._find_in_caldav_calendar(
|
||||||
|
calendar, match_start=True
|
||||||
|
)
|
||||||
|
if caldav_event:
|
||||||
|
_logger.info(
|
||||||
|
f"Found event {caldav_event.icalendar_component.get("uid")}{caldav_event.icalendar_component.get("dtstart").dt}"
|
||||||
|
)
|
||||||
|
caldav_event.delete()
|
||||||
except caldav.error.NotFoundError:
|
except caldav.error.NotFoundError:
|
||||||
_logger.info(
|
_logger.info(
|
||||||
f"CalDAV event {self.caldav_uid} not found on server during deletion."
|
f"CalDAV event {self.caldav_uid} starting at {self.start}"
|
||||||
|
f" not found on server during deletion."
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.error(f"Failed to remove event from CalDAV server: {e}")
|
_logger.error(f"Failed to remove event from CalDAV server: {e}")
|
||||||
|
|
||||||
|
def _sync_recurrence_to_caldav(self):
|
||||||
|
for event in self._to_sync():
|
||||||
|
event._sync_write_to_caldav()
|
||||||
|
|
||||||
def _to_sync(self):
|
def _to_sync(self):
|
||||||
"""Determine which records in self we need to synchronize with the
|
"""Determine which records in self we need to synchronize with the
|
||||||
CalDAV server. In essence, we only synchronize base events and those
|
CalDAV server. In essence, we only synchronize base events and those
|
||||||
|
|
@ -340,7 +360,8 @@ 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."""
|
||||||
event_tz = timezone(self.event_tz)
|
tz = self.event_tz or self.env.user.tz
|
||||||
|
event_tz = timezone(tz)
|
||||||
event_data["last-modified"] = vDatetime(
|
event_data["last-modified"] = vDatetime(
|
||||||
utc.localize(self.write_date).astimezone(event_tz)
|
utc.localize(self.write_date).astimezone(event_tz)
|
||||||
)
|
)
|
||||||
|
|
@ -408,7 +429,10 @@ class CalendarEvent(models.Model):
|
||||||
return mapping.get(state, "NEEDS-ACTION")
|
return mapping.get(state, "NEEDS-ACTION")
|
||||||
|
|
||||||
def _find_in_caldav_calendar(
|
def _find_in_caldav_calendar(
|
||||||
self, calendar: caldav.Calendar, force_recurrence_id: bool = False
|
self,
|
||||||
|
calendar: caldav.Calendar,
|
||||||
|
force_recurrence_id: bool = False,
|
||||||
|
match_start: bool = False,
|
||||||
) -> Optional[caldav.Event]:
|
) -> Optional[caldav.Event]:
|
||||||
"""Find this event in a given CalDAV Calendar. If the event is
|
"""Find this event in a given CalDAV Calendar. If the event is
|
||||||
recurring but is not a base event, it will be matched with its
|
recurring but is not a base event, it will be matched with its
|
||||||
|
|
@ -425,16 +449,26 @@ class CalendarEvent(models.Model):
|
||||||
match_recurrence_id = (
|
match_recurrence_id = (
|
||||||
force_recurrence_id or self.recurrence_id and not self.is_base_event
|
force_recurrence_id or self.recurrence_id and not self.is_base_event
|
||||||
)
|
)
|
||||||
if not match_recurrence_id:
|
if not match_recurrence_id and not match_start:
|
||||||
return calendar.event_by_uid(self.caldav_uid)
|
calendar.event_by_uid(self.caldav_uid)
|
||||||
else:
|
else:
|
||||||
events = calendar.events()
|
events = calendar.events()
|
||||||
for event in events:
|
for event in events:
|
||||||
event_instance = event.icalendar_instance
|
event_instance = event.icalendar_component
|
||||||
if (
|
if (
|
||||||
event_instance.get("uid") == self.caldav_uid
|
event_instance.get("uid") == self.caldav_uid
|
||||||
and event_instance.get("recurrence-id").dt
|
and (
|
||||||
== self._get_ical_recurrence_id()
|
not match_recurrence_id
|
||||||
|
or (rec_id := event_instance.get("recurrence-id"))
|
||||||
|
and rec_id == self._get_ical_recurrence_id()
|
||||||
|
)
|
||||||
|
and (
|
||||||
|
not match_start
|
||||||
|
or event_instance.get("dtstart").dt
|
||||||
|
== utc.localize(self.start).astimezone(
|
||||||
|
event_instance.get("dtstart").dt.tzinfo
|
||||||
|
)
|
||||||
|
)
|
||||||
):
|
):
|
||||||
return event
|
return event
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -16,3 +16,11 @@ class RecurrenceRule(models.Model):
|
||||||
default=_default_uid,
|
default=_default_uid,
|
||||||
readonly=True,
|
readonly=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _stop_at(self, event):
|
||||||
|
detached_events = super()._stop_at(event)
|
||||||
|
detached_events._recompute_caldav_uid()
|
||||||
|
detached_events._recompute_caldav_recurrence_id()
|
||||||
|
self.calendar_event_ids._sync_recurrence_to_caldav()
|
||||||
|
# detached_events._sync_recurrence_to_caldav()
|
||||||
|
return detached_events
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue