From e3822618c703910753ad4c6d647a8a6563cdb3db Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 23 May 2024 12:49:51 -0400 Subject: [PATCH] update to allow selecting calendar --- caldav_sync/controllers/__init__.py | 2 - caldav_sync/controllers/main.py | 57 ------------------------ caldav_sync/models/__init__.py | 1 + caldav_sync/models/caldav_calendar.py | 9 ++++ caldav_sync/models/calendar_event.py | 13 ++++-- caldav_sync/models/res_users.py | 30 +++++++++++-- caldav_sync/security/__init__.py | 0 caldav_sync/security/ir.model.access.csv | 3 ++ caldav_sync/tests/test_caldav_sync.py | 37 ++++++++------- caldav_sync/views/res_users_views.xml | 38 ++++++++++++---- 10 files changed, 98 insertions(+), 92 deletions(-) delete mode 100644 caldav_sync/controllers/__init__.py delete mode 100644 caldav_sync/controllers/main.py create mode 100644 caldav_sync/models/caldav_calendar.py create mode 100644 caldav_sync/security/__init__.py create mode 100644 caldav_sync/security/ir.model.access.csv diff --git a/caldav_sync/controllers/__init__.py b/caldav_sync/controllers/__init__.py deleted file mode 100644 index 757b12a..0000000 --- a/caldav_sync/controllers/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -from . import main diff --git a/caldav_sync/controllers/main.py b/caldav_sync/controllers/main.py deleted file mode 100644 index 97ca3c1..0000000 --- a/caldav_sync/controllers/main.py +++ /dev/null @@ -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) diff --git a/caldav_sync/models/__init__.py b/caldav_sync/models/__init__.py index 1ac5a2d..8ab1c11 100644 --- a/caldav_sync/models/__init__.py +++ b/caldav_sync/models/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- from . import calendar_event from . import res_users +from . import caldav_calendar diff --git a/caldav_sync/models/caldav_calendar.py b/caldav_sync/models/caldav_calendar.py new file mode 100644 index 0000000..e820c5e --- /dev/null +++ b/caldav_sync/models/caldav_calendar.py @@ -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) diff --git a/caldav_sync/models/calendar_event.py b/caldav_sync/models/calendar_event.py index fb72e64..207fe2e 100644 --- a/caldav_sync/models/calendar_event.py +++ b/caldav_sync/models/calendar_event.py @@ -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() diff --git a/caldav_sync/models/res_users.py b/caldav_sync/models/res_users.py index 6c8db32..2b240bd 100644 --- a/caldav_sync/models/res_users.py +++ b/caldav_sync/models/res_users.py @@ -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) diff --git a/caldav_sync/security/__init__.py b/caldav_sync/security/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/caldav_sync/security/ir.model.access.csv b/caldav_sync/security/ir.model.access.csv new file mode 100644 index 0000000..ac64680 --- /dev/null +++ b/caldav_sync/security/ir.model.access.csv @@ -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 + diff --git a/caldav_sync/tests/test_caldav_sync.py b/caldav_sync/tests/test_caldav_sync.py index b126f9f..09a210f 100644 --- a/caldav_sync/tests/test_caldav_sync.py +++ b/caldav_sync/tests/test_caldav_sync.py @@ -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') + diff --git a/caldav_sync/views/res_users_views.xml b/caldav_sync/views/res_users_views.xml index c1649f1..2745f71 100644 --- a/caldav_sync/views/res_users_views.xml +++ b/caldav_sync/views/res_users_views.xml @@ -1,16 +1,38 @@ - - res.users.form.caldav.sync + + res.users.form res.users - - - - - - + + + + + + +