update to allow selecting calendar

This commit is contained in:
Marc Durepos 2024-05-23 12:49:51 -04:00
parent b96a1efca9
commit e3822618c7
10 changed files with 98 additions and 92 deletions

View file

@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-
from . import main

View file

@ -1,57 +0,0 @@
from odoo import http
from odoo.http import request
import logging
import caldav
from caldav.elements import dav, cdav
from datetime import datetime
_logger = logging.getLogger(__name__)
class CaldavController(http.Controller):
@http.route('/caldav_sync/sync', type='json', auth='user')
def sync(self, **kwargs):
# Fetch user credentials and server settings from Odoo
user = request.env.user
server_url = user.caldav_server_url
username = user.caldav_username
password = user.caldav_password
# Connect to the CalDAV server
client = caldav.DAVClient(url=server_url, username=username, password=password)
principal = client.principal()
calendars = principal.calendars()
for calendar in calendars:
events = calendar.events()
for event in events:
ical = event.icalendar()
self.sync_event(ical)
return {'status': 'success', 'message': 'Synchronization completed'}
@staticmethod
def sync_event(ical):
event = ical.subcomponents[0]
uid = event['UID']
start = event['DTSTART'].dt
end = event['DTEND'].dt
summary = event['SUMMARY']
description = event.get('DESCRIPTION', '')
location = event.get('LOCATION', '')
# Search for an existing event in Odoo
odoo_event = request.env['calendar.event'].search([('caldav_uid', '=', uid)], limit=1)
values = {
'name': summary,
'start': start,
'stop': end,
'description': description,
'location': location,
}
if odoo_event:
odoo_event.write(values)
else:
values['caldav_uid'] = uid
request.env['calendar.event'].create(values)

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import calendar_event from . import calendar_event
from . import res_users from . import res_users
from . import caldav_calendar

View file

@ -0,0 +1,9 @@
from odoo import models, fields
class CaldavCalendar(models.Model):
_name = 'caldav.calendar'
_description = 'CalDAV Calendar'
user_id = fields.Many2one('res.users', string='User', required=True, ondelete='cascade')
name = fields.Char('Name', required=True)
url = fields.Char('URL', required=True)

View file

@ -1,3 +1,4 @@
from odoo import models, fields, api from odoo import models, fields, api
import caldav import caldav
import logging import logging
@ -41,7 +42,7 @@ class CalendarEvent(models.Model):
for event in self: for event in self:
if event._is_caldav_enabled(): if event._is_caldav_enabled():
client = event._get_caldav_client() client = event._get_caldav_client()
calendar = client.principal().calendars()[0] # Assuming the first calendar calendar = client.calendar(url=event._get_caldav_calendar_url())
caldav_event = calendar.add_event(event._get_icalendar()) caldav_event = calendar.add_event(event._get_icalendar())
if caldav_event.id: if caldav_event.id:
event.with_context(skip_caldav_sync=True).write({'caldav_uid': caldav_event.id}) event.with_context(skip_caldav_sync=True).write({'caldav_uid': caldav_event.id})
@ -52,7 +53,7 @@ class CalendarEvent(models.Model):
for event in self: for event in self:
if event.caldav_uid and event._is_caldav_enabled(): if event.caldav_uid and event._is_caldav_enabled():
client = event._get_caldav_client() client = event._get_caldav_client()
calendar = client.principal().calendars()[0] # Assuming the first calendar calendar = client.calendar(url=event._get_caldav_calendar_url())
caldav_event = calendar.event_by_uid(event.caldav_uid) caldav_event = calendar.event_by_uid(event.caldav_uid)
if caldav_event: if caldav_event:
caldav_event.delete() caldav_event.delete()
@ -67,7 +68,7 @@ class CalendarEvent(models.Model):
try: try:
_logger.info(f'Polling CalDAV server for user {user.name}...') _logger.info(f'Polling CalDAV server for user {user.name}...')
client = user._get_caldav_client() client = user._get_caldav_client()
calendar = client.principal().calendars()[0] # Assuming the first calendar calendar = client.calendar(url=user.caldav_calendar_id.url)
events = calendar.events() events = calendar.events()
# Collect all current CalDAV UIDs for this user # Collect all current CalDAV UIDs for this user
@ -118,7 +119,11 @@ class CalendarEvent(models.Model):
def _is_caldav_enabled(self): def _is_caldav_enabled(self):
user = self.env.user user = self.env.user
return bool(user.caldav_server_url and user.caldav_username and user.caldav_password) return bool(user.caldav_server_url and user.caldav_username and user.caldav_password and user.caldav_calendar_id)
def _get_caldav_calendar_url(self):
user = self.env.user
return user.caldav_calendar_id.url
def _get_icalendar(self): def _get_icalendar(self):
cal = Calendar() cal = Calendar()

View file

@ -1,5 +1,8 @@
from odoo import models, fields from odoo import models, fields, api
import caldav import caldav
import logging
_logger = logging.getLogger(__name__)
class ResUsers(models.Model): class ResUsers(models.Model):
_inherit = 'res.users' _inherit = 'res.users'
@ -7,9 +10,28 @@ class ResUsers(models.Model):
caldav_server_url = fields.Char('CalDAV Server URL') caldav_server_url = fields.Char('CalDAV Server URL')
caldav_username = fields.Char('CalDAV Username') caldav_username = fields.Char('CalDAV Username')
caldav_password = fields.Char('CalDAV Password') caldav_password = fields.Char('CalDAV Password')
caldav_calendar_id = fields.Many2one('caldav.calendar', string='CalDAV Calendar') # Updated field
@api.model
def _is_caldav_enabled(self):
self.ensure_one()
return bool(self.caldav_server_url and self.caldav_username and self.caldav_password and self.caldav_calendar_id)
def fetch_caldav_calendars(self):
self.ensure_one()
client = caldav.DAVClient(url=self.caldav_server_url, username=self.caldav_username, password=self.caldav_password)
principal = client.principal()
calendars = principal.calendars()
caldav_calendar_model = self.env['caldav.calendar']
caldav_calendar_model.search([('user_id', '=', self.id)]).unlink() # Clear existing calendars
for calendar in calendars:
caldav_calendar_model.create({
'user_id': self.id,
'name': calendar.name,
'url': str(calendar.url),
})
return True
def _get_caldav_client(self): def _get_caldav_client(self):
self.ensure_one()
return caldav.DAVClient(url=self.caldav_server_url, username=self.caldav_username, password=self.caldav_password) return caldav.DAVClient(url=self.caldav_server_url, username=self.caldav_username, password=self.caldav_password)
def _is_caldav_enabled(self):
return bool(self.caldav_server_url and self.caldav_username and self.caldav_password)

View file

View file

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_caldav_calendar,access_caldav_calendar,model_caldav_calendar,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_caldav_calendar access_caldav_calendar model_caldav_calendar base.group_user 1 1 1 1

View file

@ -1,9 +1,12 @@
from odoo.tests.common import TransactionCase from odoo.tests.common import TransactionCase
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
import caldav import caldav
from icalendar import Calendar, Event from icalendar import Calendar, Event
from datetime import datetime from datetime import datetime
import logging
_logger = logging.getLogger(__name__)
class TestCaldavSync(TransactionCase): class TestCaldavSync(TransactionCase):
@ -17,17 +20,21 @@ class TestCaldavSync(TransactionCase):
'caldav_password': 'password', 'caldav_password': 'password',
}) })
self.env = self.env(context=dict(self.env.context, no_reset_password=True)) self.env = self.env(context=dict(self.env.context, no_reset_password=True))
self.calendar = self.env['caldav.calendar'].create({
'user_id': self.user.id,
'name': 'Test Calendar',
'url': 'http://testserver/caldav/calendars/testuser/calendar'
})
self.user.write({'caldav_calendar_id': self.calendar.id})
@patch('odoo.addons.caldav_sync.models.calendar_event.CalendarEvent._get_caldav_client') @patch('odoo.addons.caldav_sync.models.calendar_event.CalendarEvent._get_caldav_client')
def test_create_caldav_event(self, mock_get_caldav_client): def test_create_caldav_event(self, mock_get_caldav_client):
mock_client = MagicMock() mock_client = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock() mock_calendar = MagicMock()
mock_event = MagicMock() mock_event = MagicMock()
mock_event.id = 'test-uid-12345' mock_event.id = 'test-uid-12345' # Ensure the id attribute is correctly set
mock_client.principal.return_value = mock_principal mock_client.calendar.return_value = mock_calendar
mock_principal.calendars.return_value = [mock_calendar]
mock_calendar.add_event.return_value = mock_event mock_calendar.add_event.return_value = mock_event
mock_get_caldav_client.return_value = mock_client mock_get_caldav_client.return_value = mock_client
@ -47,13 +54,11 @@ class TestCaldavSync(TransactionCase):
@patch('odoo.addons.caldav_sync.models.calendar_event.CalendarEvent.sync_to_caldav') @patch('odoo.addons.caldav_sync.models.calendar_event.CalendarEvent.sync_to_caldav')
def test_update_caldav_event(self, mock_sync_to_caldav, mock_get_caldav_client): def test_update_caldav_event(self, mock_sync_to_caldav, mock_get_caldav_client):
mock_client = MagicMock() mock_client = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock() mock_calendar = MagicMock()
mock_event = MagicMock() mock_event = MagicMock()
mock_event.id = 'test-uid-12345' mock_event.id = 'test-uid-12345'
mock_client.principal.return_value = mock_principal mock_client.calendar.return_value = mock_calendar
mock_principal.calendars.return_value = [mock_calendar]
mock_calendar.add_event.return_value = mock_event mock_calendar.add_event.return_value = mock_event
mock_get_caldav_client.return_value = mock_client mock_get_caldav_client.return_value = mock_client
@ -74,19 +79,17 @@ class TestCaldavSync(TransactionCase):
self.assertEqual(mock_sync_to_caldav.call_count, 2) self.assertEqual(mock_sync_to_caldav.call_count, 2)
self.assertEqual(event.name, 'Updated Test Event') self.assertEqual(event.name, 'Updated Test Event')
self.assertEqual(event.start, datetime.strptime('2024-05-22 12:00:00', '%Y-%m-%d %H:%M:%S')) self.assertEqual(event.start, datetime(2024, 5, 22, 12, 0))
@patch('odoo.addons.caldav_sync.models.calendar_event.CalendarEvent._get_caldav_client') @patch('odoo.addons.caldav_sync.models.calendar_event.CalendarEvent._get_caldav_client')
@patch('odoo.addons.caldav_sync.models.calendar_event.CalendarEvent.remove_from_caldav') @patch('odoo.addons.caldav_sync.models.calendar_event.CalendarEvent.remove_from_caldav')
def test_delete_caldav_event(self, mock_remove_from_caldav, mock_get_caldav_client): def test_delete_caldav_event(self, mock_remove_from_caldav, mock_get_caldav_client):
mock_client = MagicMock() mock_client = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock() mock_calendar = MagicMock()
mock_event = MagicMock() mock_event = MagicMock()
mock_event.id = 'test-uid-12345' mock_event.id = 'test-uid-12345'
mock_client.principal.return_value = mock_principal mock_client.calendar.return_value = mock_calendar
mock_principal.calendars.return_value = [mock_calendar]
mock_calendar.add_event.return_value = mock_event mock_calendar.add_event.return_value = mock_event
mock_get_caldav_client.return_value = mock_client mock_get_caldav_client.return_value = mock_client
@ -107,17 +110,16 @@ class TestCaldavSync(TransactionCase):
mock_sync_event_from_ical.return_value = None mock_sync_event_from_ical.return_value = None
with patch('caldav.DAVClient') as MockClient: with patch('caldav.DAVClient') as MockClient:
mock_client = MockClient.return_value mock_client = MockClient.return_value
mock_principal = mock_client.principal.return_value mock_calendar = mock_client.calendar.return_value
mock_calendar = mock_principal.calendars.return_value[0]
mock_event = MagicMock() mock_event = MagicMock()
# Create a Calendar object and add an Event to it # Create a Calendar object and add an Event to it
cal = Calendar() cal = Calendar()
event = Event() event = Event()
event.add('uid', 'test-uid-12345') event.add('uid', 'test-uid-12345')
event.add('dtstamp', datetime.strptime('20240522T100000Z', '%Y%m%dT%H%M%SZ')) event.add('dtstamp', datetime(2024, 5, 22, 10, 0, 0))
event.add('dtstart', datetime.strptime('20240522T100000Z', '%Y%m%dT%H%M%SZ')) event.add('dtstart', datetime(2024, 5, 22, 10, 0, 0))
event.add('dtend', datetime.strptime('20240522T110000Z', '%Y%m%dT%H%M%SZ')) event.add('dtend', datetime(2024, 5, 22, 11, 0, 0))
event.add('summary', 'Polled Event') event.add('summary', 'Polled Event')
event.add('description', 'This event was polled from CalDAV') event.add('description', 'This event was polled from CalDAV')
event.add('location', 'Polled Location') event.add('location', 'Polled Location')
@ -134,7 +136,8 @@ class TestCaldavSync(TransactionCase):
def test_poll_caldav_server_with_exception(self, mock_logger): def test_poll_caldav_server_with_exception(self, mock_logger):
with patch('caldav.DAVClient') as MockClient: with patch('caldav.DAVClient') as MockClient:
mock_client = MockClient.return_value mock_client = MockClient.return_value
mock_client.principal.side_effect = Exception('Invalid credentials') mock_client.calendar.side_effect = Exception('Invalid credentials')
self.env['calendar.event'].poll_caldav_server() self.env['calendar.event'].poll_caldav_server()
mock_logger.error.assert_any_call('Failed to poll CalDAV server for user Test User: Invalid credentials') mock_logger.error.assert_any_call('Failed to poll CalDAV server for user Test User: Invalid credentials')

View file

@ -1,16 +1,38 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<odoo> <odoo>
<record id="view_users_form_caldav_sync" model="ir.ui.view"> <record id="view_users_form" model="ir.ui.view">
<field name="name">res.users.form.caldav.sync</field> <field name="name">res.users.form</field>
<field name="model">res.users</field> <field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/> <field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//group[@name='preferences']" position="inside"> <xpath expr="//notebook" position="inside">
<group string="CalDAV Sync"> <page string="Calendar">
<field name="caldav_server_url"/> <group string="CalDAV">
<field name="caldav_username"/> <field name="caldav_server_url"/>
<field name="caldav_password"/> <field name="caldav_username"/>
</group> <field name="caldav_password"/>
<button name="fetch_caldav_calendars" type="object" string="Fetch Calendars"/>
<field name="caldav_calendar_id"/>
</group>
</page>
</xpath>
</field>
</record>
<record id="view_users_preferences_form" model="ir.ui.view">
<field name="name">res.users.form</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form_simple_modif"/>
<field name="arch" type="xml">
<xpath expr="//notebook" position="inside">
<page string="Calendar">
<group string="CalDAV">
<field name="caldav_server_url"/>
<field name="caldav_username"/>
<field name="caldav_password"/>
<button name="fetch_caldav_calendars" type="object" string="Fetch Calendars"/>
<field name="caldav_calendar_id"/>
</group>
</page>
</xpath> </xpath>
</field> </field>
</record> </record>