From 992778e353289b5f8df176f8691c1ed40acc9a9b Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Fri, 8 Nov 2024 12:41:02 -0500 Subject: [PATCH] caldav_sync: all seems good. further testing to come before merging into 17.0 --- caldav_sync/models/calendar_event.py | 25 +++++++++++++++-- caldav_sync/tests/test_calendar.py | 41 ++++++++++++++++++---------- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/caldav_sync/models/calendar_event.py b/caldav_sync/models/calendar_event.py index 94159d3..e470f56 100644 --- a/caldav_sync/models/calendar_event.py +++ b/caldav_sync/models/calendar_event.py @@ -539,7 +539,8 @@ class CalendarEvent(models.Model): :param user: The res.user record for whom to synchronize events. """ _logger.info(f"Polling CalDAV server for user {user.name}") - events = user._get_caldav_events() + calendar = user._get_caldav_client().calendar(url=user.caldav_calendar_url) + events = calendar.events() synced_events = self.env["calendar.event"] for caldav_event in events: ical_event = caldav_event.icalendar_instance @@ -556,7 +557,27 @@ class CalendarEvent(models.Model): ) orphaned_events = orphaned_events._to_sync() if orphaned_events: - orphaned_events.with_context(caldav_no_sync=True).with_user(user).unlink() + base_orphans = orphaned_events.filtered( + lambda ev: ev.recurrence_id and ev.is_base_event + ) + for base_orphan in base_orphans: + try: + if calendar.event_by_uid(base_orphan.caldav_uid): + # There are some events remaining in this recurrence series, + # so we have synchronized them individually. + pass + except caldav.error.NotFoundError: + # There are no more events with this UID, so we need to clear + # out the whole recurrence chain from the Odoo side. + ctx = {"caldav_no_sync": True} + recurrence = base_orphan.recurrence_id + recurrence.calendar_event_ids.with_context(**ctx).with_user( + user + ).unlink() + recurrence.with_context(**ctx).with_user(user).unlink() + (orphaned_events - base_orphans).with_context( + caldav_no_sync=True + ).with_user(user).unlink() @api.model def _sync_event_from_ical( diff --git a/caldav_sync/tests/test_calendar.py b/caldav_sync/tests/test_calendar.py index 3bc2706..1cc57c0 100644 --- a/caldav_sync/tests/test_calendar.py +++ b/caldav_sync/tests/test_calendar.py @@ -1,12 +1,13 @@ from collections.abc import Iterable from odoo.tests import TransactionCase from odoo import Command -from unittest.mock import patch, MagicMock, PropertyMock +from unittest.mock import patch, MagicMock, DEFAULT import icalendar from pathlib import Path from .common import CaldavTestCommon from contextlib import contextmanager from datetime import datetime, UTC, timedelta +import caldav WEEKDAY_MAP = { 0: "SUN", @@ -28,7 +29,6 @@ def _patch_caldav_with_events_from_ics(ics_paths, user, last_modified=None): with ( patch("caldav.DAVClient") as MockDAVClient, patch("caldav.Calendar") as MockCalendar, - patch("caldav.Event") as MockEvent, ): mock_client = MockDAVClient.return_value mock_calendar = MockCalendar.return_value @@ -43,6 +43,12 @@ def _patch_caldav_with_events_from_ics(ics_paths, user, last_modified=None): raise Exception("Calendar does not exist.") mock_calendar.side_effect = calendar_side_effect + + def event_by_uid_side_effect(self, uid): + for event in self.events: + if str(event.icalendar_component.get("uid")) == uid: + return event + ical_events = [] if ics_paths: if not isinstance(ics_paths, Iterable): @@ -51,18 +57,25 @@ def _patch_caldav_with_events_from_ics(ics_paths, user, last_modified=None): with ics_path.open("rb") as file: ical_content = file.read() ical_events.append(icalendar.Calendar.from_ical(ical_content)) - mock_caldav_events = [] - for ical_event in ical_events: - mock_event = MockEvent() - mock_event.icalendar_instance = ical_event - if last_modified: - for component in ical_event.walk(): - if component.name == "VEVENT": - component["last-modified"] = last_modified.strftime( - "%Y%m%dT%H%M%SZ" - ) - mock_caldav_events.append(mock_event) - mock_calendar.events.return_value = mock_caldav_events + if last_modified: + for event in ical_events: + event["last-modified"] = last_modified + event["dtstamp"] = last_modified + + base_events = [event for event in ical_events if not event.get("recurrence-id")] + for base_event in base_events: + child_events = [ + event + for event in ical_events + if event.get("recurrence-id") + and event.get("uid") == base_event.get("uid") + ] + for child_event in child_events: + base_event.add_component(child_event) + mock_calendar.add_event(base_event) + caldav_events = [caldav.Event(data=event) for event in base_events] + mock_calendar.events.return_value = caldav_events + mock_calendar.event_by_uid.side_effect = event_by_uid_side_effect user._compute_is_caldav_enabled() yield