further work on recurring events. cleaned up the code for them quite a bit.
This commit is contained in:
parent
0c93437e2c
commit
b3367a2704
1 changed files with 101 additions and 102 deletions
|
|
@ -52,12 +52,13 @@ class CalendarEvent(models.Model):
|
||||||
|
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
res = super(CalendarEvent, self).write(vals)
|
res = super(CalendarEvent, self).write(vals)
|
||||||
if not self.env.context.get('caldav_no_sync') and self.id:
|
if not self.env.context.get('caldav_no_sync') and self.ids:
|
||||||
try:
|
for rec in self:
|
||||||
_logger.debug(f"Updating event {self.name} in CalDAV")
|
try:
|
||||||
self.sync_update_to_caldav()
|
_logger.debug(f"Updating event {self.name} in CalDAV")
|
||||||
except Exception as e:
|
rec.sync_update_to_caldav()
|
||||||
_logger.error(f"Failed to update event in CalDAV server: {e}")
|
except Exception as e:
|
||||||
|
_logger.error(f"Failed to update event in CalDAV server: {e}")
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def unlink(self):
|
def unlink(self):
|
||||||
|
|
@ -181,34 +182,77 @@ class CalendarEvent(models.Model):
|
||||||
for component in ical_event.subcomponents:
|
for component in ical_event.subcomponents:
|
||||||
if isinstance(component, Event):
|
if isinstance(component, Event):
|
||||||
uid = str(component.get('uid'))
|
uid = str(component.get('uid'))
|
||||||
rrule = component.get('rrule')
|
recurrence_id = str(component.get('recurrence-id'))
|
||||||
if rrule:
|
if recurrence_id == 'None':
|
||||||
dtstart = component.decoded('dtstart')
|
recurrence_id = ''
|
||||||
if dtstart.tzinfo is not None:
|
caldav_uids.add(f"{uid}{recurrence_id}")
|
||||||
dtstart = dtstart.replace(tzinfo=None)
|
|
||||||
rrule_str = rrule.to_ical().decode('utf-8')
|
|
||||||
rrule = rrulestr(rrule_str, dtstart=dtstart)
|
|
||||||
instances = list(rrule.between(now, one_year_later))
|
|
||||||
for instance_start in instances:
|
|
||||||
recurrence_id = instance_start.strftime('%Y%m%dT%H%M%S')
|
|
||||||
instance_uid = f"{uid}-{recurrence_id}"
|
|
||||||
caldav_uids.add(instance_uid)
|
|
||||||
else:
|
|
||||||
caldav_uids.add(uid)
|
|
||||||
|
|
||||||
_logger.info(f"CalDAV UIDs fetched: {caldav_uids}")
|
_logger.info(f"CalDAV UIDs fetched: {caldav_uids}")
|
||||||
|
|
||||||
# Remove Odoo events that no longer exist on the CalDAV server
|
# Remove Odoo events that no longer exist on the CalDAV server
|
||||||
odoo_events = self.search([('caldav_uid', '!=', False)])
|
odoo_events = self.search([('caldav_uid', '!=', False)])
|
||||||
for event in odoo_events:
|
for event in odoo_events:
|
||||||
if event.caldav_recurrence_id:
|
recurrence_id = event.caldav_recurrence_id or ''
|
||||||
if event.caldav_recurrence_id not in caldav_uids:
|
event_uid = f"{event.caldav_uid}{event.caldav_recurrence_id or ''}"
|
||||||
_logger.info(f"Deleting orphan event {event.name} with Recurrence ID {event.caldav_recurrence_id}")
|
if event_uid not in caldav_uids:
|
||||||
event.with_context(caldav_no_sync=True).unlink()
|
_logger.info(f"Deleting orphan event {event.name} with UID {event.caldav_uid} "
|
||||||
elif event.caldav_uid not in caldav_uids:
|
f"and Recurrence ID {event.caldav_recurrence_id or ''}")
|
||||||
_logger.info(f"Deleting orphan event {event.name} with UID {event.caldav_uid}")
|
|
||||||
event.with_context(caldav_no_sync=True).unlink()
|
event.with_context(caldav_no_sync=True).unlink()
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_existing_instance(self, uid, recurrence_id):
|
||||||
|
instance = self.env['calendar.event'].search([
|
||||||
|
('caldav_uid', '=', uid),
|
||||||
|
('recurrence_id', '=', recurrence_id)
|
||||||
|
])
|
||||||
|
return instance or self.env['calendar.event'].search([
|
||||||
|
('caldav_uid', '=', 'uid'),
|
||||||
|
('recurrence_id', '=', False),
|
||||||
|
])
|
||||||
|
|
||||||
|
def _get_recurrency_values_from_ical_event(self, component):
|
||||||
|
""" 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 """
|
||||||
|
|
||||||
|
rrule = component.get('rrule')
|
||||||
|
if not rrule:
|
||||||
|
if not self.recurrency:
|
||||||
|
# No change, this was already not a recurring event
|
||||||
|
return {}
|
||||||
|
else:
|
||||||
|
# This was a recurring event and has been made non-recurring
|
||||||
|
if self.recurrence_id.base_event_id != self:
|
||||||
|
# This is not the base event, so change its recurrency only
|
||||||
|
return {'recurrence_update': 'self_only', 'recurrency': False, 'follow_recurrence': False}
|
||||||
|
else:
|
||||||
|
# This is the base event, so change all events in the list
|
||||||
|
return {'recurrence_update': 'all_events', 'recurrency': False}
|
||||||
|
rrule_str = rrule.to_ical().decode('utf-8')
|
||||||
|
sequence = component.get('sequence')
|
||||||
|
if sequence and sequence != 0:
|
||||||
|
# This is not the base event so we can't change recurrence properties
|
||||||
|
return {}
|
||||||
|
|
||||||
|
caldav_recurrence_id = component.get('recurrence-id')
|
||||||
|
rrule_params = self.env['calendar.recurrence']._rrule_parse(rrule_str, component.decoded('dtstart'))
|
||||||
|
vals = {
|
||||||
|
'recurrency': True,
|
||||||
|
'follow_recurrence': True,
|
||||||
|
'caldav_recurrence_id': caldav_recurrence_id,
|
||||||
|
'recurrence_update': 'all_events',
|
||||||
|
'rrule_type': rrule_params.get('rrule_type'),
|
||||||
|
'end_type': rrule_params.get('end_type'),
|
||||||
|
'interval': rrule_params.get('interval'),
|
||||||
|
'count': rrule_params.get('count'),
|
||||||
|
'month_by': rrule_params.get('monty_by'),
|
||||||
|
'day': rrule_params.get('day'),
|
||||||
|
'byday': rrule_params.get('byday'),
|
||||||
|
'until': rrule_params.get('until'),
|
||||||
|
}
|
||||||
|
if rrule_params.get('weekday'):
|
||||||
|
vals.update(rrule_params.get('weekday'))
|
||||||
|
return vals
|
||||||
|
|
||||||
def sync_event_from_ical(self, ical_event):
|
def sync_event_from_ical(self, ical_event):
|
||||||
email_regex = re.compile(r'[a-z0-9\.\-+_]+@[a-z0-9\.\-+_]+\.[a-z]+')
|
email_regex = re.compile(r'[a-z0-9\.\-+_]+@[a-z0-9\.\-+_]+\.[a-z]+')
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
|
@ -217,8 +261,9 @@ class CalendarEvent(models.Model):
|
||||||
|
|
||||||
for component in ical_event.subcomponents:
|
for component in ical_event.subcomponents:
|
||||||
if isinstance(component, Event):
|
if isinstance(component, Event):
|
||||||
uid = str(component.get('uid'))
|
uid = component.get('uid')
|
||||||
rrule = component.get('rrule')
|
recurrence_id = component.get(
|
||||||
|
'recurrence_id') # Unique identifier for a single event in a recurrence set
|
||||||
attendees = component.get('attendee', [])
|
attendees = component.get('attendee', [])
|
||||||
|
|
||||||
if isinstance(attendees, vCalAddress):
|
if isinstance(attendees, vCalAddress):
|
||||||
|
|
@ -239,83 +284,37 @@ class CalendarEvent(models.Model):
|
||||||
|
|
||||||
attendee_ids = self.env['res.partner'].search([('email', 'in', attendees_emails)])
|
attendee_ids = self.env['res.partner'].search([('email', 'in', attendees_emails)])
|
||||||
|
|
||||||
if rrule:
|
existing_instance = self._get_existing_instance(uid, recurrence_id)
|
||||||
dtstart = component.decoded('dtstart')
|
start = component.decoded('dtstart')
|
||||||
dtend = component.decoded('dtend')
|
if isinstance(start, datetime):
|
||||||
# Convert to naive datetime
|
start = start.replace(tzinfo=None)
|
||||||
if dtstart.tzinfo:
|
end = component.decoded('dtend')
|
||||||
dtstart = dtstart.replace(tzinfo=None)
|
if isinstance(end, datetime):
|
||||||
if dtend.tzinfo:
|
end = end.replace(tzinfo=None)
|
||||||
dtend = dtend.replace(tzinfo=None)
|
values = {
|
||||||
rrule_str = rrule.to_ical().decode('utf-8')
|
'name': str(component.get('summary')),
|
||||||
rrule = rrulestr(rrule_str, dtstart=dtstart)
|
'start': start,
|
||||||
instances = list(rrule.between(now, one_year_later))
|
'stop': end,
|
||||||
|
'description': str(component.get('description')),
|
||||||
for instance_start in instances:
|
'location': str(component.get('location')),
|
||||||
instance_stop = instance_start + (dtend - dtstart)
|
'caldav_uid': uid,
|
||||||
recurrence_id = instance_start.strftime('%Y%m%dT%H%M%S')
|
'partner_ids': [(6, 0, attendee_ids.ids)],
|
||||||
instance_uid = f"{uid}-{recurrence_id}"
|
}
|
||||||
existing_instance = self.search([('caldav_recurrence_id', '=', instance_uid)], limit=1)
|
recurrency_vals = self._get_recurrency_values_from_ical_event(component)
|
||||||
|
if recurrency_vals:
|
||||||
_logger.info(f"Processing instance UID: {instance_uid}")
|
values.update(recurrency_vals)
|
||||||
_logger.info(f"Instance start: {instance_start}, stop: {instance_stop}")
|
if not existing_instance:
|
||||||
|
_logger.info(f"Creating with vals: {values}")
|
||||||
if not existing_instance:
|
self.with_context({'caldav_no_sync': True}).create(values)
|
||||||
_logger.info(f"Creating new recurring event instance with UID {instance_uid}")
|
|
||||||
self.with_context({'caldav_no_sync': True}).create({
|
|
||||||
'name': str(component.get('summary')),
|
|
||||||
'start': instance_start,
|
|
||||||
'stop': instance_stop,
|
|
||||||
'description': str(component.get('description')),
|
|
||||||
'location': str(component.get('location')),
|
|
||||||
'caldav_uid': uid,
|
|
||||||
'caldav_recurrence_id': instance_uid,
|
|
||||||
'partner_ids': [(6, 0, attendee_ids.ids)],
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
_logger.info(f"Updating existing recurring event instance with UID {instance_uid}")
|
|
||||||
existing_instance.with_context({'caldav_no_sync': True}).write({
|
|
||||||
'name': str(component.get('summary')),
|
|
||||||
'start': instance_start,
|
|
||||||
'stop': instance_stop,
|
|
||||||
'description': str(component.get('description')),
|
|
||||||
'location': str(component.get('location')),
|
|
||||||
'partner_ids': [(6, 0, attendee_ids.ids)],
|
|
||||||
})
|
|
||||||
else:
|
else:
|
||||||
start = component.decoded('dtstart')
|
_logger.info(f"Updating with vals: {values}")
|
||||||
stop = component.decoded('dtend')
|
if recurrency_vals and recurrency_vals.get('recurrency'):
|
||||||
if isinstance(start, datetime):
|
existing_instance.write({
|
||||||
start = start.replace(tzinfo=None)
|
'recurrency': True,
|
||||||
if isinstance(stop, datetime):
|
'follow_recurrence': True,
|
||||||
stop = stop.replace(tzinfo=None)
|
|
||||||
event = self.search([('caldav_uid', '=', uid)], limit=1)
|
|
||||||
if not event:
|
|
||||||
_logger.info(f"Creating new event {str(component.get('summary'))} with UID {uid}")
|
|
||||||
self.with_context({'caldav_no_sync': True}).create({
|
|
||||||
'name': str(component.get('summary')),
|
|
||||||
'start': start,
|
|
||||||
'stop': stop,
|
|
||||||
'description': str(component.get('description')),
|
|
||||||
'location': str(component.get('location')),
|
|
||||||
'caldav_uid': uid,
|
|
||||||
'partner_ids': [(6, 0, attendee_ids.ids)],
|
|
||||||
})
|
})
|
||||||
else:
|
existing_instance.with_context({'caldav_no_sync': True}).write(values)
|
||||||
_logger.info(f"Updating existing event {event.name} with UID {uid}")
|
|
||||||
|
|
||||||
existing_partner_ids = set(event.partner_ids.ids)
|
|
||||||
new_partner_ids = set(attendee_ids.ids)
|
|
||||||
combined_partner_ids = list(existing_partner_ids.union(new_partner_ids))
|
|
||||||
|
|
||||||
event.with_context({'caldav_no_sync': True}).write({
|
|
||||||
'name': str(component.get('summary')),
|
|
||||||
'start': start,
|
|
||||||
'stop': stop,
|
|
||||||
'description': str(component.get('description')),
|
|
||||||
'location': str(component.get('location')),
|
|
||||||
'partner_ids': [(6, 0, combined_partner_ids)],
|
|
||||||
})
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _html_to_text(html):
|
def _html_to_text(html):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue