updates to synchronize attendees and conference URL
This commit is contained in:
parent
0c78f7314f
commit
d229f0c25e
3 changed files with 90 additions and 14 deletions
|
|
@ -1,11 +1,10 @@
|
|||
|
||||
CalDAV Synchronization
|
||||
======================
|
||||
|
||||
Bemade Inc.
|
||||
|
||||
Copyright (C) 2023-June Bemade Inc. (<https://www.bemade.org>).
|
||||
Author: Marc Durepos (Contact : mdurepos@durpro.com)
|
||||
Author: Marc Durepos (Contact : marc@bemade.org)
|
||||
|
||||
This program is under the terms of the GNU Lesser General Public License (LGPL-3)
|
||||
For details, visit https://www.gnu.org/licenses/lgpl-3.0.en.html
|
||||
|
|
@ -13,13 +12,16 @@ For details, visit https://www.gnu.org/licenses/lgpl-3.0.en.html
|
|||
Overview
|
||||
--------
|
||||
|
||||
The CalDAV Synchronization module for Odoo allows users to synchronize their calendar events with CalDAV servers. This enables seamless integration of Odoo calendar with external applications like Apple Calendar or Thunderbird.
|
||||
The CalDAV Synchronization module for Odoo allows users to synchronize their
|
||||
calendar events with CalDAV servers. This enables seamless integration of Odoo
|
||||
calendar with external applications like Apple Calendar or Thunderbird.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Synchronize Odoo calendar events with CalDAV servers.
|
||||
- Create, update, and delete events in Odoo and reflect changes on the CalDAV server.
|
||||
- Create, update, and delete events in Odoo and reflect changes on the CalDAV
|
||||
server.
|
||||
- Poll CalDAV server for changes and update Odoo calendar accordingly.
|
||||
|
||||
Configuration
|
||||
|
|
@ -27,22 +29,27 @@ Configuration
|
|||
|
||||
1. Install the module in Odoo.
|
||||
2. Go to the User settings in Odoo.
|
||||
3. Enter the CalDAV calendar URL, username, and password.
|
||||
3. Enter the CalDAV calendar URL, username, and password on the user settings.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
1. Create a calendar event in Odoo and it will be synchronized with the CalDAV calendar.
|
||||
1. Create a calendar event in Odoo and it will be synchronized with the CalDAV
|
||||
calendar.
|
||||
2. Update the event in Odoo and the changes will reflect on the CalDAV server.
|
||||
3. Delete the event in Odoo and it will be removed from the CalDAV server.
|
||||
4. Changes made to the calendar on the CalDAV server will be polled and updated in Odoo.
|
||||
4. Changes made to the calendar on the CalDAV server (other email apps) will be
|
||||
polled and updated in Odoo.
|
||||
|
||||
Technical Details
|
||||
-----------------
|
||||
|
||||
- The module extends the `calendar.event` model to add CalDAV synchronization functionality.
|
||||
- It uses the `icalendar` library to format events and the `caldav` library to interact with CalDAV servers.
|
||||
- Polling for changes on the CalDAV server can be triggered manually or set up as a scheduled job in Odoo.
|
||||
- The module extends the `calendar.event` model to add CalDAV synchronization
|
||||
functionality.
|
||||
- It uses the `icalendar` library to format events and the `caldav` library to
|
||||
interact with CalDAV servers.
|
||||
- Polling for changes on the CalDAV server can be triggered manually by
|
||||
triggering the scheduled action in Odoo.
|
||||
|
||||
License
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
{
|
||||
'name': 'CalDAV Synchronization',
|
||||
'version': '17.0.0.3.0',
|
||||
'version': '17.0.0.4.0',
|
||||
'license': 'LGPL-3',
|
||||
'category': 'Productivity',
|
||||
'summary': 'Synchronize Odoo Calendar Events with CalDAV Servers',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ from odoo import models, api, fields
|
|||
import caldav
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from icalendar import Calendar, Event
|
||||
from icalendar import Calendar, Event, vCalAddress, Alarm, vText
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -116,11 +118,25 @@ class CalendarEvent(models.Model):
|
|||
if event.name:
|
||||
ical_event.add('summary', event.name)
|
||||
if event.description:
|
||||
ical_event.add('description', event.description)
|
||||
ical_event.add('description', self._html_to_text(event.description))
|
||||
if event.location:
|
||||
ical_event.add('location', event.location)
|
||||
if event.videocall_location:
|
||||
ical_event.add('CONFERENCE', event.videocall_location)
|
||||
for partner in event.partner_ids:
|
||||
attendee = vCalAddress(f'MAILTO:{partner.email}')
|
||||
attendee.params['cn'] = vText(partner.name)
|
||||
attendee_record = self.env['calendar.attendee'].search([('event_id', '=', event.id), ('partner_id', '=', partner.id)], limit=1)
|
||||
if attendee_record:
|
||||
attendee.params['partstat'] = vText(self._map_attendee_status(attendee_record.state))
|
||||
ical_event.add('attendee', attendee, encode=0)
|
||||
for alarm in event.alarm_ids:
|
||||
ical_alarm = Alarm()
|
||||
ical_alarm.add('trigger', alarm.trigger)
|
||||
ical_alarm.add('action', 'DISPLAY')
|
||||
ical_alarm.add('description', alarm.name or 'Reminder')
|
||||
ical_event.add_component(ical_alarm)
|
||||
calendar.add_component(ical_event)
|
||||
|
||||
return calendar.to_ical()
|
||||
|
||||
@api.model
|
||||
|
|
@ -160,6 +176,8 @@ class CalendarEvent(models.Model):
|
|||
event.with_context(caldav_no_sync=True).unlink()
|
||||
|
||||
def sync_event_from_ical(self, ical_event):
|
||||
email_regex = re.compile(r'[A-Za-z0-9\.\-+_]+@[A-Za-z0-9\.\-+_]+\.[A-Za-z]+')
|
||||
|
||||
for component in ical_event.subcomponents:
|
||||
if isinstance(component, Event):
|
||||
uid = str(component.get('uid'))
|
||||
|
|
@ -170,6 +188,24 @@ class CalendarEvent(models.Model):
|
|||
start = start.replace(tzinfo=None)
|
||||
if isinstance(stop, datetime):
|
||||
stop = stop.replace(tzinfo=None)
|
||||
|
||||
attendees = component.get('attendee', [])
|
||||
if isinstance(attendees, vCalAddress):
|
||||
attendees = [attendees]
|
||||
elif isinstance(attendees, str):
|
||||
attendees = [vCalAddress(attendees)]
|
||||
|
||||
# Extract email addresses using regex
|
||||
attendees_emails = [
|
||||
email_regex.search(str(attendee)).group(0).lower().strip()
|
||||
for attendee in attendees if email_regex.search(str(attendee))
|
||||
]
|
||||
|
||||
_logger.info(f"Attendees emails: {attendees_emails}")
|
||||
|
||||
# Search for Odoo partners with the extracted email addresses
|
||||
attendee_ids = self.env['res.partner'].search([('email', 'in', attendees_emails)])
|
||||
|
||||
if not event:
|
||||
_logger.info(f"Creating new event {str(component.get('summary'))} with UID {uid}")
|
||||
self.with_context({'caldav_no_sync': True}).create({
|
||||
|
|
@ -179,13 +215,46 @@ class CalendarEvent(models.Model):
|
|||
'description': str(component.get('description')),
|
||||
'location': str(component.get('location')),
|
||||
'caldav_uid': uid,
|
||||
'partner_ids': [(6, 0, attendee_ids.ids)],
|
||||
})
|
||||
else:
|
||||
_logger.info(f"Updating existing event {event.name} with UID {uid}")
|
||||
|
||||
# Get current partner_ids and merge with new attendees
|
||||
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
|
||||
def _html_to_text(html):
|
||||
return BeautifulSoup(html, "html.parser").getText()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _map_attendee_status(state):
|
||||
mapping = {
|
||||
'needsAction': 'NEEDS-ACTION',
|
||||
'accepted': 'ACCEPTED',
|
||||
'declined': 'DECLINED',
|
||||
'tentative': 'TENTATIVE',
|
||||
}
|
||||
return mapping.get(state, 'NEEDS-ACTION')
|
||||
|
||||
@staticmethod
|
||||
def _map_ical_status(ical_status):
|
||||
mapping = {
|
||||
'NEEDS-ACTION': 'needsAction',
|
||||
'ACCEPTED': 'accepted',
|
||||
'DECLINED': 'declined',
|
||||
'TENTATIVE': 'tentative',
|
||||
}
|
||||
return mapping.get(ical_status, 'needsAction')
|
||||
|
|
|
|||
Loading…
Reference in a new issue