caldav_sync: further fixes for organizer issues

This commit is contained in:
Marc Durepos 2025-04-08 16:11:45 -04:00
parent 3ebadcb969
commit 4b19f8262e
2 changed files with 44 additions and 14 deletions

View file

@ -611,7 +611,8 @@ class CalendarEvent(models.Model):
owned = ( owned = (
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) # 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) recurrency_vals = self._get_recurrency_values_from_ical_event(component)
if not existing_instance: if not existing_instance:
# If we're creating an instance and it doesn't follow the recurrence, # If we're creating an instance and it doesn't follow the recurrence,
@ -647,7 +648,7 @@ class CalendarEvent(models.Model):
return synced_events return synced_events
@api.model @api.model
def _get_existing_instance(self, uid, recurrence_id: datetime) -> CalendarEvent: def _get_existing_instance(self, uid, recurrence_id: Optional[datetime]) -> CalendarEvent:
"""Find the Odoo calendar.event record matching uid and, """Find the Odoo calendar.event record matching uid and,
if set, recurrence_id. if set, recurrence_id.
""" """
@ -686,7 +687,7 @@ class CalendarEvent(models.Model):
return instance or self.env["calendar.event"].search( return instance or self.env["calendar.event"].search(
[ [
("caldav_uid", "=", "uid"), ("caldav_uid", "=", uid),
("recurrence_id", "=", False), ("recurrence_id", "=", False),
] ]
) )
@ -841,14 +842,15 @@ class CalendarEvent(models.Model):
@api.model @api.model
def _get_values_from_ical_component( def _get_values_from_ical_component(
self, component: icalendar.cal.Component, user: User self, component: icalendar.cal.Component, user: User, for_creation: bool = False
) -> Dict: ) -> Dict:
"""Get the dictionary representing calendar.event field values from """Get the dictionary representing calendar.event field values from
an iCalendar event. an iCalendar event.
:param component: The iCalendar component from which to extract :param component: The iCalendar component from which to extract values.
values.
:param user: The res.users record the event will belong to. :param user: The res.users record the event will belong to.
:param for_creation: Whether these values are for creating a new event (True)
or updating an existing one (False).
:return: The dictionary of values to construct a calendar.event.""" :return: The dictionary of values to construct a calendar.event."""
start = component.get("dtstart") and component.decoded("dtstart") start = component.get("dtstart") and component.decoded("dtstart")
if isinstance(start, datetime): if isinstance(start, datetime):
@ -856,11 +858,11 @@ class CalendarEvent(models.Model):
end = component.get("dtend") and component.decoded("dtend") end = component.get("dtend") and component.decoded("dtend")
if isinstance(end, datetime): if isinstance(end, datetime):
end = end.astimezone(utc).replace(tzinfo=None) end = end.astimezone(utc).replace(tzinfo=None)
organizer_partner = self._get_organizer_partner(component)
# Get the Odoo user ID associated with the organizer partner # Get attendees regardless of creation/update
# If no Odoo user is associated, set to False (external)
organizer = organizer_partner.user_ids[0].id if organizer_partner.user_ids else False
attendee_ids = self._get_attendee_partners(component, user.partner_id.email) attendee_ids = self._get_attendee_partners(component, user.partner_id.email)
# Basic values that apply to both creation and updates
values = { values = {
"name": str(component.get("summary")), "name": str(component.get("summary")),
"start": start, "start": start,
@ -872,10 +874,25 @@ class CalendarEvent(models.Model):
"videocall_location": self._extract_component_text(component, "conference"), "videocall_location": self._extract_component_text(component, "conference"),
"caldav_uid": str(component.get("uid")), "caldav_uid": str(component.get("uid")),
"partner_ids": [(6, 0, attendee_ids.ids)], "partner_ids": [(6, 0, attendee_ids.ids)],
"partner_id": organizer_partner.id if organizer_partner else user.partner_id.id,
"user_id": organizer,
} }
# Only set user_id and partner_id during creation
if for_creation:
organizer_partner = self._get_organizer_partner(component)
if organizer_partner:
# Get the Odoo user ID associated with the organizer partner
organizer = organizer_partner.user_ids[0].id if organizer_partner.user_ids else False
values.update({
"partner_id": organizer_partner.id,
"user_id": organizer,
})
else:
# For new events without an organizer, use the current user
values.update({
"partner_id": user.partner_id.id,
"user_id": user.id,
})
return values return values
@api.model @api.model

View file

@ -1,5 +1,5 @@
from collections.abc import Iterable from collections.abc import Iterable
from odoo.tests import TransactionCase from odoo.tests import TransactionCase, tagged
from odoo import Command from odoo import Command
from unittest.mock import patch, MagicMock, DEFAULT from unittest.mock import patch, MagicMock, DEFAULT
import icalendar import icalendar
@ -85,6 +85,7 @@ def _patch_caldav_with_events_from_ics(ics_paths, user, last_modified=None):
yield yield
@tagged('post_install', '-at_install')
class TestCalendarEvent(TransactionCase, CaldavTestCommon): class TestCalendarEvent(TransactionCase, CaldavTestCommon):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
@ -127,17 +128,29 @@ class TestCalendarEvent(TransactionCase, CaldavTestCommon):
ics_path = _get_ics_path("basic.ics") ics_path = _get_ics_path("basic.ics")
with _patch_caldav_with_events_from_ics(ics_path, user): with _patch_caldav_with_events_from_ics(ics_path, user):
self.env["calendar.event"].poll_caldav_server() self.env["calendar.event"].poll_caldav_server()
# Verify the event was created correctly
event = self.env["calendar.event"].search([("user_id", "=", user.id)]) event = self.env["calendar.event"].search([("user_id", "=", user.id)])
self.assertEqual(event.name, "Test")
orig_start = event.start orig_start = event.start
orig_stop = event.stop orig_stop = event.stop
# Now update the event with the updated ICS data
ics_path = _get_ics_path("basic_updated.ics") ics_path = _get_ics_path("basic_updated.ics")
with _patch_caldav_with_events_from_ics( with _patch_caldav_with_events_from_ics(
ics_path, ics_path,
user, user,
last_modified=(datetime.now(UTC)), last_modified=(datetime.now(UTC)),
): ):
# Clear any caches to ensure fresh data
self.env["calendar.event"].invalidate_model()
self.env["calendar.event"].poll_caldav_server() self.env["calendar.event"].poll_caldav_server()
# Refresh the event from the database to get updated values
event.invalidate_recordset()
event = self.env["calendar.event"].search([("user_id", "=", user.id)]) event = self.env["calendar.event"].search([("user_id", "=", user.id)])
# Verify the event was updated correctly
self.assertEqual(event.name, "Test Updated") self.assertEqual(event.name, "Test Updated")
# This next one is just lazy avoiding the HTML stripping # This next one is just lazy avoiding the HTML stripping
self.assertIn("Some note ...", event.description) self.assertIn("Some note ...", event.description)