radicale_odoo/radicale_odoo_storage/__init__.py

150 lines
5.9 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2018 Fabien Bourgeois <fabien@yaltik.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" Odoo Radicale Storage Plugin """
from json import dumps
import threading
from contextlib import contextmanager
from odoorpc import ODOO
from odoorpc.error import RPCError
from radicale.storage import BaseCollection
class Collection(BaseCollection):
""" BaseCollection implementation for Odoo Radicale Storage """
odoo = False
def __init__(self, path):
""" Init function """
if not self.odoo:
self.__class__.odoo_connect()
# self.odoo_init()
attributes = path.strip('/').split('/')
self.tag = None
self.props = {}
if 'odoo-contact' in attributes:
self.tag = 'VADDRESSBOOK'
self.content_suffix = '.vcf'
self.props.update({'tag': 'VADDRESSBOOK',
'D:displayname': 'Odoo contacts',
'CR:addressbook-description': 'Contacts form your Odoo account'})
elif 'odoo-calendar' in attributes:
self.tag = 'VCALENDAR'
self.content_suffix = '.ics'
self.props.update({'tag': 'VCALENDAR',
'D:displayname': 'Odoo calendar',
'C:calendar-description': 'Events form your Odoo calendar'})
self.path = path.strip('/')
self.owner = attributes[0]
self.is_principal = len(attributes) == 0
@classmethod
def odoo_connect(cls):
""" Global Odoo connection : server and admin account """
host = cls.configuration.get('storage', 'odoo_host', fallback='127.0.0.1')
port = cls.configuration.get('storage', 'odoo_port', fallback=8069)
admin = cls.configuration.get('storage', 'odoo_admin_username')
password = cls.configuration.get('storage', 'odoo_admin_password')
database = cls.configuration.get('storage', 'odoo_database')
try:
cls.odoo = ODOO(host, port=port)
except RPCError as rpcerr:
cls.logger.error(rpcerr)
raise RuntimeError(rpcerr)
try:
cls.odoo.login(database, admin, password)
cls.logger.info('Login successfull for {} on database {}'.format(admin, database))
except RPCError as rpcerr:
cls.logger.error('Login problem for {} on database {}'.format(cls, database))
cls.logger.error(rpcerr)
raise RuntimeError(rpcerr)
return True
def odoo_init(self):
""" Init Odoo collections if not found """
# TODO: disallow collection deletion ?
user_ids = self.odoo.env['res.users'].search([])
users = self.odoo.execute('res.users', 'read', user_ids, ['login', 'email'])
for user in users:
principal_path = user.get('login')
self.logger.debug('Check collections from Odoo for %s' % principal_path)
contact_path = '%s/odoo-contact' % principal_path
calendar_path = '%s/odoo-calendar' % principal_path
collections = self.discover(principal_path, depth='1')
paths = [coll.path for coll in collections]
if contact_path not in paths:
props = {'tag': 'VADDRESSBOOK',
'D:displayname': 'Odoo contacts',
'C:calendar-description': 'Contacts form your Odoo account'}
self.create_collection(contact_path, props=props)
self.logger.info('Collection creation for Odoo Sync : %s' % contact_path)
if calendar_path not in paths:
props = {'tag': 'VCALENDAR',
'D:displayname': 'Odoo calendar',
'C:calendar-description': 'Events form your Odoo calendar'}
self.create_collection(calendar_path, props=props)
self.logger.info('Collection creation for Odoo Sync : %s' % calendar_path)
@classmethod
@contextmanager
def acquire_lock(cls, mode, user=None):
cls.user = user
yield
@classmethod
def discover(cls, path, depth="0"):
"""Discover a list of collections under the given ``path``.
``path`` is sanitized.
If ``depth`` is "0", only the actual object under ``path`` is
returned.
If ``depth`` is anything but "0", it is considered as "1" and direct
children are included in the result.
The root collection "/" must always exist.
"""
attributes = path.strip('/').split('/') or []
if path and not cls.user:
cls.user = attributes[0]
cls.logger.warning('Discover : %s (path), %s (depth), %s (cls.user), %s (attributes)' %
(path, depth, cls.user, attributes))
yield cls(path)
contact_path = '%s/odoo-contact' % path
calendar_path = '%s/odoo-calendar' % path
yield cls(contact_path)
yield cls(calendar_path)
def get_meta(self, key=None):
"""Get metadata value for collection """
if key == 'tag':
return self.tag
return self.props.get(key)
@classmethod
def create_collection(cls, href, collection=None, props=None):
""" Create collection implementation : only warns ATM """
cls.logger.error('Attemmpt to create a new collection for %s' % href)