From 72e7ebe7692f6198075c03c572f931c7b1806b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phan=20Sainl=C3=A9ger?= Date: Mon, 8 Aug 2022 14:15:44 +0200 Subject: [PATCH] [ADD] create dav_calendar_sync addon creates new addon dav_calendar_sync to synchronize caldav events on Odoo calendar --- dav_account/models/dav_server.py | 2 +- dav_account/views/dav_server.xml | 3 + dav_calendar_sync/README.rst | 7 ++ dav_calendar_sync/__init__.py | 5 + dav_calendar_sync/__manifest__.py | 91 +++++++++++++++++++ dav_calendar_sync/controllers/__init__.py | 1 + dav_calendar_sync/i18n/README | 1 + dav_calendar_sync/models/__init__.py | 4 + dav_calendar_sync/models/calendar_event.py | 8 ++ dav_calendar_sync/models/dav_calendar.py | 83 +++++++++++++++++ dav_calendar_sync/models/dav_server.py | 19 ++++ dav_calendar_sync/models/res_users.py | 7 ++ .../security/ir.model.access.csv | 2 + dav_calendar_sync/views/dav_calendar.xml | 44 +++++++++ dav_calendar_sync/views/dav_server.xml | 13 +++ dav_calendar_sync/wizard/__init__.py | 0 16 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 dav_calendar_sync/README.rst create mode 100644 dav_calendar_sync/__init__.py create mode 100644 dav_calendar_sync/__manifest__.py create mode 100644 dav_calendar_sync/controllers/__init__.py create mode 100644 dav_calendar_sync/i18n/README create mode 100644 dav_calendar_sync/models/__init__.py create mode 100644 dav_calendar_sync/models/calendar_event.py create mode 100644 dav_calendar_sync/models/dav_calendar.py create mode 100644 dav_calendar_sync/models/dav_server.py create mode 100644 dav_calendar_sync/models/res_users.py create mode 100644 dav_calendar_sync/security/ir.model.access.csv create mode 100644 dav_calendar_sync/views/dav_calendar.xml create mode 100644 dav_calendar_sync/views/dav_server.xml create mode 100644 dav_calendar_sync/wizard/__init__.py diff --git a/dav_account/models/dav_server.py b/dav_account/models/dav_server.py index 02e18a7..aaff6eb 100644 --- a/dav_account/models/dav_server.py +++ b/dav_account/models/dav_server.py @@ -14,7 +14,7 @@ class DavServer(models.Model): status = fields.Char(compute='_compute_status', string='Status', store=True) @api.depends("url", "username", "password") - def _compute_status(self): + def compute_status(self): try: self.get_principal() self.write({"status": "OK"}) diff --git a/dav_account/views/dav_server.xml b/dav_account/views/dav_server.xml index 4100bfb..3ce2dc4 100644 --- a/dav_account/views/dav_server.xml +++ b/dav_account/views/dav_server.xml @@ -19,6 +19,9 @@ dav.server
+
+
diff --git a/dav_calendar_sync/README.rst b/dav_calendar_sync/README.rst new file mode 100644 index 0000000..f604018 --- /dev/null +++ b/dav_calendar_sync/README.rst @@ -0,0 +1,7 @@ +===================== +dav_calendar_sync +===================== + +This module adds dav calendar synchronization + +This is an Odoo addon. diff --git a/dav_calendar_sync/__init__.py b/dav_calendar_sync/__init__.py new file mode 100644 index 0000000..9fa4f20 --- /dev/null +++ b/dav_calendar_sync/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from . import models +# from . import controllers +# from . import wizard diff --git a/dav_calendar_sync/__manifest__.py b/dav_calendar_sync/__manifest__.py new file mode 100644 index 0000000..47eb8eb --- /dev/null +++ b/dav_calendar_sync/__manifest__.py @@ -0,0 +1,91 @@ +# Copyright 2022 Stéphan Sainléger (Elabore) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "dav_calendar_sync", + "version": "14.0", + "author": "Elabore", + "website": "https://elabore.coop", + "maintainer": "Stéphan Sainléger", + "license": "AGPL-3", + "category": "Tools", + "summary": "This module adds dav calendar synchronization", + "description": """ + :image: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +=============== +dav_calendar_sync +=============== + +This module adds dav calendar synchronization + +Installation +============ + +Use Odoo normal module installation procedure to install +``dav_calendar_sync``. + +Known issues / Roadmap +====================== + +None yet. +Bug Tracker +=========== + +Bugs are tracked on `our issues website `_. In case of +trouble, please check there if your issue has already been +reported. If you spotted it first, help us smashing it by providing a +detailed and welcomed feedback. + +Credits +======= + +Contributors +------------ + +* Stéphan Sainléger + +Funders +------- + +The development of this module has been financially supported by: +* Elabore (https://elabore.coop) + + +Maintainer +---------- + +This module is maintained by Elabore. + +""", + # any module necessary for this one to work correctly + "depends": [ + "base", + "calendar", + ], + "qweb": [ + # "static/src/xml/*.xml", + ], + "external_dependencies": { + "python": [], + }, + # always loaded + "data": [ + # "security/security.xml", + "security/ir.model.access.csv", + "views/dav_server.xml", + "views/dav_calendar.xml", + # "views/menu.xml", + # "data/data.xml", + ], + # only loaded in demonstration mode + "demo": [], + "js": [], + "css": [], + "installable": True, + # Install this module automatically if all dependency have been previously + # and independently installed. Used for synergetic or glue modules. + "auto_install": False, + "application": False, +} \ No newline at end of file diff --git a/dav_calendar_sync/controllers/__init__.py b/dav_calendar_sync/controllers/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/dav_calendar_sync/controllers/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/dav_calendar_sync/i18n/README b/dav_calendar_sync/i18n/README new file mode 100644 index 0000000..62197a1 --- /dev/null +++ b/dav_calendar_sync/i18n/README @@ -0,0 +1 @@ +This directory should contain the *.po for Odoo translation. diff --git a/dav_calendar_sync/models/__init__.py b/dav_calendar_sync/models/__init__.py new file mode 100644 index 0000000..c73f816 --- /dev/null +++ b/dav_calendar_sync/models/__init__.py @@ -0,0 +1,4 @@ +from . import dav_server +from . import dav_calendar +from . import res_users +from . import calendar_event \ No newline at end of file diff --git a/dav_calendar_sync/models/calendar_event.py b/dav_calendar_sync/models/calendar_event.py new file mode 100644 index 0000000..e5969f1 --- /dev/null +++ b/dav_calendar_sync/models/calendar_event.py @@ -0,0 +1,8 @@ +from odoo import _, api, fields, models + + +class Meeting(models.Model): + _inherit = 'calendar.event' + + dav_calendar_id = fields.Many2one('dav.calendar', string='Dav Calendar') + dav_uid = fields.Char('Dav Uid', index=True) \ No newline at end of file diff --git a/dav_calendar_sync/models/dav_calendar.py b/dav_calendar_sync/models/dav_calendar.py new file mode 100644 index 0000000..f87c59a --- /dev/null +++ b/dav_calendar_sync/models/dav_calendar.py @@ -0,0 +1,83 @@ +import logging +from odoo import fields, models + +_logger = logging.getLogger(__name__) + +class DavCalendar(models.Model): + _name = "dav.calendar" + _description = "Dav calendar" + + name = fields.Char('name') + url = fields.Char('url') + dav_server_id = fields.Many2one('dav.server', string='Dav Server') + + sync_token = fields.Char("Synchronization Token") + + active_sync_event = fields.Boolean('active_sync_event') + active_sync_todo = fields.Boolean('active_sync_todo') + + def _manage_timezones(self, ical_event): + start = ical_event.get("DTSTART").dt + end = ical_event.get("DTEND").dt + # Odoo only uses naive dates, timezone data must be removed + # TODO: removing timezone data is not enough, naive dates must match the user timezone + if hasattr(start, "tzinfo"): + start = start.replace(tzinfo=None) + if hasattr(end, "tzinfo"): + end = start.replace(tzinfo=None) + return start, end + + def _existing_event(self,ical_event): + calendar_events = self.env["calendar.event"].search([("dav_uid", "=", str(ical_event.get("UID")))]) + if len(calendar_events) > 0: + return True + else: + return False + + def _create_calendar_event(self, ical_event): + start, end = self._manage_timezones(ical_event) + values = { + "name":str(ical_event.get("SUMMARY")), + "start": start, + "stop": end, + "dav_calendar_id": self.id, + "dav_uid" : str(ical_event.get("UID")), + } + self.env["calendar.event"].create(values) + + def _update_calendar_event(self, ical_event): + calendar_event = self.env["calendar.event"].search([("dav_uid", "=", str(ical_event.get("UID")))])[0] + start, end = self._manage_timezones(ical_event) + values = { + "name":str(ical_event.get("SUMMARY")), + "start": start, + "stop": end, + } + calendar_event.write(values) + + def _delete_calendar_event(self, ical_event): + calendar_event = self.env["calendar.event"].search([("dav_uid", "=", str(ical_event.get("UID")))])[0] + calendar_event.unlink() + + def sync_dav_events(self): + calendar = self.dav_server_id.get_principal().calendar(self.name) + if self.sync_token: + dav_events = calendar.objects_by_sync_token(self.sync_token, True) + else: + dav_events = calendar.objects_by_sync_token(load_objects=True) + event_created = 0 + for dav_event in dav_events: + try: + ical_event = dav_event.icalendar_instance.subcomponents[0] + if dav_event.data is None: + self._delete_calendar_event(ical_event) + elif self._existing_event(ical_event): + self._update_calendar_event(ical_event) + else: + self._create_calendar_event(ical_event) + event_created += 1 + except Exception as e: + _logger.exception("SYNC FAILURE on following event: %s", dav_event.data) + continue + self.sync_token = dav_events.sync_token + _logger.debug("NB EVENT CREATED: %s", event_created) \ No newline at end of file diff --git a/dav_calendar_sync/models/dav_server.py b/dav_calendar_sync/models/dav_server.py new file mode 100644 index 0000000..e196e06 --- /dev/null +++ b/dav_calendar_sync/models/dav_server.py @@ -0,0 +1,19 @@ +import caldav +from odoo import _, api, fields, models + + +class DavServer(models.Model): + _inherit = "dav.server" + + dav_calendar_ids = fields.One2many('dav.calendar', 'dav_server_id', string='Dav Calendars') + + def compute_dav_calendar_ids(self): + calendars = self.get_principal().calendars() + for calendar in calendars: + values = { + "name": calendar.name, + "url": calendar.url, + "dav_server_id": self.id, + } + self.env["dav.calendar"].create(values) + diff --git a/dav_calendar_sync/models/res_users.py b/dav_calendar_sync/models/res_users.py new file mode 100644 index 0000000..3811a23 --- /dev/null +++ b/dav_calendar_sync/models/res_users.py @@ -0,0 +1,7 @@ +from odoo import _, api, fields, models + +class Users(models.Model): + _inherit="res.users" + + calendar_event_ids = fields.Many2many('dav.calendar', 'dav_calendar_event_user_rel', string='Dav Calendars Events') + calendar_todo_ids = fields.Many2many('dav.calendar', 'dav_calendar_todo_user_rel', string='Dav Calendars Todo') \ No newline at end of file diff --git a/dav_calendar_sync/security/ir.model.access.csv b/dav_calendar_sync/security/ir.model.access.csv new file mode 100644 index 0000000..b6a33eb --- /dev/null +++ b/dav_calendar_sync/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +access_dav_calendar_user,access_dav_calendar_user,model_dav_calendar,,1,1,1,1 \ No newline at end of file diff --git a/dav_calendar_sync/views/dav_calendar.xml b/dav_calendar_sync/views/dav_calendar.xml new file mode 100644 index 0000000..fabc1d0 --- /dev/null +++ b/dav_calendar_sync/views/dav_calendar.xml @@ -0,0 +1,44 @@ + + + + + dav.calendar.view.tree + dav.calendar + + + + + + + + + + + dav.calendar.view.form + dav.calendar + + +
+
+ + + + + + + + + +
+
+ + + Dav Calendars + dav.calendar + tree,form + + + + +
\ No newline at end of file diff --git a/dav_calendar_sync/views/dav_server.xml b/dav_calendar_sync/views/dav_server.xml new file mode 100644 index 0000000..8b909ca --- /dev/null +++ b/dav_calendar_sync/views/dav_server.xml @@ -0,0 +1,13 @@ + + + + dav.server.view.form.inherit.calendar + dav.server + + +
+
+
+
+
\ No newline at end of file diff --git a/dav_calendar_sync/wizard/__init__.py b/dav_calendar_sync/wizard/__init__.py new file mode 100644 index 0000000..e69de29