From 663acd236b541d82ea5d2fd964ed6d0fca1be9a3 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Tue, 17 Dec 2024 10:41:30 -0500 Subject: [PATCH] caldav_sync: back-port to 16.0 and add SELF_WRITEABLE_FIELDS for res.users --- caldav_sync/README.rst | 58 +++ caldav_sync/__init__.py | 2 + caldav_sync/__manifest__.py | 29 ++ caldav_sync/data/caldav_sync_data.xml | 13 + caldav_sync/data/neutralize.sql | 8 + caldav_sync/models/__init__.py | 3 + caldav_sync/models/calendar_event.py | 447 ++++++++++++++++++ caldav_sync/models/res_users.py | 17 + caldav_sync/requirements.txt | 3 + caldav_sync/static/description/icon.png | Bin 0 -> 20840 bytes .../description/images/main_screenshot.png | Bin 0 -> 55221 bytes caldav_sync/static/description/index.html | 72 +++ caldav_sync/tests/__init__.py | 1 + caldav_sync/tests/test_caldav_sync.py | 167 +++++++ caldav_sync/views/res_users_views.xml | 35 ++ 15 files changed, 855 insertions(+) create mode 100644 caldav_sync/README.rst create mode 100644 caldav_sync/__init__.py create mode 100644 caldav_sync/__manifest__.py create mode 100644 caldav_sync/data/caldav_sync_data.xml create mode 100644 caldav_sync/data/neutralize.sql create mode 100644 caldav_sync/models/__init__.py create mode 100644 caldav_sync/models/calendar_event.py create mode 100644 caldav_sync/models/res_users.py create mode 100644 caldav_sync/requirements.txt create mode 100644 caldav_sync/static/description/icon.png create mode 100644 caldav_sync/static/description/images/main_screenshot.png create mode 100644 caldav_sync/static/description/index.html create mode 100644 caldav_sync/tests/__init__.py create mode 100644 caldav_sync/tests/test_caldav_sync.py create mode 100644 caldav_sync/views/res_users_views.xml diff --git a/caldav_sync/README.rst b/caldav_sync/README.rst new file mode 100644 index 0000000..301aabd --- /dev/null +++ b/caldav_sync/README.rst @@ -0,0 +1,58 @@ +CalDAV Synchronization +====================== + +Bemade Inc. + +Copyright (C) 2023-June Bemade Inc. (). +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 + +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. + +Features +-------- + +- Synchronize Odoo calendar events with CalDAV servers. +- 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 +------------- + +1. Install the module in Odoo. +2. Go to the User settings in Odoo. +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. +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 (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 by + triggering the scheduled action in Odoo. + +License +------- + +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 diff --git a/caldav_sync/__init__.py b/caldav_sync/__init__.py new file mode 100644 index 0000000..a0fdc10 --- /dev/null +++ b/caldav_sync/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/caldav_sync/__manifest__.py b/caldav_sync/__manifest__.py new file mode 100644 index 0000000..f0a695e --- /dev/null +++ b/caldav_sync/__manifest__.py @@ -0,0 +1,29 @@ +# Bemade Inc. +# +# Copyright (C) 2023-June Bemade Inc. (). +# Author: Marc Durepos (Contact : mdurepos@durpro.com) +# +# 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 + +{ + "name": "CalDAV Synchronization", + "version": "16.0.0.5.6", + "license": "LGPL-3", + "category": "Productivity", + "summary": "Synchronize Odoo Calendar Events with CalDAV Servers", + "author": "Bemade Inc.", + "website": "https://www.bemade.org", + "depends": ["base", "calendar"], + "external_dependencies": { + "python": ["caldav", "icalendar", "bs4"], + }, + "images": ["static/description/images/main_screenshot.png"], + "data": [ + "views/res_users_views.xml", + "data/caldav_sync_data.xml", + ], + "installable": True, + "application": True, + "auto_install": False, +} diff --git a/caldav_sync/data/caldav_sync_data.xml b/caldav_sync/data/caldav_sync_data.xml new file mode 100644 index 0000000..a97fad2 --- /dev/null +++ b/caldav_sync/data/caldav_sync_data.xml @@ -0,0 +1,13 @@ + + + CalDAV Sync + + code + model.poll_caldav_server() + 1 + minutes + -1 + + + + diff --git a/caldav_sync/data/neutralize.sql b/caldav_sync/data/neutralize.sql new file mode 100644 index 0000000..93cf59e --- /dev/null +++ b/caldav_sync/data/neutralize.sql @@ -0,0 +1,8 @@ +-- Neutralize CalDAV synchronization by setting credentials to NULL +UPDATE res_users +SET caldav_calendar_url = NULL, + caldav_username = NULL, + caldav_password = NULL +WHERE caldav_calendar_url IS NOT NULL + OR caldav_username IS NOT NULL + OR caldav_password IS NOT NULL; diff --git a/caldav_sync/models/__init__.py b/caldav_sync/models/__init__.py new file mode 100644 index 0000000..1ac5a2d --- /dev/null +++ b/caldav_sync/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from . import calendar_event +from . import res_users diff --git a/caldav_sync/models/calendar_event.py b/caldav_sync/models/calendar_event.py new file mode 100644 index 0000000..a117cf0 --- /dev/null +++ b/caldav_sync/models/calendar_event.py @@ -0,0 +1,447 @@ +import uuid +from odoo import models, api, fields +from odoo.exceptions import UserError +import caldav +import logging +from datetime import datetime +from icalendar import Calendar, Event, vCalAddress, vText +from bs4 import BeautifulSoup +import re +from pytz import timezone, utc +from caldav.elements.cdav import CompFilter + +_logger = logging.getLogger(__name__) + +WEEKDAY_MAP = { + 0: "MO", + 1: "TU", + 2: "WE", + 3: "TH", + 4: "FR", + 5: "SA", + 6: "SU", +} + + +def _parse_rrule_string(rrule_str): + def try_to_int(part): + try: + return int(part) + except Exception: + return part + + regex_str = "RRULE:(.*)$" + regex = re.compile(regex_str) + params_match = regex.search(rrule_str) + params_part = params_match.groups()[0] + params = params_part.split(";") + params_dict = {} + for param in params: + parts = param.split("=") + params_dict.update({parts[0]: try_to_int(parts[1])}) + return params_dict + + +class CalendarEvent(models.Model): + _inherit = "calendar.event" + + caldav_uid = fields.Char(string="CalDAV UID", readonly=True) + caldav_recurrence_id = fields.Char(string="CalDAV Recurrence ID", readonly=True) + + @api.model + def create(self, vals): + if not vals.get("caldav_uid"): + vals["caldav_uid"] = str(uuid.uuid4()) + event = super(CalendarEvent, self).create(vals) + if not self.env.context.get("caldav_no_sync"): + try: + _logger.debug(f"Creating event {event.name} in CalDAV") + event.sync_create_to_caldav() + except Exception as e: + _logger.error(f"Failed to create event in CalDAV server: {e}") + return event + + def write(self, vals): + res = super(CalendarEvent, self).write(vals) + if not self.env.context.get("caldav_no_sync") and self.ids: + for rec in self: + try: + _logger.debug(f"Updating event {self.name} in CalDAV") + rec.sync_update_to_caldav() + except Exception as e: + _logger.error(f"Failed to update event in CalDAV server: {e}") + return res + + def unlink(self): + if not self.env.context.get("caldav_no_sync"): + for event in self: + try: + _logger.debug(f"Removing event {event.name} from CalDAV") + event.sync_remove_from_caldav() + except Exception as e: + _logger.error(f"Failed to delete event from CalDAV server: {e}") + return super(CalendarEvent, self).unlink() + + def _is_caldav_enabled(self): + return self.env.user.is_caldav_enabled() + + def _get_caldav_client(self): + user = self.env.user + return caldav.DAVClient( + url=user.caldav_calendar_url, + username=user.caldav_username, + password=user.caldav_password, + ) + + def sync_create_to_caldav(self): + if not self._is_caldav_enabled(): + return + client = self._get_caldav_client() + calendar = client.calendar(url=self.env.user.caldav_calendar_url) + for event in self: + ical_event = event._get_icalendar() + try: + _logger.debug(f"Creating new CalDAV event for {event.name}") + caldav_event = calendar.add_event(ical_event) + caldav_uid = caldav_event.vobject_instance.vevent.uid.value + _logger.debug(f"New CalDAV UID: {caldav_uid}") + event.with_context(caldav_no_sync=True).write( + {"caldav_uid": caldav_uid} + ) + except Exception as e: + _logger.error(f"Failed to sync event to CalDAV server: {e}") + + def sync_update_to_caldav(self): + if not self._is_caldav_enabled(): + return + client = self._get_caldav_client() + calendar = client.calendar(url=self.env.user.caldav_calendar_url) + for event in self: + ical_event = event._get_icalendar() + try: + _logger.debug(f"Updating existing CalDAV event {event.caldav_uid}") + calendar.save_event(ical=ical_event) + except Exception as e: + _logger.error(f"Failed to sync event to CalDAV server: {e}") + + def sync_remove_from_caldav(self): + if not self._is_caldav_enabled(): + return + client = self._get_caldav_client() + calendar = client.calendar(url=self.env.user.caldav_calendar_url) + for event in self: + if event.caldav_uid: + try: + _logger.debug(f"Removing CalDAV event {event.caldav_uid}") + event._get_icalendar().delete() + except caldav.error.NotFoundError: + _logger.warning( + f"CalDAV event {event.caldav_uid} not found on server." + ) + except Exception as e: + _logger.error(f"Failed to remove event from CalDAV server: {e}") + + def _get_icalendar(self): + calendar = Calendar() + calendar.add("prodid", "-//Odoo//mxm.dk//") + calendar.add("version", "2.0") + + for event in self: + user_tz = timezone("UTC") + if event.user_id.tz: + user_tz = timezone(event.user_id.tz) + ical_event = Event() + ical_event.add("uid", event.caldav_uid) + ical_event.add( + "dtstamp", utc.localize(event.write_date).astimezone(user_tz) + ) + if event.name: + ical_event.add("summary", event.name) + if event.description and self._html_to_text(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: + if partner == event.user_id.partner_id: + continue + 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) + organizer = vCalAddress(f"MAILTO:{event.user_id.email}") + organizer.params["cn"] = event.user_id.name + ical_event.add("organizer", organizer) + # Add RRULE if the event is recurrent + if event.recurrency and event.recurrence_id: + rrule = event.recurrence_id._get_rrule() + rrule_dict = _parse_rrule_string(str(rrule)) + ical_event.add("rrule", rrule_dict) + + # Add DTSTART and DTEND + ical_event.add("dtstart", utc.localize(event.start).astimezone(user_tz)) + ical_event.add("dtend", utc.localize(event.stop).astimezone(user_tz)) + + calendar.add_component(ical_event) + + return calendar.to_ical() + + @api.model + def poll_caldav_server(self): + all_users = ( + self.env["res.users"].search([]).filtered(lambda u: u.is_caldav_enabled()) + ) + for user in all_users: + self.with_user(user).poll_user_caldav_server() + + @api.model + def poll_user_caldav_server(self): + if not self._is_caldav_enabled(): + return + client = self._get_caldav_client() + try: + calendar = client.calendar(url=self.env.user.caldav_calendar_url) + events = calendar.events() + except Exception as e: + _logger.error(e) + try: + principal = client.principal() + msg = f"""Failed to connect to the calendar, but successfully connected to the + server at {client.url}. + You may need to select another calendar URL from those below. + + Available calendars: + + """ + for calendar in principal.calendars(): + msg += f"{calendar.name}: {calendar.url}\n" + raise UserError(msg) + except Exception as e: + _logger.error(e) + return + caldav_uids = set() + + _logger.info(f"Polling CalDAV server for user {self.env.user.name}") + + for caldav_event in events: + ical_event = caldav_event.icalendar_instance + self.sync_event_from_ical(ical_event) + for component in ical_event.subcomponents: + if isinstance(component, Event): + uid = str(component.get("uid")) + recurrence_id = str(component.get("recurrence-id")) + if recurrence_id == "None": + recurrence_id = "" + caldav_uids.add(f"{uid}{recurrence_id}") + + _logger.info(f"CalDAV UIDs fetched: {caldav_uids}") + + # Remove Odoo events that no longer exist on the CalDAV server + odoo_events = self.search([("caldav_uid", "!=", False)]) + for event in odoo_events: + recurrence_id = event.caldav_recurrence_id or "" + event_uid = f"{event.caldav_uid}{event.caldav_recurrence_id or ''}" + if event_uid not in caldav_uids: + _logger.info( + f"Deleting orphan event {event.name} with UID {event.caldav_uid} " + f"and Recurrence ID {event.caldav_recurrence_id or ''}" + ) + event.with_context(caldav_no_sync=True).unlink() + + @api.model + def _get_existing_instance(self, uid, recurrence_id): + instance = self.env["calendar.event"].search( + [("caldav_uid", "=", uid), ("recurrence_id", "=", recurrence_id)] + ) + return instance or self.env["calendar.event"].search( + [ + ("caldav_uid", "=", "uid"), + ("recurrence_id", "=", False), + ] + ) + + def _get_recurrency_values_from_ical_event(self, component): + """Match the fields from calendar.event (recurring fields) to the fields specified in RRULE at + https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html""" + + rrule = component.get("rrule") + if not rrule: + if not self.recurrency: + # No change, this was already not a recurring event + return {} + else: + # This was a recurring event and has been made non-recurring + if self.recurrence_id.base_event_id != self: + # This is not the base event, so change its recurrency only + return { + "recurrence_update": "self_only", + "recurrency": False, + "follow_recurrence": False, + } + else: + # This is the base event, so change all events in the list + return {"recurrence_update": "all_events", "recurrency": False} + rrule_str = rrule.to_ical().decode("utf-8") + sequence = component.get("sequence") + if sequence and sequence != 0: + # This is not the base event so we can't change recurrence properties + return {} + + caldav_recurrence_id = component.get("recurrence-id") + rrule_params = self.env["calendar.recurrence"]._rrule_parse( + rrule_str, component.decoded("dtstart") + ) + vals = { + "recurrency": True, + "follow_recurrence": True, + "caldav_recurrence_id": caldav_recurrence_id, + "recurrence_update": "all_events", + "rrule_type": rrule_params.get("rrule_type"), + "end_type": rrule_params.get("end_type"), + "interval": rrule_params.get("interval"), + "count": rrule_params.get("count"), + "month_by": rrule_params.get("monty_by"), + "day": rrule_params.get("day"), + "byday": rrule_params.get("byday"), + "until": rrule_params.get("until"), + } + + if rrule_params.get("weekday"): + vals.update(rrule_params.get("weekday")) + day_list = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] + vals.update( + {day: rrule_params.get(day) for day in day_list if day in rrule_params} + ) + + return vals + + def sync_event_from_ical(self, ical_event): + email_regex = re.compile(r"[a-z0-9.\-+_]+@[a-z0-9.\-+_]+\.[a-z]+") + current_user_email = self.env.user.email.lower() + + for component in ical_event.subcomponents: + if isinstance(component, Event): + uid = component.get("uid") + recurrence_id = component.get( + "recurrence_id" + ) # Unique identifier for a single event in a recurrence set + attendees = component.get("attendee", []) + + if isinstance(attendees, vCalAddress): + attendees = [attendees] + elif isinstance(attendees, str): + attendees = [vCalAddress(attendees)] + + 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}") + + # Add current user email to attendees if not already present + if current_user_email not in attendees_emails: + attendees_emails.append(current_user_email) + + attendee_ids = self.env["res.partner"].search( + [("email", "in", attendees_emails)] + ) + + existing_instance = self._get_existing_instance(uid, recurrence_id) + start = component.decoded("dtstart") + if isinstance(start, datetime): + start = start.astimezone(utc).replace(tzinfo=None) + end = component.decoded("dtend") + if isinstance(end, datetime): + end = end.astimezone(utc).replace(tzinfo=None) + values = { + "name": str(component.get("summary")), + "start": start, + "stop": end, + "description": self._extract_component_text( + component, "description" + ), + "location": self._extract_component_text(component, "location"), + "videocall_location": self._extract_component_text( + component, "conference" + ), + "caldav_uid": uid, + "partner_ids": [(6, 0, attendee_ids.ids)], + } + recurrency_vals = self._get_recurrency_values_from_ical_event(component) + if recurrency_vals: + values.update(recurrency_vals) + if not existing_instance: + _logger.info(f"Creating with vals: {values}") + self.with_context(caldav_no_sync=True).create(values) + else: + _logger.info(f"Updating with vals: {values}") + changed_vals = {} + # Don't update partner_ids if no change + if attendee_ids - existing_instance.partner_ids: + changed_vals.update( + { + "partner_ids", + values.pop("partner_ids"), + } + ) + + # Don't write values that haven't changed + for key, val in values.items(): + if getattr(existing_instance, key) != val: + changed_vals.update({key: values.get(key)}) + if ( + recurrency_vals + and recurrency_vals.get("recurrency") + and ( + not existing_instance.recurrency + or not existing_instance.follow_recurrence + ) + ): + existing_instance.write( + { + "recurrency": True, + "follow_recurrence": True, + } + ) + existing_instance.with_context( + caldav_no_sync=True, + ).write(changed_vals) + + @staticmethod + def _extract_component_text(component, subcomponent_name): + text = str(component.get(subcomponent_name)) + text = text if text != "None" else "" + + @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") diff --git a/caldav_sync/models/res_users.py b/caldav_sync/models/res_users.py new file mode 100644 index 0000000..c7526f8 --- /dev/null +++ b/caldav_sync/models/res_users.py @@ -0,0 +1,17 @@ +from odoo import models, fields + +class ResUsers(models.Model): + _inherit = 'res.users' + caldav_calendar_url = fields.Char(string='CalDAV Calendar URL') + caldav_username = fields.Char(string='CalDAV Username') + caldav_password = fields.Char(string='CalDAV Password', password=True) + + @property + def SELF_WRITEABLE_FIELDS(self): + return super().SELF_WRITEABLE_FIELDS+ ["caldav_calendar_url", + "caldav_username", + "caldav_password",] + + def is_caldav_enabled(self): + self.ensure_one() + return bool(self.caldav_calendar_url and self.caldav_username and self.caldav_password) \ No newline at end of file diff --git a/caldav_sync/requirements.txt b/caldav_sync/requirements.txt new file mode 100644 index 0000000..895ba39 --- /dev/null +++ b/caldav_sync/requirements.txt @@ -0,0 +1,3 @@ +caldav==1.3.9 +icalendar==5.0.13 +bs4==0.0.2 diff --git a/caldav_sync/static/description/icon.png b/caldav_sync/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2da875737494dcfa766535745a5dd54e01121db5 GIT binary patch literal 20840 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 DgCXao literal 0 HcmV?d00001 diff --git a/caldav_sync/static/description/images/main_screenshot.png b/caldav_sync/static/description/images/main_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..fe41c3a49c25274be6906f342b40681c55949f1f GIT binary patch literal 55221 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 literal 0 HcmV?d00001 diff --git a/caldav_sync/static/description/index.html b/caldav_sync/static/description/index.html new file mode 100644 index 0000000..a09ded5 --- /dev/null +++ b/caldav_sync/static/description/index.html @@ -0,0 +1,72 @@ + + + + + + CalDAV Synchronization + + + + +

+

CalDAV Synchronization

+ Main Screenshot +

Author: Bemade Inc. (Marc Durepos)

+

Website: www.bemade.org

+

License: GNU Lesser General Public License (LGPL-3)

+

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.

+

Features

+
    +
  • Synchronize Odoo calendar events with CalDAV servers.
  • +
  • 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

+
    +
  1. Install the module in Odoo.
  2. +
  3. Go to the User settings in Odoo.
  4. +
  5. + Enter the CalDAV calendar URL, username, and password on the user settings. + In some cases, the calendar URL to use is not evident. For Apple iCloud + calendars, for example, we recommend starting with the base server url + at https://caldav.icloud.com/ and then selecting a specific calendar url + from the error given in the server logs. +
  6. +
+

Note: Some calendar service providers such as + Apple iCloud require app-specific passwords to be set so that + the calendar API can bypass 2-factor authentication. Please look + into setting up app-specific passwords for your cloud calendar + provider if you're faced with authentication errors.

+

Usage

+
    +
  1. Create a calendar event in Odoo and it will be synchronized with the CalDAV calendar.
  2. +
  3. Update the event in Odoo and the changes will reflect on the CalDAV server.
  4. +
  5. Delete the event in Odoo and it will be removed from the CalDAV server.
  6. +
  7. Changes made to the calendar on the CalDAV server will be polled and updated in Odoo.
  8. +
+

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 by triggering the scheduled action in Odoo.

+
+ + + + + diff --git a/caldav_sync/tests/__init__.py b/caldav_sync/tests/__init__.py new file mode 100644 index 0000000..7b0bb1f --- /dev/null +++ b/caldav_sync/tests/__init__.py @@ -0,0 +1 @@ +from . import test_caldav_sync diff --git a/caldav_sync/tests/test_caldav_sync.py b/caldav_sync/tests/test_caldav_sync.py new file mode 100644 index 0000000..15cb617 --- /dev/null +++ b/caldav_sync/tests/test_caldav_sync.py @@ -0,0 +1,167 @@ +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): + + def setUp(self): + super(TestCaldavSync, self).setUp() + self.user = self.env["res.users"].create( + { + "name": "Test User", + "login": "testuser", + "caldav_calendar_url": "http://test.calendar.url", + "caldav_username": "testuser", + "caldav_password": "password", + } + ) + self.env = self.env(context=dict(self.env.context, no_reset_password=True)) + + @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_calendar = MagicMock() + + def add_event_side_effect(ical_event): + caldav_event = MagicMock() + cal = Calendar.from_ical(ical_event) + ical_event_instance = next(iter(cal.subcomponents)) + caldav_event.vobject_instance.vevent.uid.value = ical_event_instance["UID"] + return caldav_event + + mock_calendar.add_event.side_effect = add_event_side_effect + mock_get_caldav_client.return_value = mock_client + mock_client.calendar.return_value = mock_calendar + + event_data = { + "name": "Test Event", + "start": "2024-05-22 10:00:00", + "stop": "2024-05-22 11:00:00", + "description": "This is a test event", + "location": "Test Location", + } + + event = self.env["calendar.event"].with_user(self.user).create(event_data) + + cal = Calendar.from_ical(mock_calendar.add_event.call_args[0][0]) + ical_event = next(iter(cal.subcomponents)) + + self.assertEqual(str(ical_event.get("summary")), event_data["name"]) + self.assertEqual(str(ical_event.get("location")), event_data["location"]) + self.assertEqual(ical_event.get("description"), event_data["description"]) + self.assertIsNotNone(event.caldav_uid) + self.assertEqual(event.caldav_uid, ical_event["UID"]) + + @patch( + "odoo.addons.caldav_sync.models.calendar_event.CalendarEvent._get_caldav_client" + ) + @patch( + "odoo.addons.caldav_sync.models.calendar_event.CalendarEvent.sync_update_to_caldav" + ) + def test_update_caldav_event( + self, mock_sync_update_to_caldav, mock_get_caldav_client + ): + mock_client = MagicMock() + mock_calendar = MagicMock() + mock_event = MagicMock() + mock_event.id = "test-uid-12345" + + mock_client.calendar.return_value = mock_calendar + mock_calendar.add_event.return_value = mock_event + mock_get_caldav_client.return_value = mock_client + + event = ( + self.env["calendar.event"] + .with_user(self.user) + .create( + { + "name": "Test Event", + "start": "2024-05-22 10:00:00", + "stop": "2024-05-22 11:00:00", + "description": "This is a test event", + "location": "Test Location", + "create_uid": self.user.id, + } + ) + ) + + event.with_user(self.user).write( + { + "name": "Updated Test Event", + "start": "2024-05-22 12:00:00", + "stop": "2024-05-22 13:00:00", + } + ) + mock_sync_update_to_caldav.assert_called_once() + + self.assertEqual(event.name, "Updated Test Event") + self.assertEqual(event.start, datetime(2024, 5, 22, 12, 0)) + + @patch( + "odoo.addons.caldav_sync.models.calendar_event.CalendarEvent._get_caldav_client" + ) + def test_delete_caldav_event(self, mock_get_caldav_client): + mock_client = MagicMock() + mock_calendar = MagicMock() + mock_event = MagicMock() + + mock_client.calendar.return_value = mock_calendar + mock_calendar.object_by_uid.return_value = mock_event + mock_get_caldav_client.return_value = mock_client + + event = ( + self.env["calendar.event"] + .with_user(self.user) + .create( + { + "name": "Test Event", + "start": "2024-05-22 10:00:00", + "stop": "2024-05-22 11:00:00", + "description": "This is a test event", + "location": "Test Location", + "create_uid": self.user.id, + } + ) + ) + uid = event.caldav_uid + event.with_user(self.user).unlink() + + mock_calendar.object_by_uid.assert_called_once_with(uid) + mock_event.delete.assert_called_once() + + @patch( + "odoo.addons.caldav_sync.models.calendar_event.CalendarEvent.sync_event_from_ical" + ) + def test_poll_caldav_server(self, mock_sync_event_from_ical): + mock_sync_event_from_ical.return_value = None + with patch("caldav.DAVClient") as MockClient: + mock_client = MockClient.return_value + 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(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") + cal.add_component(event) + + # Set the mock event's icalendar_instance to the iCal string + mock_event.icalendar_instance = Calendar.from_ical(cal.to_ical()) + mock_calendar.events.return_value = [mock_event] + + self.env["calendar.event"].poll_caldav_server() + mock_sync_event_from_ical.assert_called_once() diff --git a/caldav_sync/views/res_users_views.xml b/caldav_sync/views/res_users_views.xml new file mode 100644 index 0000000..f082fd8 --- /dev/null +++ b/caldav_sync/views/res_users_views.xml @@ -0,0 +1,35 @@ + + + + res.users.form.inherit + res.users + + + + + + + + + + + + + + + res.users.form.simple.modif.inherit + res.users + + + + + + + + + + + + + +