caldav_sync: no longer create past events in Odoo when synchronizing from CalDAV server

This commit is contained in:
Marc Durepos 2025-05-02 11:51:54 -04:00
parent c3af7223de
commit 2e15f18285
3 changed files with 33 additions and 7 deletions

View file

@ -8,7 +8,7 @@
{
"name": "CalDAV Synchronization",
"version": "18.0.0.6.3",
"version": "18.0.0.7.0",
"license": "LGPL-3",
"category": "Productivity",
"summary": "Synchronize Odoo Calendar Events with CalDAV Servers",

View file

@ -584,11 +584,11 @@ class CalendarEvent(models.Model):
@api.model
def _sync_event_from_ical(
self, ical_event: icalendar.cal.Event, user: User
self, ical_event: icalendar.cal.Event, user: User
) -> CalendarEvent:
"""Given an iCalendar event, compare the event with any existing
Odoo event that it matches and synchronize the changes. If no event
exists, create one.
exists, create one iff the event is in the future.
:param ical_event: The iCalendar event to synchronize with Odoo.
:param user: The res.user record for whom to synchronize the event.
@ -609,12 +609,15 @@ class CalendarEvent(models.Model):
existing_instance = self._get_existing_instance(uid, recurrence_id)
outdated = self._get_outdated(component, existing_instance, synced_events)
owned = (
existing_instance and existing_instance.partner_id == user.partner_id
existing_instance and existing_instance.partner_id == user.partner_id
)
# Pass for_creation=True only when creating a new event
values = self._get_values_from_ical_component(component, user, for_creation=not existing_instance)
recurrency_vals = self._get_recurrency_values_from_ical_event(component)
if not existing_instance:
# If the event is in the past, we just ignore it.
if values.get("stop") < datetime.now(tz=None):
continue
# 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"):

View file

@ -25,7 +25,7 @@ def _get_ics_path(filename):
@contextmanager
def _patch_caldav_with_events_from_ics(ics_paths, user, last_modified=None):
def _patch_caldav_with_events_from_ics(ics_paths, user, last_modified=None, futurize=True):
with (
patch("caldav.DAVClient") as MockDAVClient,
patch("caldav.Calendar") as MockCalendar,
@ -46,8 +46,8 @@ def _patch_caldav_with_events_from_ics(ics_paths, user, last_modified=None):
# Get or create the mock calendar for this user
mock_calendar = calendar_side_effect(user.caldav_calendar_url)
def event_by_uid_side_effect(self, uid):
for event in self.events():
def event_by_uid_side_effect(uid):
for event in mock_calendar.events():
if str(event.icalendar_component.get("uid")) == uid:
return event
return DEFAULT
@ -66,6 +66,18 @@ def _patch_caldav_with_events_from_ics(ics_paths, user, last_modified=None):
if subcomponent.name == "VEVENT":
subcomponent["last-modified"] = icalendar.vDate(last_modified)
subcomponent["dtstamp"] = icalendar.vDate(last_modified)
if futurize:
for event in ical_events:
for subcomponent in event.subcomponents:
if subcomponent.name == "VEVENT":
start = subcomponent.get("dtstart") and subcomponent.decoded("dtstart")
end = subcomponent.get("dtend") and subcomponent.decoded("dtend")
if isinstance(start, datetime) and isinstance(end, datetime):
duration = end - start
else:
duration = timedelta(hours=1)
subcomponent["dtstart"] = icalendar.vDDDTypes(datetime.now())
subcomponent["dtend"] = icalendar.vDDDTypes(datetime.now() + duration)
base_events = [event for event in ical_events if not event.get("recurrence-id")]
for base_event in base_events:
@ -123,6 +135,16 @@ class TestCalendarEvent(TransactionCase, CaldavTestCommon):
new_events = events_after_sync - current_events
self.assertEqual(len(new_events), 1)
def test_basic_past_event_from_server_no_create(self):
user = self.user_1
ics_path = _get_ics_path("basic.ics")
with _patch_caldav_with_events_from_ics(ics_path, user, futurize=False):
current_events = self.env["calendar.event"].search([])
self.env["calendar.event"].poll_caldav_server()
events_after_sync = self.env["calendar.event"].search([])
new_events = events_after_sync - current_events
self.assertEqual(len(new_events), 0)
def test_basic_event_from_server_update(self):
user = self.user_1
ics_path = _get_ics_path("basic.ics")
@ -239,6 +261,7 @@ class TestCalendarEvent(TransactionCase, CaldavTestCommon):
self.assertIn(user2.partner_id, event.partner_ids)
self.assertIn(user3.partner_id, event.partner_ids)
def test_multiple_user_attendees_event_from_server_update(self):
"""Test event has (as in above test):
Organizer: user1 (test1@example.com)