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 -*-
from . import calendar_event
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
import caldav
import logging
@ -41,7 +42,7 @@ class CalendarEvent(models.Model):
for event in self:
if event._is_caldav_enabled():
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())
if 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:
if event.caldav_uid and event._is_caldav_enabled():
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)
if caldav_event:
caldav_event.delete()
@ -67,7 +68,7 @@ class CalendarEvent(models.Model):
try:
_logger.info(f'Polling CalDAV server for user {user.name}...')
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()
# Collect all current CalDAV UIDs for this user
@ -118,7 +119,11 @@ class CalendarEvent(models.Model):
def _is_caldav_enabled(self):
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):
cal = Calendar()

View file

@ -1,5 +1,8 @@
from odoo import models, fields
from odoo import models, fields, api
import caldav
import logging
_logger = logging.getLogger(__name__)
class ResUsers(models.Model):
_inherit = 'res.users'
@ -7,9 +10,28 @@ class ResUsers(models.Model):
caldav_server_url = fields.Char('CalDAV Server URL')
caldav_username = fields.Char('CalDAV Username')
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):
self.ensure_one()
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 unittest.mock import patch, MagicMock
import caldav
from icalendar import Calendar, Event
from datetime import datetime
import logging
_logger = logging.getLogger(__name__)
class TestCaldavSync(TransactionCase):
@ -17,17 +20,21 @@ class TestCaldavSync(TransactionCase):
'caldav_password': 'password',
})
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')
def test_create_caldav_event(self, mock_get_caldav_client):
mock_client = MagicMock()
mock_principal = MagicMock()
mock_calendar = 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_principal.calendars.return_value = [mock_calendar]
mock_client.calendar.return_value = mock_calendar
mock_calendar.add_event.return_value = mock_event
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')
def test_update_caldav_event(self, mock_sync_to_caldav, mock_get_caldav_client):
mock_client = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_event = MagicMock()
mock_event.id = 'test-uid-12345'
mock_client.principal.return_value = mock_principal
mock_principal.calendars.return_value = [mock_calendar]
mock_client.calendar.return_value = mock_calendar
mock_calendar.add_event.return_value = mock_event
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(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.remove_from_caldav')
def test_delete_caldav_event(self, mock_remove_from_caldav, mock_get_caldav_client):
mock_client = MagicMock()
mock_principal = MagicMock()
mock_calendar = MagicMock()
mock_event = MagicMock()
mock_event.id = 'test-uid-12345'
mock_client.principal.return_value = mock_principal
mock_principal.calendars.return_value = [mock_calendar]
mock_client.calendar.return_value = mock_calendar
mock_calendar.add_event.return_value = mock_event
mock_get_caldav_client.return_value = mock_client
@ -107,17 +110,16 @@ class TestCaldavSync(TransactionCase):
mock_sync_event_from_ical.return_value = None
with patch('caldav.DAVClient') as MockClient:
mock_client = MockClient.return_value
mock_principal = mock_client.principal.return_value
mock_calendar = mock_principal.calendars.return_value[0]
mock_calendar = mock_client.calendar.return_value
mock_event = MagicMock()
# Create a Calendar object and add an Event to it
cal = Calendar()
event = Event()
event.add('uid', 'test-uid-12345')
event.add('dtstamp', datetime.strptime('20240522T100000Z', '%Y%m%dT%H%M%SZ'))
event.add('dtstart', datetime.strptime('20240522T100000Z', '%Y%m%dT%H%M%SZ'))
event.add('dtend', datetime.strptime('20240522T110000Z', '%Y%m%dT%H%M%SZ'))
event.add('dtstamp', datetime(2024, 5, 22, 10, 0, 0))
event.add('dtstart', datetime(2024, 5, 22, 10, 0, 0))
event.add('dtend', datetime(2024, 5, 22, 11, 0, 0))
event.add('summary', 'Polled Event')
event.add('description', 'This event was polled from CalDAV')
event.add('location', 'Polled Location')
@ -134,7 +136,8 @@ class TestCaldavSync(TransactionCase):
def test_poll_caldav_server_with_exception(self, mock_logger):
with patch('caldav.DAVClient') as MockClient:
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()
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" ?>
<odoo>
<record id="view_users_form_caldav_sync" model="ir.ui.view">
<field name="name">res.users.form.caldav.sync</field>
<record id="view_users_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"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='preferences']" position="inside">
<group string="CalDAV Sync">
<field name="caldav_server_url"/>
<field name="caldav_username"/>
<field name="caldav_password"/>
</group>
<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>
</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>
</field>
</record>