caldav_sync: all CRUD operations working. Recurrences working to some degree but not fully.
This commit is contained in:
parent
0104e3897c
commit
068d1fb29f
3 changed files with 147 additions and 73 deletions
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "CalDAV Synchronization",
|
"name": "CalDAV Synchronization",
|
||||||
"version": "17.0.0.5.9",
|
"version": "17.0.0.6.0",
|
||||||
"license": "LGPL-3",
|
"license": "LGPL-3",
|
||||||
"category": "Productivity",
|
"category": "Productivity",
|
||||||
"summary": "Synchronize Odoo Calendar Events with CalDAV Servers",
|
"summary": "Synchronize Odoo Calendar Events with CalDAV Servers",
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import uuid
|
import uuid
|
||||||
from wsgiref.util import request_uri
|
|
||||||
|
|
||||||
from odoo import models, api, fields, Command
|
from odoo import models, api, fields
|
||||||
from odoo.addons.calendar.models.calendar_recurrence import MAX_RECURRENT_EVENT
|
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 Calendar, Event, vCalAddress, vText
|
from icalendar import vCalAddress, vText, vDatetime, vRecur
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import re
|
import re
|
||||||
from pytz import timezone, utc
|
from pytz import timezone, utc
|
||||||
|
|
@ -105,7 +104,7 @@ class CalendarEvent(models.Model):
|
||||||
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 = (
|
||||||
not rec.recurrency or rec.recurrence_id.base_event_id == rec
|
not rec.recurrence_id or rec.recurrence_id.base_event_id == rec
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.depends("user_id", "partner_ids", "partner_ids.user_id")
|
@api.depends("user_id", "partner_ids", "partner_ids.user_id")
|
||||||
|
|
@ -126,15 +125,25 @@ class CalendarEvent(models.Model):
|
||||||
for vals in vals_list:
|
for vals in vals_list:
|
||||||
if not vals.get("caldav_uid"):
|
if not vals.get("caldav_uid"):
|
||||||
vals["caldav_uid"] = str(uuid.uuid4())
|
vals["caldav_uid"] = str(uuid.uuid4())
|
||||||
events = super(CalendarEvent, self).create(vals_list)
|
events = super(
|
||||||
|
CalendarEvent, self.with_context({"caldav_no_sync": True})
|
||||||
|
).create(vals_list)
|
||||||
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
|
||||||
|
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
res = super().write(vals)
|
res = super(CalendarEvent, self.with_context({"caldav_no_sync": True})).write(
|
||||||
|
vals
|
||||||
|
)
|
||||||
|
# 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
|
||||||
|
if "active" in vals and not vals.get("active"):
|
||||||
|
for event in self:
|
||||||
|
event._sync_remove_from_caldav()
|
||||||
|
return res
|
||||||
to_sync = self._to_sync()
|
to_sync = self._to_sync()
|
||||||
if to_sync and 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.debug(
|
||||||
f"Updating event {rec.name} in CalDAV. " f"{rec.fields_get()}"
|
f"Updating event {rec.name} in CalDAV. " f"{rec.fields_get()}"
|
||||||
|
|
@ -153,33 +162,43 @@ class CalendarEvent(models.Model):
|
||||||
return super(CalendarEvent, self).unlink()
|
return super(CalendarEvent, self).unlink()
|
||||||
|
|
||||||
def _is_caldav_enabled(self):
|
def _is_caldav_enabled(self):
|
||||||
return self.env.user.is_caldav_enabled
|
return self.user_id.is_caldav_enabled
|
||||||
|
|
||||||
def _sync_create_to_caldav(self):
|
def _sync_create_to_caldav(self):
|
||||||
for event in self:
|
for event in self:
|
||||||
for user in event.caldav_user_ids:
|
for user in event.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:
|
||||||
_logger.debug(f"Creating new CalDAV event for {event.name}")
|
_logger.debug(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.debug(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}
|
||||||
)
|
)
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
_logger.error(f"Failed to sync event to CalDAV server: {e}")
|
# _logger.error(f"Failed to sync event to CalDAV server: {e}")
|
||||||
|
|
||||||
def _sync_update_to_caldav(self):
|
def _sync_update_to_caldav(self):
|
||||||
ical_event_data = self._create_event_data()
|
ical_event_data = self._create_event_data()
|
||||||
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=self.env.user.caldav_calendar_url)
|
calendar = client.calendar(url=user.caldav_calendar_url)
|
||||||
event = self._find_in_icalendar(calendar, user)
|
|
||||||
|
event = None
|
||||||
|
try:
|
||||||
|
event = self._find_in_icalendar(calendar, user)
|
||||||
|
except caldav.error.NotFoundError:
|
||||||
|
_logger.info(
|
||||||
|
f"CalDAV event {self.caldav_uid} not found on server during update."
|
||||||
|
f" Creating it instead."
|
||||||
|
)
|
||||||
|
|
||||||
if event:
|
if event:
|
||||||
event.save(**ical_event_data)
|
event.icalendar_component.update(ical_event_data)
|
||||||
|
event.save()
|
||||||
else:
|
else:
|
||||||
calendar.save_event(**ical_event_data)
|
calendar.save_event(**ical_event_data)
|
||||||
|
|
||||||
|
|
@ -187,32 +206,34 @@ class CalendarEvent(models.Model):
|
||||||
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=self.env.user.caldav_calendar_url)
|
calendar = client.calendar(url=user.caldav_calendar_url)
|
||||||
try:
|
try:
|
||||||
_logger.debug(f"Removing CalDAV event {self.caldav_uid}")
|
_logger.debug(f"Removing CalDAV event {self.caldav_uid}")
|
||||||
ical_event = self._find_in_icalendar(calendar, user)
|
ical_event = self._find_in_icalendar(calendar, user)
|
||||||
if ical_event:
|
if ical_event:
|
||||||
ical_event.delete()
|
ical_event.delete()
|
||||||
except caldav.error.NotFoundError:
|
except caldav.error.NotFoundError:
|
||||||
_logger.warning(
|
_logger.info(
|
||||||
f"CalDAV event {self.caldav_uid} not found on server during deletion."
|
f"CalDAV event {self.caldav_uid} 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 _find_in_icalendar(self, calendar, user):
|
def _find_in_icalendar(self, calendar, user, force_recurrence_id=False):
|
||||||
# Since search by uid and recurrence-id is not yet supported,
|
# Since search by uid and recurrence-id is not yet supported,
|
||||||
# we do it manually
|
# we do it manually
|
||||||
match_recurrence_id = self.recurrency and not self.is_base_event
|
match_recurrence_id = (
|
||||||
|
force_recurrence_id or self.recurrence_id and not self.is_base_event
|
||||||
|
)
|
||||||
if not match_recurrence_id:
|
if not match_recurrence_id:
|
||||||
return calendar.event_by_uid(self.caldav_uid)
|
return calendar.event_by_uid(self.caldav_uid)
|
||||||
else:
|
else:
|
||||||
events = user._get_caldav_events()
|
events = user._get_caldav_events()
|
||||||
for event in events:
|
for event in events:
|
||||||
event = event.icalendar_instance
|
event_instance = event.icalendar_instance
|
||||||
if (
|
if (
|
||||||
event.get("uid") == self.caldav_uid
|
event_instance.get("uid") == self.caldav_uid
|
||||||
and event.get("recurrence-id") == self.caldav_recurrence_id
|
and event_instance.get("recurrence-id") == self.caldav_recurrence_id
|
||||||
):
|
):
|
||||||
return event
|
return event
|
||||||
return None
|
return None
|
||||||
|
|
@ -220,7 +241,7 @@ class CalendarEvent(models.Model):
|
||||||
def _create_in_icalendar(self, calendar):
|
def _create_in_icalendar(self, calendar):
|
||||||
ical_event_data = self._create_event_data()
|
ical_event_data = self._create_event_data()
|
||||||
caldav_event = calendar.save_event(**ical_event_data)
|
caldav_event = calendar.save_event(**ical_event_data)
|
||||||
if self.recurrency and self.is_base_event and not self.follow_recurrence:
|
if self.recurrence_id and self.is_base_event and not self.follow_recurrence:
|
||||||
ical_event_data = self._create_event_data()
|
ical_event_data = self._create_event_data()
|
||||||
second_caldav_event = calendar.save_event(**ical_event_data)
|
second_caldav_event = calendar.save_event(**ical_event_data)
|
||||||
return [caldav_event, second_caldav_event]
|
return [caldav_event, second_caldav_event]
|
||||||
|
|
@ -231,44 +252,48 @@ class CalendarEvent(models.Model):
|
||||||
self._add_event_dates(event_data)
|
self._add_event_dates(event_data)
|
||||||
self._add_event_header_info(event_data)
|
self._add_event_header_info(event_data)
|
||||||
self._add_event_attendees(event_data)
|
self._add_event_attendees(event_data)
|
||||||
if self.is_base_event and self.recurrency:
|
if self.is_base_event and self.recurrence_id:
|
||||||
self._add_event_recurrence(event_data)
|
self._add_event_recurrence(event_data)
|
||||||
elif self.recurrency:
|
elif self.recurrence_id:
|
||||||
self._add_event_recurrence_id(event_data)
|
self._add_event_recurrence_id(event_data)
|
||||||
return event_data
|
return event_data
|
||||||
|
|
||||||
def _add_event_header_info(self, event_data):
|
def _add_event_header_info(self, event_data):
|
||||||
event_data["uid"] = self.caldav_uid
|
event_data["uid"] = vText(self.caldav_uid)
|
||||||
if self.name:
|
if self.name:
|
||||||
event_data["summary"] = self.name
|
event_data["summary"] = vText(self.name)
|
||||||
# TODO: Consider using X-ALT-DESC to stick HTML into the iCal event desc.
|
# TODO: Consider using X-ALT-DESC to stick HTML into the iCal event desc.
|
||||||
if self.description and self._html_to_text(self.description):
|
if self.description and self._html_to_text(self.description):
|
||||||
event_data["description"] = self._html_to_text(self.description)
|
event_data["description"] = vText(self._html_to_text(self.description))
|
||||||
if self.location:
|
if self.location:
|
||||||
event_data["location"] = 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
|
||||||
|
|
||||||
def _add_event_dates(self, event_data):
|
def _add_event_dates(self, event_data):
|
||||||
user_tz = timezone("UTC")
|
user_tz = timezone("UTC")
|
||||||
if self.user_id.tz:
|
if self.user_id.tz:
|
||||||
user_tz = timezone(self.user_id.tz)
|
user_tz = timezone(self.user_id.tz)
|
||||||
event_data["last-modified"] = utc.localize(self.write_date).astimezone(user_tz)
|
event_data["last-modified"] = vDatetime(
|
||||||
event_data["created"] = utc.localize(self.create_date).astimezone(user_tz)
|
utc.localize(self.write_date).astimezone(user_tz)
|
||||||
event_data["dtstart"] = utc.localize(self.start).astimezone(user_tz)
|
)
|
||||||
event_data["dtend"] = utc.localize(self.stop).astimezone(user_tz)
|
event_data["created"] = vDatetime(
|
||||||
|
utc.localize(self.create_date).astimezone(user_tz)
|
||||||
|
)
|
||||||
|
event_data["dtstart"] = vDatetime(utc.localize(self.start).astimezone(user_tz))
|
||||||
|
event_data["dtend"] = vDatetime(utc.localize(self.stop).astimezone(user_tz))
|
||||||
return event_data
|
return event_data
|
||||||
|
|
||||||
def _add_event_recurrence_id(self, event_data):
|
def _add_event_recurrence_id(self, event_data):
|
||||||
if self.recurrency:
|
if self.recurrence_id:
|
||||||
event_data["recurrence-id"] = self._get_ical_recurrence_id()
|
event_data["recurrence-id"] = self._get_ical_recurrence_id()
|
||||||
|
|
||||||
def _add_event_recurrence(self, event_data):
|
def _add_event_recurrence(self, event_data):
|
||||||
# Add RRULE if the event is recurrent
|
# Add RRULE if the event is recurrent
|
||||||
if self.recurrency:
|
if self.recurrence_id:
|
||||||
rrule = self.recurrence_id._get_rrule()
|
rrule = str(self.recurrence_id._get_rrule())
|
||||||
rrule_dict = _parse_rrule_string(str(rrule))
|
rrule_dict = _parse_rrule_string(rrule)
|
||||||
event_data["rrule"] = rrule_dict
|
event_data["rrule"] = vRecur(**rrule_dict)
|
||||||
|
|
||||||
def _add_event_attendees(self, event_data):
|
def _add_event_attendees(self, event_data):
|
||||||
attendee_lines = []
|
attendee_lines = []
|
||||||
|
|
@ -293,9 +318,11 @@ class CalendarEvent(models.Model):
|
||||||
|
|
||||||
def _get_ical_recurrence_id(self):
|
def _get_ical_recurrence_id(self):
|
||||||
user_tz = timezone(self.user_id.tz)
|
user_tz = timezone(self.user_id.tz)
|
||||||
recurrence_id = utc.localize(
|
recurrence_id = (
|
||||||
datetime.strptime(self.caldav_recurrence_id, "%Y%m%dT%H%M%S")
|
utc.localize(datetime.strptime(self.caldav_recurrence_id, "%Y%m%dT%H%M%S"))
|
||||||
).astimezone(user_tz)
|
.astimezone(user_tz)
|
||||||
|
.strftime("%Y%m%dT%H%M%S")
|
||||||
|
)
|
||||||
return recurrence_id
|
return recurrence_id
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
|
|
@ -321,7 +348,8 @@ class CalendarEvent(models.Model):
|
||||||
("id", "not " "in", synced_events.ids),
|
("id", "not " "in", synced_events.ids),
|
||||||
("user_id", "=", user.id),
|
("user_id", "=", user.id),
|
||||||
]
|
]
|
||||||
)._to_sync()
|
)
|
||||||
|
orphaned_events = orphaned_events._to_sync()
|
||||||
if orphaned_events:
|
if orphaned_events:
|
||||||
_logger.info(f"Deleting orphaned events {orphaned_events.ids}")
|
_logger.info(f"Deleting orphaned events {orphaned_events.ids}")
|
||||||
orphaned_events.with_context(caldav_no_sync=True).with_user(user).unlink()
|
orphaned_events.with_context(caldav_no_sync=True).with_user(user).unlink()
|
||||||
|
|
@ -368,6 +396,7 @@ class CalendarEvent(models.Model):
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@api.model
|
||||||
def _get_recurrency_values_from_ical_event(self, component):
|
def _get_recurrency_values_from_ical_event(self, component):
|
||||||
"""Match the fields from calendar.event (recurring fields) to the fields specified in RRULE at
|
"""Match the fields from calendar.event (recurring fields) to the fields specified in RRULE at
|
||||||
https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html"""
|
https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html"""
|
||||||
|
|
@ -379,15 +408,9 @@ class CalendarEvent(models.Model):
|
||||||
follow_recurrence = (
|
follow_recurrence = (
|
||||||
component.get("recurrence-id").dt == component.get("dtstart").dt
|
component.get("recurrence-id").dt == component.get("dtstart").dt
|
||||||
)
|
)
|
||||||
if self and self.follow_recurrence == follow_recurrence:
|
if not self.follow_recurrence:
|
||||||
return {
|
return {"recurrency": False}
|
||||||
"follow_recurrence": follow_recurrence,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
return {
|
|
||||||
"follow_recurrence": follow_recurrence,
|
|
||||||
"recurrence_update": "self-only",
|
|
||||||
}
|
|
||||||
rrule = [item[1] for item in component.property_items() if item[0] == "RRULE"]
|
rrule = [item[1] for item in component.property_items() if item[0] == "RRULE"]
|
||||||
rrule = rrule[0] if rrule else None
|
rrule = rrule[0] if rrule else None
|
||||||
|
|
||||||
|
|
@ -396,7 +419,7 @@ class CalendarEvent(models.Model):
|
||||||
|
|
||||||
rrule_str = rrule.to_ical() and rrule.to_ical().decode("utf-8")
|
rrule_str = rrule.to_ical() and rrule.to_ical().decode("utf-8")
|
||||||
rrule_params = self.env["calendar.recurrence"]._rrule_parse(
|
rrule_params = self.env["calendar.recurrence"]._rrule_parse(
|
||||||
rrule_str, component.decoded("dtstart")
|
"RRULE:" + rrule_str, component.decoded("dtstart")
|
||||||
)
|
)
|
||||||
vals = {
|
vals = {
|
||||||
"recurrency": True,
|
"recurrency": True,
|
||||||
|
|
@ -418,11 +441,11 @@ class CalendarEvent(models.Model):
|
||||||
return vals
|
return vals
|
||||||
|
|
||||||
def _get_recurrence_changes(self, recurrency_vals):
|
def _get_recurrence_changes(self, recurrency_vals):
|
||||||
if not recurrency_vals and not self.recurrency:
|
if not recurrency_vals and not self.recurrence_id:
|
||||||
return {}
|
return {}
|
||||||
if not recurrency_vals and self.recurrency:
|
if not recurrency_vals and self.recurrence_id:
|
||||||
return {"recurrence_update": "all_events", "recurrency": False}
|
return {"recurrence_update": "all_events", "recurrency": False}
|
||||||
if recurrency_vals and not self.recurrency:
|
if recurrency_vals and not self.recurrence_id:
|
||||||
return recurrency_vals
|
return recurrency_vals
|
||||||
changed_fields = {
|
changed_fields = {
|
||||||
key: recurrency_vals[key]
|
key: recurrency_vals[key]
|
||||||
|
|
@ -483,10 +506,13 @@ class CalendarEvent(models.Model):
|
||||||
existing_instance and existing_instance.partner_id == user.partner_id
|
existing_instance and existing_instance.partner_id == user.partner_id
|
||||||
)
|
)
|
||||||
values = self._get_values_from_ical_component(component, user)
|
values = self._get_values_from_ical_component(component, user)
|
||||||
recurrency_vals = existing_instance._get_recurrency_values_from_ical_event(
|
recurrency_vals = self._get_recurrency_values_from_ical_event(component)
|
||||||
component
|
|
||||||
)
|
|
||||||
if not existing_instance:
|
if not existing_instance:
|
||||||
|
# If we're creating an instance and it doesn't follow the recurrence,
|
||||||
|
# just scrap the recurrency vals, they're not useful
|
||||||
|
if not recurrency_vals.get("follow_recurrence"):
|
||||||
|
recurrency_vals = {}
|
||||||
|
|
||||||
_logger.info(f"Creating with vals: {values | recurrency_vals}")
|
_logger.info(f"Creating with vals: {values | recurrency_vals}")
|
||||||
new_event = self.with_context(caldav_no_sync=True).create(
|
new_event = self.with_context(caldav_no_sync=True).create(
|
||||||
values | recurrency_vals
|
values | recurrency_vals
|
||||||
|
|
@ -504,7 +530,6 @@ class CalendarEvent(models.Model):
|
||||||
f"{'not owned by user' + user.name if not owned else ''}."
|
f"{'not owned by user' + user.name if not owned else ''}."
|
||||||
f" Skipping."
|
f" Skipping."
|
||||||
)
|
)
|
||||||
# Do nothing, it's not this user's event to modify or it's outdated
|
|
||||||
else:
|
else:
|
||||||
changed_vals = existing_instance._get_recurrence_changes(
|
changed_vals = existing_instance._get_recurrence_changes(
|
||||||
recurrency_vals
|
recurrency_vals
|
||||||
|
|
@ -516,6 +541,7 @@ class CalendarEvent(models.Model):
|
||||||
existing_instance.with_context(
|
existing_instance.with_context(
|
||||||
caldav_no_sync=True,
|
caldav_no_sync=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:
|
||||||
synced_events |= existing_instance.recurrence_id.calendar_event_ids
|
synced_events |= existing_instance.recurrence_id.calendar_event_ids
|
||||||
else:
|
else:
|
||||||
|
|
@ -635,3 +661,36 @@ class CalendarEvent(models.Model):
|
||||||
"tentative": "TENTATIVE",
|
"tentative": "TENTATIVE",
|
||||||
}
|
}
|
||||||
return mapping.get(state, "NEEDS-ACTION")
|
return mapping.get(state, "NEEDS-ACTION")
|
||||||
|
|
||||||
|
def _post_recurrence_detach(self):
|
||||||
|
"""After we detach events from a recurrence, we need to split them up on the
|
||||||
|
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)."""
|
||||||
|
caldav_events = []
|
||||||
|
# Start by deleting the old calendar events since we will need to remake them
|
||||||
|
for event in self:
|
||||||
|
for user in event.caldav_user_ids:
|
||||||
|
calendar = user._get_caldav_client().calendar(
|
||||||
|
url=user.caldav_calendar_url
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
caldav_event = event._find_in_icalendar(
|
||||||
|
calendar, user, force_recurrence_id=True
|
||||||
|
)
|
||||||
|
if caldav_event:
|
||||||
|
caldav_event.delete()
|
||||||
|
except caldav.error.NotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
base_events = self.filtered("is_base_event")
|
||||||
|
# Assign a new UID for each base event and its recurrences
|
||||||
|
for event in base_events:
|
||||||
|
recurrence_events = (
|
||||||
|
event.recurrence_id and event.recurrence_id.calendar_event_ids | event
|
||||||
|
)
|
||||||
|
new_uid = str(uuid.uuid4())
|
||||||
|
recurrence_events.write({"caldav_uid": new_uid})
|
||||||
|
|
||||||
|
def _break_recurrence(self, future=True):
|
||||||
|
super()._break_recurrence(future)
|
||||||
|
self._post_recurrence_detach()
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,29 @@ from pytz import timezone
|
||||||
class RecurrenceRule(models.Model):
|
class RecurrenceRule(models.Model):
|
||||||
_inherit = "calendar.recurrence"
|
_inherit = "calendar.recurrence"
|
||||||
|
|
||||||
def _apply_recurrence(self, **kwargs):
|
def _recompute_event_caldav_recurrence_ids(self):
|
||||||
detached_events = super()._apply_recurrence(**kwargs)
|
|
||||||
|
|
||||||
def _get_recurrence_id(event):
|
def _get_recurrence_id(event):
|
||||||
format = "%Y%m%d" if event.allday else "%Y%m%dT%H%M%S"
|
date_format = "%Y%m%d" if event.allday else "%Y%m%dT%H%M%S"
|
||||||
return event.start.astimezone(timezone(event.user_id.tz)).strftime(format)
|
return event.start.astimezone(timezone(event.user_id.tz)).strftime(
|
||||||
|
date_format
|
||||||
|
)
|
||||||
|
|
||||||
for event in self.calendar_event_ids:
|
for event in self.calendar_event_ids:
|
||||||
event.caldav_recurrence_id = _get_recurrence_id(event)
|
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
|
return detached_events
|
||||||
|
|
||||||
|
def _detach_events(self, events):
|
||||||
|
events = super()._detach_events(events)
|
||||||
|
events.with_context(dont_notify=True)._post_recurrence_detach()
|
||||||
|
return events
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue