Sélectionner une révision Git
Bifurcation depuis
Le Filament / Confédération Générale des SCOP / cgscop_partner
Le projet source a une visibilité limitée.
res_users.py 9,31 Kio
# Copyright 2021 Le Filament (https://le-filament.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
import logging
from netbluemind.python.client import BMClient, ServerFault
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.loglevels import exception_to_unicode
_logger = logging.getLogger(__name__)
class ResUser(models.Model):
_inherit = "res.users"
"""
This inheriting class adds some fields and methods to res.users in order to store
information for connecting user to Bluemind :
* bluemind_login / bluemind_password
* is_bm_connection_ok : boolean set when connection was successful
* bluemind_user_id : to store the identifier of the user in Bluemind
* bluemind_calendar_id : to store the main calendar id linked to user
* last_sync_version : last version of calendar synced
(Bluemind increments a versionning sequence at each change on calendar)
"""
bluemind_login = fields.Char("Bluemind e-mail")
bluemind_password = fields.Char("Bluemind password or API key")
is_bm_connection_ok = fields.Boolean("Bluemind Connection OK", default=False)
bluemind_user_id = fields.Char(
"Bluemind user ID", compute="_compute_bluemind_user_id", store=True
)
bluemind_calendar_id = fields.Char(
"Bluemind calendar ID", compute="_compute_bluemind_calendar_uid", store=True
)
last_sync_version = fields.Integer("Last sync version", readonly=True, default=0)
@api.depends("company_id.bluemind_url", "bluemind_login", "bluemind_password")
def bluemind_auth(self, force=False):
"""
This method connect to Bluemind API and returns the client object
This authentication is only performed if all the above conditions are met:
* bluemind_url is set on company
* bluemind_login and bluemind_password are set on user
* connection was already successful with provided creds or force
parameter is given (allowing to make first auth) - this last condition
is here to avoid locking the account in case the creds are incorrect,
since this method is used in most other methods to retrieve client object
"""
self.ensure_one()
if (
self.company_id.bluemind_url
and self.bluemind_login
and self.bluemind_password
and (self.is_bm_connection_ok or force)
):
try:
client = BMClient(self.company_id.bluemind_url + "/api")
client.login(self.bluemind_login, self.bluemind_password)
self.is_bm_connection_ok = True
return client
# TODO: better manage exception
except (ServerFault, Exception) as e:
self.is_bm_connection_ok = False
if force:
raise UserError(
_(
"Something went wrong during connection : [%s]",
exception_to_unicode(e),
)
)
return False
def force_auth(self):
"""
This methods force authentication (or re-auth) mechanism with creds stored on user
It is called from button on user view
"""
# TODO : add notification that it works or not
return self.bluemind_auth(force=True)
@api.depends("company_id.bluemind_domain", "bluemind_login", "bluemind_password", "is_bm_connection_ok")
def _compute_bluemind_user_id(self):
"""
This method retrieves the user id from Bluemind client object
"""
for user in self:
if (
user.company_id.bluemind_url
and user.bluemind_login
and user.bluemind_password
and user.is_bm_connection_ok
):
client = user.bluemind_auth()
if client:
user.bluemind_user_id = (
client
.directory(user.company_id.bluemind_domain)
.getByEmail(user.bluemind_login)
.entryUid
)
@api.depends("bluemind_user_id")
def _compute_bluemind_calendar_uid(self):
"""
This method computes default Bluemind calendar_id for the current user
by concatenating calendar:Default: and Bluemind user_id
"""
for user in self:
if user.bluemind_user_id:
user.bluemind_calendar_id = "calendar:Default:" + user.bluemind_user_id
def sync_bluemind_calendar(self):
"""
This methods retrieves last calendar changes from Bluemind
These charges are grouped in 3 objects :
* created : all events created since last sync
* updated : all events updated since last sync
* deleted : all events deleted since last sync
Then it creates/updates/deletes Odoo events accordingly
The parti pris here is the following : every event in Bluemind calendar
is recreated in Odoo with organizer = user
"""
self.ensure_one()
# TODO: add checks and error handling
# Retrieve all events modified since last sync (followed by self.bluemind_id_version)
bm_calendar = self.bluemind_auth().calendar(self.bluemind_calendar_id)
# TODO : avoid retrieving all events from beginning, limit to 3 months in past ?
# The following function actually retrieves only Blemind event ids, not the full events
bm_changeset = bm_calendar.changeset(self.last_sync_version)
bm_created_uids = bm_changeset.created
bm_updated_uids = bm_changeset.updated
bm_deleted_uids = bm_changeset.deleted
bm_last_version = bm_changeset.version
# Retrieve all events from Odoo organized by the current user
# and with a bluemind_id (= already synced at least once with Bluemind)
Calendar = self.env["calendar.event"]
odoo_events_bm_linked = Calendar.search(
[("user_id", "=", self.id), ("bluemind_id", "!=", False)]
)
odoo_events_bm_uids = odoo_events_bm_linked.mapped("bluemind_id")
# Calendar entries created on Bluemind side, not already in Odoo
# The events already in Odoo are ignored (probably already synced previously
events_to_create = []
bm_events_to_create_uids = [
e for e in bm_created_uids if e not in odoo_events_bm_uids
]
# Retrieve remaining full events from Bluemind
bm_events_to_create = bm_calendar.multipleGet(bm_events_to_create_uids)
# Create corresponding events in Odoo
for bm_event in bm_events_to_create:
events_to_create.append(Calendar._bm_to_odoo_values(bm_event))
Calendar.create(events_to_create)
# Calendar entries that have been updated on Bluemind
# Retrieve full events from Bluemind
bm_events_to_update = bm_calendar.multipleGet(bm_updated_uids)
# Update corresponding events
for bm_event in bm_events_to_update:
odoo_event = odoo_events_bm_linked.filtered(
[("bluemind_id", "=", bm_event.uid)]
)
# If event exists in Odoo update it
if odoo_event:
odoo_event.update_odoo_event_from_bm(bm_event)
# Otherwise log an error
else:
_logger.error(
"Event %s updated in Bluemind did not exist in Odoo", bm_event.uid
)
# Calendar entries that have been deleted on Bluemind to be deleted on Odoo
odoo_events_to_delete = odoo_events_bm_linked.filtered(
[("bluemind_id", "in", bm_deleted_uids)]
)
odoo_events_to_delete.unlink(from_bluemind=True)
# Update user last_sync_version with the latest one retrieved from Bluemind
self.last_sync_version = bm_last_version
# TODO : sync all Odoo events without bluemind_id
# Should we sync only events on which user_id is the current user
# Or also all events to which he was invited as participant ?
# First we would sync only the ones where he is user_id
# (because otherwise you would set bluemind_id on an event which
# does not belong to the current user, and may have already been synced
# - with different bluemind_id by the organizer (user_id))
odoo_events_no_bm = Calendar.search(
[("user_id", "=", self.id), ("bluemind_id", "=", False)]
)
odoo_event_no_bm.create_odoo_events_in_bm()
@api.model
def _sync_all_bm_calendar(self):
"""
Cron job to sync calendars for all users with is_bm_connection_ok
"""
users = self.env["res.users"].search([("is_bm_connection_ok", "!=", False)])
for user in users:
_logger.info("Calendar Synchro - Starting synchronization for %s", user)
try:
user.with_user(user).sudo().sync_bluemind_calendar()
_logger.info(
"Calendar Synchro - Completed synchronization for %s", user
)
except Exception as e:
_logger.exception(
"[%s] Calendar Synchro - Exception : %s !",
user,
exception_to_unicode(e),
)