caldav_sync: better sync with calls to _rewrite_recurrence
This commit is contained in:
parent
5ca1d56e54
commit
90d05d75a2
2 changed files with 59 additions and 23 deletions
|
|
@ -102,7 +102,8 @@ class CalendarEvent(models.Model):
|
||||||
time.second,
|
time.second,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
event.caldav_uid = uuid.uuid4()
|
if not self._context.get("caldav_keep_ids"):
|
||||||
|
event.caldav_uid = uuid.uuid4()
|
||||||
event.caldav_recurrence_id = False
|
event.caldav_recurrence_id = False
|
||||||
|
|
||||||
@api.depends("name", "description", "partner_ids", "location", "videocall_location")
|
@api.depends("name", "description", "partner_ids", "location", "videocall_location")
|
||||||
|
|
@ -143,7 +144,7 @@ class CalendarEvent(models.Model):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.depends("recurrency", "recurrence_id.base_event_id")
|
@api.depends("recurrency", "recurrence_id", "recurrence_id.base_event_id")
|
||||||
def _compute_is_base_event(self):
|
def _compute_is_base_event(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
rec.is_base_event = (
|
rec.is_base_event = (
|
||||||
|
|
@ -177,11 +178,9 @@ class CalendarEvent(models.Model):
|
||||||
|
|
||||||
@api.model_create_multi
|
@api.model_create_multi
|
||||||
def create(self, vals_list):
|
def create(self, vals_list):
|
||||||
events = super(
|
|
||||||
CalendarEvent, self.with_context({"caldav_no_sync": True})
|
|
||||||
).create(vals_list)
|
|
||||||
ctx = {"caldav_no_sync": True}
|
ctx = {"caldav_no_sync": True}
|
||||||
events.with_context(ctx)._recompute_caldav_ids()
|
events = super(CalendarEvent, self.with_context(**ctx)).create(vals_list)
|
||||||
|
events.with_context(**ctx)._recompute_caldav_ids()
|
||||||
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
|
||||||
|
|
@ -202,9 +201,7 @@ class CalendarEvent(models.Model):
|
||||||
_logger.error(f"Failed to sync event to CalDAV server: {e}")
|
_logger.error(f"Failed to sync event to CalDAV server: {e}")
|
||||||
|
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
res = super(CalendarEvent, self.with_context({"caldav_no_sync": True})).write(
|
res = super(CalendarEvent, self.with_context(caldav_no_sync=True)).write(vals)
|
||||||
vals
|
|
||||||
)
|
|
||||||
# Events sometimes get archived in Odoo in the process of updating recurrence
|
# Events sometimes get archived in Odoo in the process of updating recurrence
|
||||||
# In that case, we will delete them from the CalDAV server before recreating
|
# In that case, we will delete them from the CalDAV server before recreating
|
||||||
if "active" in vals and not vals.get("active"):
|
if "active" in vals and not vals.get("active"):
|
||||||
|
|
@ -229,7 +226,7 @@ class CalendarEvent(models.Model):
|
||||||
start = utc.localize(self.start).astimezone(tz)
|
start = utc.localize(self.start).astimezone(tz)
|
||||||
# We have an event in the recurrence chain that differs from the base
|
# We have an event in the recurrence chain that differs from the base
|
||||||
# We need to add an iCalendar VEVENT with recurrence-id or update it
|
# We need to add an iCalendar VEVENT with recurrence-id or update it
|
||||||
if self.is_base_event and start == self._get_ical_recurrence_id():
|
if self.is_base_event:
|
||||||
# We have a normal base event, so we just update it or create it
|
# We have a normal base event, so we just update it or create it
|
||||||
self._update_base_caldav_event(
|
self._update_base_caldav_event(
|
||||||
calendar, base_event, ical_event_data
|
calendar, base_event, ical_event_data
|
||||||
|
|
@ -274,6 +271,8 @@ class CalendarEvent(models.Model):
|
||||||
def _update_ical_event_values(
|
def _update_ical_event_values(
|
||||||
self, ical_event: icalendar.cal.Event, event_data: dict
|
self, ical_event: icalendar.cal.Event, event_data: dict
|
||||||
):
|
):
|
||||||
|
sequence = (seq := ical_event.get("sequence") or 0) + 1
|
||||||
|
event_data.update(sequence=sequence)
|
||||||
for key, value in event_data.items():
|
for key, value in event_data.items():
|
||||||
ical_event[key] = value
|
ical_event[key] = value
|
||||||
|
|
||||||
|
|
@ -307,13 +306,14 @@ class CalendarEvent(models.Model):
|
||||||
return index
|
return index
|
||||||
|
|
||||||
def _sync_unlink_to_caldav(self):
|
def _sync_unlink_to_caldav(self):
|
||||||
|
delete_all = self._context.get("caldav_delete_all", False)
|
||||||
if self.caldav_uid:
|
if self.caldav_uid:
|
||||||
for user in self.caldav_user_ids:
|
for user in self.caldav_user_ids:
|
||||||
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:
|
||||||
caldav_event = calendar.event_by_uid(self.caldav_uid)
|
caldav_event = calendar.event_by_uid(self.caldav_uid)
|
||||||
if self.recurrence_id and not self.is_base_event:
|
if not delete_all and self.recurrence_id and not self.is_base_event:
|
||||||
index = self._get_subcomponent_index_for_recurrence(
|
index = self._get_subcomponent_index_for_recurrence(
|
||||||
caldav_event
|
caldav_event
|
||||||
)
|
)
|
||||||
|
|
@ -325,7 +325,7 @@ class CalendarEvent(models.Model):
|
||||||
# they can be base events but have an old caldav_uid from when
|
# they can be base events but have an old caldav_uid from when
|
||||||
# they were recurring. That's why we make sure that the start
|
# they were recurring. That's why we make sure that the start
|
||||||
# of the event matches.
|
# of the event matches.
|
||||||
if self._matches_caldav_start(caldav_event):
|
if delete_all or self._matches_caldav_start(caldav_event):
|
||||||
caldav_event.delete()
|
caldav_event.delete()
|
||||||
except caldav.error.NotFoundError:
|
except caldav.error.NotFoundError:
|
||||||
# No worries - it just didn't exist so nothing to sync
|
# No worries - it just didn't exist so nothing to sync
|
||||||
|
|
@ -350,10 +350,16 @@ class CalendarEvent(models.Model):
|
||||||
We do, however, need to get a new caldav_uid and caldav_recurrence_id for all
|
We do, however, need to get a new caldav_uid and caldav_recurrence_id for all
|
||||||
the events in the new chain.
|
the events in the new chain.
|
||||||
"""
|
"""
|
||||||
|
ctx = {"keep_caldav_ids": False}
|
||||||
old_base = self.recurrence_id.base_event_id
|
old_base = self.recurrence_id.base_event_id
|
||||||
super()._update_future_events(values, time_values, recurrence_values)
|
super().with_context(**ctx)._update_future_events(
|
||||||
|
values, time_values, recurrence_values
|
||||||
|
)
|
||||||
events_to_refresh = self.recurrence_id._get_events_from(self.start)
|
events_to_refresh = self.recurrence_id._get_events_from(self.start)
|
||||||
events_to_refresh._recompute_caldav_ids()
|
# Future events are archived but not this one. We need to remove the old version
|
||||||
|
# from the calendar if it existed.
|
||||||
|
self._sync_unlink_to_caldav()
|
||||||
|
events_to_refresh.with_context(**ctx)._recompute_caldav_ids()
|
||||||
self._sync_write_to_caldav()
|
self._sync_write_to_caldav()
|
||||||
old_base._sync_write_to_caldav()
|
old_base._sync_write_to_caldav()
|
||||||
|
|
||||||
|
|
@ -369,6 +375,24 @@ class CalendarEvent(models.Model):
|
||||||
recurrence.base_event_id._sync_write_to_caldav()
|
recurrence.base_event_id._sync_write_to_caldav()
|
||||||
return detached_events - self
|
return detached_events - self
|
||||||
|
|
||||||
|
def _rewrite_recurrence(self, values, time_values, recurrence_values):
|
||||||
|
"""
|
||||||
|
When _rewrite_recurrence is called, it archives all the events in the
|
||||||
|
recurrence but only after messing with the recurrence_id. This breaks our check
|
||||||
|
on is_base_event, so we preemptively sync the deletion here.
|
||||||
|
|
||||||
|
Because this method is usually called with caldav_no_sync=True in the context
|
||||||
|
to avoid looping, we manually call _sync_write_to_caldav once the operation is
|
||||||
|
completed.
|
||||||
|
"""
|
||||||
|
for event in self.recurrence_id.calendar_event_ids:
|
||||||
|
event.with_context(caldav_delete_all=True)._sync_unlink_to_caldav()
|
||||||
|
res = super()._rewrite_recurrence(values, time_values, recurrence_values)
|
||||||
|
for event in self.recurrence_id.calendar_event_ids:
|
||||||
|
event._recompute_caldav_ids()
|
||||||
|
self._sync_write_to_caldav()
|
||||||
|
return res
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -406,7 +430,13 @@ class CalendarEvent(models.Model):
|
||||||
return event_data
|
return event_data
|
||||||
|
|
||||||
def _add_event_header_info(self, event_data: Dict) -> None:
|
def _add_event_header_info(self, event_data: Dict) -> None:
|
||||||
"""Add event header info to a dict containing event data."""
|
"""Add event header info to a dict containing event data.
|
||||||
|
|
||||||
|
IMPORTANT: the VEVENT sequence is set to 0. It should be changed if this method
|
||||||
|
is used to update an existing event!
|
||||||
|
|
||||||
|
:param event_data: The dictionary of event data to be updated with header info.
|
||||||
|
"""
|
||||||
event_data["uid"] = vText(self.caldav_uid)
|
event_data["uid"] = vText(self.caldav_uid)
|
||||||
if self.name:
|
if self.name:
|
||||||
event_data["summary"] = vText(self.name)
|
event_data["summary"] = vText(self.name)
|
||||||
|
|
@ -417,6 +447,7 @@ class CalendarEvent(models.Model):
|
||||||
event_data["location"] = vText(self.location)
|
event_data["location"] = vText(self.location)
|
||||||
if self.videocall_location:
|
if self.videocall_location:
|
||||||
event_data["conference"] = self.videocall_location
|
event_data["conference"] = self.videocall_location
|
||||||
|
event_data["sequence"] = 0
|
||||||
|
|
||||||
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."""
|
||||||
|
|
@ -564,9 +595,9 @@ class CalendarEvent(models.Model):
|
||||||
if not recurrency_vals.get("follow_recurrence"):
|
if not recurrency_vals.get("follow_recurrence"):
|
||||||
recurrency_vals = {}
|
recurrency_vals = {}
|
||||||
|
|
||||||
new_event = self.with_context(caldav_no_sync=True).create(
|
new_event = self.with_context(
|
||||||
values | recurrency_vals
|
caldav_no_sync=True, caldav_keep_ids=True
|
||||||
)
|
).create(values | recurrency_vals)
|
||||||
self.env["calendar.event"].flush_model()
|
self.env["calendar.event"].flush_model()
|
||||||
if new_event.recurrency:
|
if new_event.recurrency:
|
||||||
synced_events |= new_event.recurrence_id.calendar_event_ids
|
synced_events |= new_event.recurrence_id.calendar_event_ids
|
||||||
|
|
@ -582,6 +613,7 @@ class CalendarEvent(models.Model):
|
||||||
if changed_vals:
|
if changed_vals:
|
||||||
existing_instance.with_context(
|
existing_instance.with_context(
|
||||||
caldav_no_sync=True,
|
caldav_no_sync=True,
|
||||||
|
caldav_keep_ids=True,
|
||||||
).write(changed_vals)
|
).write(changed_vals)
|
||||||
|
|
||||||
if existing_instance.recurrency and existing_instance.is_base_event:
|
if existing_instance.recurrency and existing_instance.is_base_event:
|
||||||
|
|
@ -773,14 +805,13 @@ class CalendarEvent(models.Model):
|
||||||
:return: True if the iCalendar component is outdated, False otherwise.
|
:return: True if the iCalendar component is outdated, False otherwise.
|
||||||
"""
|
"""
|
||||||
outdated = False
|
outdated = False
|
||||||
last_modified = component.get("last-modified")
|
last_modified = component.get("dtstamp") and component.get("dtstamp").dt
|
||||||
if (
|
if (
|
||||||
existing_instance
|
existing_instance
|
||||||
and last_modified
|
and last_modified
|
||||||
and existing_instance not in synced_events
|
and existing_instance not in synced_events
|
||||||
):
|
):
|
||||||
last_modified = last_modified.dt.astimezone(utc).replace(tzinfo=None)
|
if last_modified < utc.localize(existing_instance.write_date):
|
||||||
if last_modified < existing_instance.write_date:
|
|
||||||
outdated = True
|
outdated = True
|
||||||
return outdated
|
return outdated
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,13 @@ class RecurrenceRule(models.Model):
|
||||||
|
|
||||||
@api.model_create_multi
|
@api.model_create_multi
|
||||||
def create(self, vals_list):
|
def create(self, vals_list):
|
||||||
for vals in vals_list:
|
if not self._context.get("caldav_keep_ids"):
|
||||||
vals["caldav_uid"] = str(uuid.uuid4())
|
for vals in vals_list:
|
||||||
|
vals["caldav_uid"] = str(uuid.uuid4())
|
||||||
|
else:
|
||||||
|
for vals in vals_list:
|
||||||
|
base_event = self.env["calendar.event"].browse(vals["base_event_id"])
|
||||||
|
vals.update(caldav_uid=base_event.caldav_uid)
|
||||||
return super().create(vals_list)
|
return super().create(vals_list)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue