mirror of
https://github.com/bringout/oca-ocb-security.git
synced 2026-04-21 21:52:05 +02:00
19.0 vanilla
This commit is contained in:
parent
20ddc1b4a3
commit
c0efcc53f5
1162 changed files with 125577 additions and 105287 deletions
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
|
@ -7,10 +6,12 @@ from functools import wraps
|
|||
from requests import HTTPError
|
||||
import pytz
|
||||
from dateutil.parser import parse
|
||||
from markupsafe import Markup
|
||||
|
||||
from odoo import api, fields, models, registry, _
|
||||
from odoo.tools import ormcache_context, email_normalize
|
||||
from odoo.osv import expression
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.fields import Domain
|
||||
from odoo.modules.registry import Registry
|
||||
from odoo.tools import email_normalize
|
||||
from odoo.sql_db import BaseCursor
|
||||
|
||||
from odoo.addons.google_calendar.utils.google_event import GoogleEvent
|
||||
|
|
@ -37,7 +38,7 @@ def after_commit(func):
|
|||
|
||||
@self.env.cr.postcommit.add
|
||||
def called_after():
|
||||
db_registry = registry(dbname)
|
||||
db_registry = Registry(dbname)
|
||||
with db_registry.cursor() as cr:
|
||||
env = api.Environment(cr, uid, context)
|
||||
try:
|
||||
|
|
@ -53,43 +54,45 @@ def google_calendar_token(user):
|
|||
yield user._get_google_calendar_token()
|
||||
|
||||
|
||||
class GoogleSync(models.AbstractModel):
|
||||
class GoogleCalendarSync(models.AbstractModel):
|
||||
_name = 'google.calendar.sync'
|
||||
_description = "Synchronize a record with Google Calendar"
|
||||
|
||||
google_id = fields.Char('Google Calendar Id', copy=False)
|
||||
google_id = fields.Char('Google Calendar Id', index='btree_not_null', copy=False)
|
||||
need_sync = fields.Boolean(default=True, copy=False)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
def write(self, vals):
|
||||
google_service = GoogleCalendarService(self.env['google.service'])
|
||||
if 'google_id' in vals:
|
||||
self._event_ids_from_google_ids.clear_cache(self)
|
||||
synced_fields = self._get_google_synced_fields()
|
||||
if 'need_sync' not in vals and vals.keys() & synced_fields and not self.env.user.google_synchronization_stopped:
|
||||
vals['need_sync'] = True
|
||||
|
||||
result = super().write(vals)
|
||||
for record in self.filtered('need_sync'):
|
||||
if record.google_id:
|
||||
record.with_user(record._get_event_user())._google_patch(google_service, record.google_id, record._google_values(), timeout=3)
|
||||
if self.env.user._get_google_sync_status() != "sync_paused":
|
||||
for record in self:
|
||||
if record.need_sync and record.google_id:
|
||||
record.with_user(record._get_event_user())._google_patch(google_service, record.google_id, record._google_values(), timeout=3)
|
||||
|
||||
return result
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
if any(vals.get('google_id') for vals in vals_list):
|
||||
self._event_ids_from_google_ids.clear_cache(self)
|
||||
if self.env.user.google_synchronization_stopped:
|
||||
for vals in vals_list:
|
||||
user_ids = {v['user_id'] for v in vals_list if v.get('user_id')}
|
||||
users_with_sync = self.env['res.users'].browse(user_ids).filtered(lambda u: not u.sudo().google_synchronization_stopped)
|
||||
users_with_sync_set = set(users_with_sync.ids)
|
||||
|
||||
for vals in vals_list:
|
||||
if vals.get('user_id', False) and vals['user_id'] not in users_with_sync_set:
|
||||
vals.update({'need_sync': False})
|
||||
records = super().create(vals_list)
|
||||
self._handle_allday_recurrences_edge_case(records, vals_list)
|
||||
|
||||
google_service = GoogleCalendarService(self.env['google.service'])
|
||||
records_to_sync = records.filtered(lambda r: r.need_sync and r.active)
|
||||
for record in records_to_sync:
|
||||
record.with_user(record._get_event_user())._google_insert(google_service, record._google_values(), timeout=3)
|
||||
if self.env.user._get_google_sync_status() != "sync_paused":
|
||||
for record in records:
|
||||
if record.need_sync and record.active:
|
||||
record.with_user(record._get_event_user())._google_insert(google_service, record._google_values(), timeout=3)
|
||||
return records
|
||||
|
||||
def _handle_allday_recurrences_edge_case(self, records, vals_list):
|
||||
|
|
@ -124,12 +127,7 @@ class GoogleSync(models.AbstractModel):
|
|||
def _from_google_ids(self, google_ids):
|
||||
if not google_ids:
|
||||
return self.browse()
|
||||
return self.browse(self._event_ids_from_google_ids(google_ids))
|
||||
|
||||
@api.model
|
||||
@ormcache_context('google_ids', keys=('active_test',))
|
||||
def _event_ids_from_google_ids(self, google_ids):
|
||||
return self.search([('google_id', 'in', google_ids)]).ids
|
||||
return self.search([('google_id', 'in', google_ids)])
|
||||
|
||||
def _sync_odoo2google(self, google_service: GoogleCalendarService):
|
||||
if not self:
|
||||
|
|
@ -142,36 +140,42 @@ class GoogleSync(models.AbstractModel):
|
|||
|
||||
updated_records = records_to_sync.filtered('google_id')
|
||||
new_records = records_to_sync - updated_records
|
||||
for record in cancelled_records.filtered(lambda e: e.google_id and e.need_sync):
|
||||
record.with_user(record._get_event_user())._google_delete(google_service, record.google_id)
|
||||
for record in new_records:
|
||||
record.with_user(record._get_event_user())._google_insert(google_service, record._google_values())
|
||||
for record in updated_records:
|
||||
record.with_user(record._get_event_user())._google_patch(google_service, record.google_id, record._google_values())
|
||||
if self.env.user._get_google_sync_status() != "sync_paused":
|
||||
for record in cancelled_records:
|
||||
if record.google_id and record.need_sync:
|
||||
record.with_user(record._get_event_user())._google_delete(google_service, record.google_id)
|
||||
for record in new_records:
|
||||
if record._is_google_insertion_blocked(sender_user=self.env.user):
|
||||
continue
|
||||
record.with_user(record._get_event_user())._google_insert(google_service, record._google_values())
|
||||
for record in updated_records:
|
||||
record.with_user(record._get_event_user())._google_patch(google_service, record.google_id, record._google_values())
|
||||
|
||||
def _cancel(self):
|
||||
self.with_context(dont_notify=True).write({'google_id': False})
|
||||
self.unlink()
|
||||
|
||||
@api.model
|
||||
def _sync_google2odoo(self, google_events: GoogleEvent, default_reminders=()):
|
||||
def _sync_google2odoo(self, google_events: GoogleEvent, write_dates=None, default_reminders=()):
|
||||
"""Synchronize Google recurrences in Odoo. Creates new recurrences, updates
|
||||
existing ones.
|
||||
|
||||
:param google_recurrences: Google recurrences to synchronize in Odoo
|
||||
:param google_events: Google recurrences to synchronize in Odoo
|
||||
:param write_dates: A dictionary mapping Odoo record IDs to their write dates.
|
||||
:param default_reminders:
|
||||
:return: synchronized odoo recurrences
|
||||
"""
|
||||
write_dates = dict(write_dates or {})
|
||||
existing = google_events.exists(self.env)
|
||||
new = google_events - existing - google_events.cancelled()
|
||||
write_dates = self._context.get('write_dates', {})
|
||||
|
||||
odoo_values = [
|
||||
dict(self._odoo_values(e, default_reminders), need_sync=False)
|
||||
for e in new
|
||||
]
|
||||
new_odoo = self.with_context(dont_notify=True)._create_from_google(new, odoo_values)
|
||||
new_odoo = self.with_context(dont_notify=True, skip_contact_description=True)._create_from_google(new, odoo_values)
|
||||
cancelled = existing.cancelled()
|
||||
cancelled_odoo = self.browse(cancelled.odoo_ids(self.env)).exists()
|
||||
cancelled_odoo = self.browse(cancelled.odoo_ids(self.env))
|
||||
|
||||
# Check if it is a recurring event that has been rescheduled.
|
||||
# We have to check if an event already exists in Odoo.
|
||||
|
|
@ -184,7 +188,7 @@ class GoogleSync(models.AbstractModel):
|
|||
google_ids_to_remove = [event.full_recurring_event_id() for event in rescheduled_events]
|
||||
cancelled_odoo += self.env['calendar.event'].search([('google_id', 'in', google_ids_to_remove)])
|
||||
|
||||
cancelled_odoo._cancel()
|
||||
cancelled_odoo.exists()._cancel()
|
||||
synced_records = new_odoo + cancelled_odoo
|
||||
pending = existing - cancelled
|
||||
pending_odoo = self.browse(pending.odoo_ids(self.env)).exists()
|
||||
|
|
@ -244,10 +248,9 @@ class GoogleSync(models.AbstractModel):
|
|||
'reason': reason}
|
||||
_logger.warning(error_log)
|
||||
|
||||
body = _(
|
||||
"The following event could not be synced with Google Calendar. </br>"
|
||||
"It will not be synced as long at it is not updated.</br>"
|
||||
"%(reason)s", reason=reason)
|
||||
body = _("The following event could not be synced with Google Calendar.") + Markup("<br/>") + \
|
||||
_("It will not be synced as long at it is not updated.") + Markup("<br/>") + \
|
||||
reason
|
||||
|
||||
if event:
|
||||
event.message_post(
|
||||
|
|
@ -260,7 +263,7 @@ class GoogleSync(models.AbstractModel):
|
|||
def _google_delete(self, google_service: GoogleCalendarService, google_id, timeout=TIMEOUT):
|
||||
with google_calendar_token(self.env.user.sudo()) as token:
|
||||
if token:
|
||||
is_recurrence = self._context.get('is_recurrence', False)
|
||||
is_recurrence = self.env.context.get('is_recurrence', False)
|
||||
google_service.google_service = google_service.google_service.with_context(is_recurrence=is_recurrence)
|
||||
google_service.delete(google_id, token=token, timeout=timeout)
|
||||
# When the record has been deleted on our side, we need to delete it on google but we don't want
|
||||
|
|
@ -272,6 +275,8 @@ class GoogleSync(models.AbstractModel):
|
|||
with google_calendar_token(self.env.user.sudo()) as token:
|
||||
if token:
|
||||
try:
|
||||
send_updates = not self._is_event_over()
|
||||
google_service.google_service = google_service.google_service.with_context(send_updates=send_updates)
|
||||
google_service.patch(google_id, values, token=token, timeout=timeout)
|
||||
except HTTPError as e:
|
||||
if e.response.status_code in (400, 403):
|
||||
|
|
@ -279,6 +284,21 @@ class GoogleSync(models.AbstractModel):
|
|||
if values:
|
||||
self.exists().with_context(dont_notify=True).need_sync = False
|
||||
|
||||
def _get_post_sync_values(self, request_values, google_values):
|
||||
""" Return the values to be written in the event right after its insertion in Google side. """
|
||||
writeable_values = {
|
||||
'google_id': request_values['id'],
|
||||
'need_sync': False,
|
||||
}
|
||||
return writeable_values
|
||||
|
||||
def _need_video_call(self):
|
||||
""" Implement this method to return True if the event needs a video call
|
||||
:return: bool
|
||||
"""
|
||||
self.ensure_one()
|
||||
return True
|
||||
|
||||
@after_commit
|
||||
def _google_insert(self, google_service: GoogleCalendarService, values, timeout=TIMEOUT):
|
||||
if not values:
|
||||
|
|
@ -286,14 +306,10 @@ class GoogleSync(models.AbstractModel):
|
|||
with google_calendar_token(self.env.user.sudo()) as token:
|
||||
if token:
|
||||
try:
|
||||
send_updates = self._context.get('send_updates', True)
|
||||
send_updates = self.env.context.get('send_updates', True) and not self._is_event_over()
|
||||
google_service.google_service = google_service.google_service.with_context(send_updates=send_updates)
|
||||
google_id = google_service.insert(values, token=token, timeout=timeout)
|
||||
# Everything went smoothly
|
||||
self.with_context(dont_notify=True).write({
|
||||
'google_id': google_id,
|
||||
'need_sync': False,
|
||||
})
|
||||
google_values = google_service.insert(values, token=token, timeout=timeout, need_video_call=self._need_video_call())
|
||||
self.with_context(dont_notify=True).write(self._get_post_sync_values(values, google_values))
|
||||
except HTTPError as e:
|
||||
if e.response.status_code in (400, 403):
|
||||
self._google_error_handling(e)
|
||||
|
|
@ -307,18 +323,21 @@ class GoogleSync(models.AbstractModel):
|
|||
"""
|
||||
domain = self._get_sync_domain()
|
||||
if not full_sync:
|
||||
is_active_clause = (self._active_name, '=', True) if self._active_name else expression.TRUE_LEAF
|
||||
domain = expression.AND([domain, [
|
||||
'|',
|
||||
'&', ('google_id', '=', False), is_active_clause,
|
||||
('need_sync', '=', True),
|
||||
]])
|
||||
is_active_clause = Domain(self._active_name, '=', True) if self._active_name else Domain.TRUE
|
||||
domain &= (Domain('google_id', '=', False) & is_active_clause) | Domain('need_sync', '=', True)
|
||||
# We want to limit to 200 event sync per transaction, it shouldn't be a problem for the day to day
|
||||
# but it allows to run the first synchro within an acceptable time without timeout.
|
||||
# If there is a lot of event to synchronize to google the first time,
|
||||
# they will be synchronized eventually with the cron running few times a day
|
||||
return self.with_context(active_test=False).search(domain, limit=200)
|
||||
|
||||
def _check_any_records_to_sync(self):
|
||||
""" Returns True if there are pending records to be synchronized from Odoo to Google, False otherwise. """
|
||||
is_active_clause = Domain(self._active_name, '=', True) if self._active_name else Domain.TRUE
|
||||
domain = self._get_sync_domain()
|
||||
domain &= (Domain('google_id', '=', False) & is_active_clause) | Domain('need_sync', '=', True)
|
||||
return self.search_count(domain, limit=1) > 0
|
||||
|
||||
def _write_from_google(self, gevent, vals):
|
||||
self.write(vals)
|
||||
|
||||
|
|
@ -329,17 +348,10 @@ class GoogleSync(models.AbstractModel):
|
|||
@api.model
|
||||
def _get_sync_partner(self, emails):
|
||||
normalized_emails = [email_normalize(contact) for contact in emails if email_normalize(contact)]
|
||||
user_partners = self.env['mail.thread']._mail_search_on_user(normalized_emails, extra_domain=[('share', '=', False)])
|
||||
partners = [user_partner for user_partner in user_partners if user_partner.type != 'private']
|
||||
remaining = [email for email in normalized_emails if
|
||||
email not in [partner.email_normalized for partner in partners]]
|
||||
if remaining:
|
||||
partners += self.env['mail.thread']._mail_find_partner_from_emails(remaining, records=self, force_create=True, extra_domain=[('type', '!=', 'private')])
|
||||
unsorted_partners = self.env['res.partner'].browse([p.id for p in partners if p.id])
|
||||
partners = self.env['mail.thread']._partner_find_from_emails_single(normalized_emails)
|
||||
# partners needs to be sorted according to the emails order provided by google
|
||||
k = {value: idx for idx, value in enumerate(emails)}
|
||||
result = unsorted_partners.sorted(key=lambda p: k.get(p.email_normalized, -1))
|
||||
return result
|
||||
return partners.sorted(key=lambda p: k.get(p.email_normalized, -1))
|
||||
|
||||
@api.model
|
||||
def _odoo_values(self, google_event: GoogleEvent, default_reminders=()):
|
||||
|
|
@ -382,3 +394,12 @@ class GoogleSync(models.AbstractModel):
|
|||
the appropriate user accordingly.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _is_google_insertion_blocked(self, sender_user):
|
||||
"""
|
||||
Returns True if the record insertion to Google should be blocked.
|
||||
This is a necessary step for ensuring data match between Odoo and Google,
|
||||
as it avoids that events have permanently the wrong organizer in Google
|
||||
by not synchronizing records through owner and not through the attendees.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue