update to allow selecting calendar
This commit is contained in:
parent
b96a1efca9
commit
e3822618c7
10 changed files with 98 additions and 92 deletions
|
|
@ -1,2 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from . import main
|
||||
|
|
@ -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)
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from . import calendar_event
|
||||
from . import res_users
|
||||
from . import caldav_calendar
|
||||
|
|
|
|||
9
caldav_sync/models/caldav_calendar.py
Normal file
9
caldav_sync/models/caldav_calendar.py
Normal 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)
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
0
caldav_sync/security/__init__.py
Normal file
0
caldav_sync/security/__init__.py
Normal file
3
caldav_sync/security/ir.model.access.csv
Normal file
3
caldav_sync/security/ir.model.access.csv
Normal 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,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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue