[REF] calendar: Lint + Clean code
This commit is contained in:
committed by
Lucas Lefèvre
parent
f2b6918649
commit
1a9be3a869
@@ -11,15 +11,20 @@ from odoo.tools.misc import get_lang
|
||||
|
||||
class CalendarController(http.Controller):
|
||||
|
||||
# YTI Note: Keep id and kwargs only for retrocompatibility purpose
|
||||
@http.route('/calendar/meeting/accept', type='http', auth="calendar")
|
||||
def accept(self, token, action, id, **kwargs):
|
||||
attendee = request.env['calendar.attendee'].search([('access_token', '=', token), ('state', '!=', 'accepted')])
|
||||
def accept_meeting(self, token, id, **kwargs):
|
||||
attendee = request.env['calendar.attendee'].search([
|
||||
('access_token', '=', token),
|
||||
('state', '!=', 'accepted')])
|
||||
attendee.do_accept()
|
||||
return self.view(token, action, id, view='form')
|
||||
return self.view_meeting(token, id)
|
||||
|
||||
@http.route('/calendar/recurrence/accept', type='http', auth="calendar")
|
||||
def accept_recurrence(self, token, action, id, **kwargs):
|
||||
attendee = request.env['calendar.attendee'].sudo().search([('access_token', '=', token), ('state', '!=', 'accepted')])
|
||||
def accept_recurrence(self, token, id, **kwargs):
|
||||
attendee = request.env['calendar.attendee'].sudo().search([
|
||||
('access_token', '=', token),
|
||||
('state', '!=', 'accepted')])
|
||||
if attendee:
|
||||
attendees = request.env['calendar.attendee'].sudo().search([
|
||||
('event_id', 'in', attendee.event_id.recurrence_id.calendar_event_ids.ids),
|
||||
@@ -27,11 +32,21 @@ class CalendarController(http.Controller):
|
||||
('state', '!=', 'accepted'),
|
||||
])
|
||||
attendees.do_accept()
|
||||
return self.view(token, action, id, view='form')
|
||||
return self.view_meeting(token, id)
|
||||
|
||||
@http.route('/calendar/meeting/decline', type='http', auth="calendar")
|
||||
def decline_meeting(self, token, id, **kwargs):
|
||||
attendee = request.env['calendar.attendee'].search([
|
||||
('access_token', '=', token),
|
||||
('state', '!=', 'declined')])
|
||||
attendee.do_decline()
|
||||
return self.view_meeting(token, id)
|
||||
|
||||
@http.route('/calendar/recurrence/decline', type='http', auth="calendar")
|
||||
def decline_recurrence(self, token, action, id, **kwargs):
|
||||
attendee = request.env['calendar.attendee'].sudo().search([('access_token', '=', token), ('state', '!=', 'declined')])
|
||||
def decline_recurrence(self, token, id, **kwargs):
|
||||
attendee = request.env['calendar.attendee'].sudo().search([
|
||||
('access_token', '=', token),
|
||||
('state', '!=', 'declined')])
|
||||
if attendee:
|
||||
attendees = request.env['calendar.attendee'].sudo().search([
|
||||
('event_id', 'in', attendee.event_id.recurrence_id.calendar_event_ids.ids),
|
||||
@@ -39,17 +54,13 @@ class CalendarController(http.Controller):
|
||||
('state', '!=', 'declined'),
|
||||
])
|
||||
attendees.do_decline()
|
||||
return self.view(token, action, id, view='form')
|
||||
|
||||
@http.route('/calendar/meeting/decline', type='http', auth="calendar")
|
||||
def decline(self, token, action, id, **kwargs):
|
||||
attendee = request.env['calendar.attendee'].search([('access_token', '=', token), ('state', '!=', 'declined')])
|
||||
attendee.do_decline()
|
||||
return self.view(token, action, id, view='form')
|
||||
return self.view_meeting(token, id)
|
||||
|
||||
@http.route('/calendar/meeting/view', type='http', auth="calendar")
|
||||
def view(self, token, action, id, view='calendar'):
|
||||
attendee = request.env['calendar.attendee'].search([('access_token', '=', token), ('event_id', '=', int(id))])
|
||||
def view_meeting(self, token, id, **kwargs):
|
||||
attendee = request.env['calendar.attendee'].search([
|
||||
('access_token', '=', token),
|
||||
('event_id', '=', int(id))])
|
||||
if not attendee:
|
||||
return request.not_found()
|
||||
timezone = attendee.partner_id.tz
|
||||
@@ -78,5 +89,5 @@ class CalendarController(http.Controller):
|
||||
return request.env['calendar.alarm_manager'].get_next_notif()
|
||||
|
||||
@http.route('/calendar/notify_ack', type='json', auth="user")
|
||||
def notify_ack(self, type=''):
|
||||
def notify_ack(self):
|
||||
return request.env['res.partner'].sudo()._set_calendar_last_notif_ack()
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
</p>
|
||||
<div style="text-align: center; margin: 16px 0px 16px 0px;">
|
||||
% set target = 'recurrence' if recurrent else 'meeting'
|
||||
<a href="/calendar/${target}/accept?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}"
|
||||
<a href="/calendar/${target}/accept?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
Accept</a>
|
||||
<a href="/calendar/${target}/decline?db=${'dbname' in ctx and ctx['dbname'] or '' }&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}"
|
||||
<a href="/calendar/${target}/decline?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
Decline</a>
|
||||
<a href="/calendar/meeting/view?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}"
|
||||
<a href="/calendar/meeting/view?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
View</a>
|
||||
</div>
|
||||
@@ -110,13 +110,13 @@
|
||||
</p>
|
||||
<div style="text-align: center; margin: 16px 0px 16px 0px;">
|
||||
% set target = 'recurrence' if recurrent else 'meeting'
|
||||
<a href="/calendar/${target}/accept?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}"
|
||||
<a href="/calendar/${target}/accept?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
Accept</a>
|
||||
<a href="/calendar/${target}/decline?db=${'dbname' in ctx and ctx['dbname'] or '' }&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}"
|
||||
<a href="/calendar/${target}/decline?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
Decline</a>
|
||||
<a href="/calendar/meeting/view?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}"
|
||||
<a href="/calendar/meeting/view?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
View</a>
|
||||
</div>
|
||||
@@ -204,13 +204,13 @@
|
||||
This is a reminder for the below event :
|
||||
</p>
|
||||
<div style="text-align: center; margin: 16px 0px 16px 0px;">
|
||||
<a href="/calendar/meeting/accept?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}"
|
||||
<a href="/calendar/meeting/accept?token=${object.access_token}&event_id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
Accept</a>
|
||||
<a href="/calendar/meeting/decline?db=${'dbname' in ctx and ctx['dbname'] or '' }&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}"
|
||||
<a href="/calendar/meeting/decline?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
Decline</a>
|
||||
<a href="/calendar/meeting/view?db=${'dbname' in ctx and ctx['dbname'] or ''}&token=${object.access_token}&action=${'action_id' in ctx and ctx['action_id'] or ''}&id=${object.event_id.id}"
|
||||
<a href="/calendar/meeting/view?token=${object.access_token}&id=${object.event_id.id}"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">
|
||||
View</a>
|
||||
</div>
|
||||
|
||||
@@ -23,13 +23,20 @@ class Alarm(models.Model):
|
||||
_interval_selection = {'minutes': 'Minutes', 'hours': 'Hours', 'days': 'Days'}
|
||||
|
||||
name = fields.Char('Name', translate=True, required=True)
|
||||
alarm_type = fields.Selection([('notification', 'Notification'), ('email', 'Email')], string='Type', required=True, default='email')
|
||||
alarm_type = fields.Selection(
|
||||
[('notification', 'Notification'), ('email', 'Email')],
|
||||
string='Type', required=True, default='email')
|
||||
duration = fields.Integer('Remind Before', required=True, default=1)
|
||||
interval = fields.Selection(list(_interval_selection.items()), 'Unit', required=True, default='hours')
|
||||
duration_minutes = fields.Integer('Duration in minutes', search='_search_duration_minutes', compute='_compute_duration_minutes', store=True, help="Duration in minutes")
|
||||
interval = fields.Selection(
|
||||
list(_interval_selection.items()), 'Unit', required=True, default='hours')
|
||||
duration_minutes = fields.Integer(
|
||||
'Duration in minutes', store=True,
|
||||
search='_search_duration_minutes', compute='_compute_duration_minutes',
|
||||
help="Duration in minutes")
|
||||
|
||||
def _search_duration_minutes(self, operator, value):
|
||||
return ['|', '|',
|
||||
return [
|
||||
'|', '|',
|
||||
'&', ('interval', '=', 'minutes'), ('duration', operator, value),
|
||||
'&', ('interval', '=', 'hours'), ('duration', operator, value / 60),
|
||||
'&', ('interval', '=', 'days'), ('duration', operator, value / 60 / 24),
|
||||
@@ -38,7 +45,9 @@ class Alarm(models.Model):
|
||||
@api.onchange('duration', 'interval', 'alarm_type')
|
||||
def _onchange_duration_interval(self):
|
||||
display_interval = self._interval_selection.get(self.interval, '')
|
||||
display_alarm_type = {key: value for key, value in self._fields['alarm_type']._description_selection(self.env)}[self.alarm_type]
|
||||
display_alarm_type = {
|
||||
key: value for key, value in self._fields['alarm_type']._description_selection(self.env)
|
||||
}[self.alarm_type]
|
||||
self.name = "%s - %s %s" % (display_alarm_type, self.duration, display_interval)
|
||||
|
||||
def _update_cron(self):
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
@@ -11,7 +10,6 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AlarmManager(models.AbstractModel):
|
||||
|
||||
_name = 'calendar.alarm_manager'
|
||||
_description = 'Event Alarm Manager'
|
||||
|
||||
@@ -77,12 +75,12 @@ class AlarmManager(models.AbstractModel):
|
||||
tuple_params += (seconds,)
|
||||
|
||||
self._cr.execute("""
|
||||
WITH calcul_delta AS (%s)
|
||||
SELECT *
|
||||
FROM ( %s WHERE cal.active = True ) AS ALL_EVENTS
|
||||
WHERE ALL_EVENTS.first_alarm < %s
|
||||
AND ALL_EVENTS.last_event_date > (now() at time zone 'utc')
|
||||
""" % (delta_request, base_request, first_alarm_max_value), tuple_params)
|
||||
WITH calcul_delta AS (%s)
|
||||
SELECT *
|
||||
FROM ( %s WHERE cal.active = True ) AS ALL_EVENTS
|
||||
WHERE ALL_EVENTS.first_alarm < %s
|
||||
AND ALL_EVENTS.last_event_date > (now() at time zone 'utc')
|
||||
""" % (delta_request, base_request, first_alarm_max_value), tuple_params)
|
||||
|
||||
for event_id, first_alarm, last_alarm, first_meeting, last_meeting, min_duration, max_duration, rule in self._cr.fetchall():
|
||||
result[event_id] = {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import uuid
|
||||
import base64
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
@@ -13,7 +12,6 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
class Attendee(models.Model):
|
||||
""" Calendar Attendee Information """
|
||||
|
||||
_name = 'calendar.attendee'
|
||||
_rec_name = 'common_name'
|
||||
_description = 'Calendar Attendee Information'
|
||||
@@ -28,13 +26,15 @@ class Attendee(models.Model):
|
||||
('accepted', 'Accepted'),
|
||||
]
|
||||
|
||||
event_id = fields.Many2one('calendar.event', 'Meeting linked', required=True, ondelete='cascade')
|
||||
event_id = fields.Many2one(
|
||||
'calendar.event', 'Meeting linked', required=True, ondelete='cascade')
|
||||
partner_id = fields.Many2one('res.partner', 'Contact', required=True, readonly=True)
|
||||
state = fields.Selection(STATE_SELECTION, string='Status', readonly=True, default='needsAction',
|
||||
help="Status of the attendee's participation")
|
||||
common_name = fields.Char('Common name', compute='_compute_common_name', store=True)
|
||||
email = fields.Char('Email', related='partner_id.email', help="Email of Invited Person")
|
||||
availability = fields.Selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly=True)
|
||||
availability = fields.Selection(
|
||||
[('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly=True)
|
||||
access_token = fields.Char('Invitation Token', default=_default_access_token)
|
||||
recurrence_id = fields.Many2one('calendar.recurrence', related='event_id.recurrence_id')
|
||||
|
||||
@@ -50,8 +50,8 @@ class Attendee(models.Model):
|
||||
values['state'] = 'accepted'
|
||||
if not values.get("email") and values.get("common_name"):
|
||||
common_nameval = values.get("common_name").split(':')
|
||||
email = [x for x in common_nameval if '@' in x] # TODO JEM : should be refactored
|
||||
values['email'] = email and email[0] or ''
|
||||
email = [x for x in common_nameval if '@' in x]
|
||||
values['email'] = email[0] if email else ''
|
||||
values['common_name'] = values.get("common_name")
|
||||
attendees = super().create(vals_list)
|
||||
attendees._subscribe_partner()
|
||||
@@ -64,7 +64,8 @@ class Attendee(models.Model):
|
||||
def _subscribe_partner(self):
|
||||
for event in self.event_id:
|
||||
partners = (event.attendee_ids & self).partner_id - event.message_partner_ids
|
||||
partners -= self.env.user.partner_id # current user is automatically added as followers, don't add it twice.
|
||||
# current user is automatically added as followers, don't add it twice.
|
||||
partners -= self.env.user.partner_id
|
||||
event.message_subscribe(partner_ids=partners.ids)
|
||||
|
||||
def _unsubscribe_partner(self):
|
||||
@@ -147,12 +148,15 @@ class Attendee(models.Model):
|
||||
def do_accept(self):
|
||||
""" Marks event invitation as Accepted. """
|
||||
for attendee in self:
|
||||
attendee.event_id.message_post(body=_("%s has accepted invitation") % (attendee.common_name), subtype_xmlid="calendar.subtype_invitation")
|
||||
attendee.event_id.message_post(
|
||||
body=_("%s has accepted invitation") % (attendee.common_name),
|
||||
subtype_xmlid="calendar.subtype_invitation")
|
||||
return self.write({'state': 'accepted'})
|
||||
|
||||
def do_decline(self):
|
||||
""" Marks event invitation as Declined. """
|
||||
for attendee in self:
|
||||
attendee.event_id.message_post(body=_("%s has declined invitation") % (attendee.common_name), subtype_xmlid="calendar.subtype_invitation")
|
||||
attendee.event_id.message_post(
|
||||
body=_("%s has declined invitation") % (attendee.common_name),
|
||||
subtype_xmlid="calendar.subtype_invitation")
|
||||
return self.write({'state': 'declined'})
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import babel.dates
|
||||
import datetime
|
||||
import math
|
||||
from datetime import timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
import math
|
||||
import babel.dates
|
||||
import logging
|
||||
import pytz
|
||||
|
||||
@@ -19,10 +17,8 @@ from odoo.tools.misc import get_lang
|
||||
from odoo.tools import pycompat
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
SORT_ALIASES = {
|
||||
'start': 'sort_start',
|
||||
'start_date': 'sort_start',
|
||||
@@ -45,12 +41,6 @@ def get_weekday_occurence(date):
|
||||
|
||||
|
||||
class Meeting(models.Model):
|
||||
""" Model for Calendar Event
|
||||
|
||||
Special context keys :
|
||||
- `no_mail_to_attendees` : disabled sending email to attendees when creating/editing a meeting
|
||||
"""
|
||||
|
||||
_name = 'calendar.event'
|
||||
_description = "Calendar Event"
|
||||
_order = "start desc"
|
||||
@@ -188,27 +178,45 @@ class Meeting(models.Model):
|
||||
|
||||
name = fields.Char('Meeting Subject', required=True)
|
||||
|
||||
attendee_status = fields.Selection(Attendee.STATE_SELECTION, string='Attendee Status', compute='_compute_attendee')
|
||||
attendee_status = fields.Selection(
|
||||
Attendee.STATE_SELECTION, string='Attendee Status', compute='_compute_attendee')
|
||||
display_time = fields.Char('Event Time', compute='_compute_display_time')
|
||||
start = fields.Datetime('Start', required=True, tracking=True, default=fields.Date.today, help="Start date of an event, without time for full days events")
|
||||
stop = fields.Datetime('Stop', required=True, tracking=True, default=fields.Date.today, compute='_compute_stop', readonly=False, store=True,
|
||||
help="Stop date of an event, without time for full days events")
|
||||
start = fields.Datetime(
|
||||
'Start', required=True, tracking=True, default=fields.Date.today,
|
||||
help="Start date of an event, without time for full days events")
|
||||
stop = fields.Datetime(
|
||||
'Stop', required=True, tracking=True, default=fields.Date.today,
|
||||
compute='_compute_stop', readonly=False, store=True,
|
||||
help="Stop date of an event, without time for full days events")
|
||||
|
||||
allday = fields.Boolean('All Day', default=False)
|
||||
start_date = fields.Date('Start Date', compute='_compute_dates', inverse='_inverse_dates', store=True, tracking=True)
|
||||
stop_date = fields.Date('End Date', compute='_compute_dates', inverse='_inverse_dates', store=True, tracking=True)
|
||||
event_tz = fields.Selection('_event_tz_get', string='Timezone', default=lambda self: self.env.context.get('tz') or self.user_id.tz)
|
||||
start_date = fields.Date(
|
||||
'Start Date', store=True, tracking=True,
|
||||
compute='_compute_dates', inverse='_inverse_dates')
|
||||
stop_date = fields.Date(
|
||||
'End Date', store=True, tracking=True,
|
||||
compute='_compute_dates', inverse='_inverse_dates')
|
||||
event_tz = fields.Selection(
|
||||
'_event_tz_get', string='Timezone',
|
||||
default=lambda self: self.env.context.get('tz') or self.user_id.tz)
|
||||
duration = fields.Float('Duration', compute='_compute_duration', store=True, readonly=False)
|
||||
description = fields.Text('Description')
|
||||
privacy = fields.Selection([('public', 'Everyone'), ('private', 'Only me'), ('confidential', 'Only internal users')], 'Privacy', default='public', required=True)
|
||||
privacy = fields.Selection(
|
||||
[('public', 'Everyone'),
|
||||
('private', 'Only me'),
|
||||
('confidential', 'Only internal users')],
|
||||
'Privacy', default='public', required=True)
|
||||
location = fields.Char('Location', tracking=True, help="Location of Event")
|
||||
show_as = fields.Selection([('free', 'Free'), ('busy', 'Busy')], 'Show Time as', default='busy', required=True)
|
||||
show_as = fields.Selection(
|
||||
[('free', 'Free'),
|
||||
('busy', 'Busy')], 'Show Time as', default='busy', required=True)
|
||||
|
||||
# linked document
|
||||
# LUL TODO use fields.Reference ?
|
||||
res_id = fields.Integer('Document ID')
|
||||
res_model_id = fields.Many2one('ir.model', 'Document Model', ondelete='cascade')
|
||||
res_model = fields.Char('Document Model Name', related='res_model_id.model', readonly=True, store=True)
|
||||
res_model = fields.Char(
|
||||
'Document Model Name', related='res_model_id.model', readonly=True, store=True)
|
||||
activity_ids = fields.One2many('mail.activity', 'calendar_event_id', string='Activities')
|
||||
|
||||
#redifine message_ids to remove autojoin to avoid search to crash in get_recurrent_ids
|
||||
@@ -235,7 +243,8 @@ class Meeting(models.Model):
|
||||
|
||||
# RECURRENCE FIELD
|
||||
recurrency = fields.Boolean('Recurrent', help="Recurrent Event")
|
||||
recurrence_id = fields.Many2one('calendar.recurrence', string="Recurrence Rule", index=True)
|
||||
recurrence_id = fields.Many2one(
|
||||
'calendar.recurrence', string="Recurrence Rule", index=True)
|
||||
recurrence_update = fields.Selection([
|
||||
('self_only', "This event"),
|
||||
('future_events', "This and following events"),
|
||||
@@ -246,16 +255,22 @@ class Meeting(models.Model):
|
||||
# Those field are pseudo-related fields of recurrence_id.
|
||||
# They can't be "real" related fields because it should work at record creation
|
||||
# when recurrence_id is not created yet.
|
||||
# If some of these fields are set and recurrence_id does not exists, a `calendar.recurrence.rule`
|
||||
# will be dynamically created.
|
||||
# If some of these fields are set and recurrence_id does not exists,
|
||||
# a `calendar.recurrence.rule` will be dynamically created.
|
||||
rrule = fields.Char('Recurrent Rule', compute='_compute_recurrence', readonly=False)
|
||||
rrule_type = fields.Selection(RRULE_TYPE_SELECTION, string='Recurrence',
|
||||
help="Let the event automatically repeat at that interval",
|
||||
compute='_compute_recurrence', readonly=False)
|
||||
event_tz = fields.Selection(_tz_get, string='Timezone', compute='_compute_recurrence', readonly=False)
|
||||
end_type = fields.Selection(END_TYPE_SELECTION, string='Recurrence Termination', compute='_compute_recurrence', readonly=False)
|
||||
interval = fields.Integer(string='Repeat Every', help="Repeat every (Days/Week/Month/Year)", compute='_compute_recurrence', readonly=False)
|
||||
count = fields.Integer(string='Repeat', help="Repeat x times", compute='_compute_recurrence', readonly=False)
|
||||
event_tz = fields.Selection(
|
||||
_tz_get, string='Timezone', compute='_compute_recurrence', readonly=False)
|
||||
end_type = fields.Selection(
|
||||
END_TYPE_SELECTION, string='Recurrence Termination',
|
||||
compute='_compute_recurrence', readonly=False)
|
||||
interval = fields.Integer(
|
||||
string='Repeat Every', compute='_compute_recurrence', readonly=False,
|
||||
help="Repeat every (Days/Week/Month/Year)")
|
||||
count = fields.Integer(
|
||||
string='Repeat', help="Repeat x times", compute='_compute_recurrence', readonly=False)
|
||||
mo = fields.Boolean('Mon', compute='_compute_recurrence', readonly=False)
|
||||
tu = fields.Boolean('Tue', compute='_compute_recurrence', readonly=False)
|
||||
we = fields.Boolean('Wed', compute='_compute_recurrence', readonly=False)
|
||||
@@ -263,7 +278,8 @@ class Meeting(models.Model):
|
||||
fr = fields.Boolean('Fri', compute='_compute_recurrence', readonly=False)
|
||||
sa = fields.Boolean('Sat', compute='_compute_recurrence', readonly=False)
|
||||
su = fields.Boolean('Sun', compute='_compute_recurrence', readonly=False)
|
||||
month_by = fields.Selection(MONTH_BY_SELECTION, string='Option', compute='_compute_recurrence', readonly=False)
|
||||
month_by = fields.Selection(
|
||||
MONTH_BY_SELECTION, string='Option', compute='_compute_recurrence', readonly=False)
|
||||
day = fields.Integer('Date of month', compute='_compute_recurrence', readonly=False)
|
||||
weekday = fields.Selection(WEEKDAY_SELECTION, compute='_compute_recurrence', readonly=False)
|
||||
byday = fields.Selection(BYDAY_SELECTION, compute='_compute_recurrence', readonly=False)
|
||||
@@ -280,8 +296,10 @@ class Meeting(models.Model):
|
||||
|
||||
@api.depends('allday', 'start', 'stop')
|
||||
def _compute_dates(self):
|
||||
""" Adapt the value of start_date(time)/stop_date(time) according to start/stop fields and allday. Also, compute
|
||||
the duration for not allday meeting ; otherwise the duration is set to zero, since the meeting last all the day.
|
||||
""" Adapt the value of start_date(time)/stop_date(time)
|
||||
according to start/stop fields and allday. Also, compute
|
||||
the duration for not allday meeting ; otherwise the
|
||||
duration is set to zero, since the meeting last all the day.
|
||||
"""
|
||||
for meeting in self:
|
||||
if meeting.allday and meeting.start and meeting.stop:
|
||||
@@ -380,8 +398,7 @@ class Meeting(models.Model):
|
||||
if idate:
|
||||
if allday:
|
||||
return idate
|
||||
else:
|
||||
return idate.replace(tzinfo=pytz.timezone('UTC'))
|
||||
return idate.replace(tzinfo=pytz.timezone('UTC'))
|
||||
return False
|
||||
|
||||
try:
|
||||
@@ -749,8 +766,6 @@ class Meeting(models.Model):
|
||||
private_fields = set(groupby) - self._get_public_fields()
|
||||
if not self.env.su and private_fields:
|
||||
raise UserError(_("Grouping by %s is not allowed." % ', '.join([self._fields[field_name].string for field_name in private_fields])))
|
||||
if 'date' in groupby:
|
||||
raise UserError(_('Group by date is not supported, use the calendar view instead.'))
|
||||
return super(Meeting, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
|
||||
|
||||
def unlink(self):
|
||||
@@ -797,7 +812,6 @@ class Meeting(models.Model):
|
||||
attendee = self.attendee_ids.filtered(lambda x: x.partner_id == self.env.user.partner_id)
|
||||
if status == 'accepted':
|
||||
return attendee.do_accept()
|
||||
elif status == 'declined':
|
||||
if status == 'declined':
|
||||
return attendee.do_decline()
|
||||
else:
|
||||
return attendee.do_tentative()
|
||||
return attendee.do_tentative()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, time
|
||||
import pytz
|
||||
|
||||
from datetime import datetime, time
|
||||
from dateutil import rrule
|
||||
from dateutil.relativedelta import relativedelta, MO
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
@@ -91,9 +91,12 @@ class RecurrenceRule(models.Model):
|
||||
_description = 'Event Recurrence Rule'
|
||||
|
||||
name = fields.Char(compute='_compute_name', store=True)
|
||||
base_event_id = fields.Many2one('calendar.event', ondelete='set null', copy=False)
|
||||
base_event_id = fields.Many2one(
|
||||
'calendar.event', ondelete='set null', copy=False) # store=False ?
|
||||
calendar_event_ids = fields.One2many('calendar.event', 'recurrence_id')
|
||||
event_tz = fields.Selection(_tz_get, string='Timezone', default=lambda self: self.env.context.get('tz') or self.env.user.tz)
|
||||
event_tz = fields.Selection(
|
||||
_tz_get, string='Timezone',
|
||||
default=lambda self: self.env.context.get('tz') or self.env.user.tz)
|
||||
rrule = fields.Char(compute='_compute_rrule', inverse='_inverse_rrule', store=True)
|
||||
dtstart = fields.Datetime(compute='_compute_dtstart')
|
||||
rrule_type = fields.Selection(RRULE_TYPE_SELECTION, default='weekly')
|
||||
@@ -132,8 +135,8 @@ class RecurrenceRule(models.Model):
|
||||
|
||||
if recurrence.rrule_type == 'weeky':
|
||||
weekdays = recurrence._get_week_days()
|
||||
fields = (self._fields[weekday_to_field(w)] for w in weekdays)
|
||||
on = _("on %s,") % ", ".join([field.string for field in fields])
|
||||
weekday_fields = (self._fields[weekday_to_field(w)] for w in weekdays)
|
||||
on = _("on %s,") % ", ".join([field.string for field in weekday_fields])
|
||||
elif recurrence.rrule_type == 'monthly':
|
||||
if recurrence.month_by == 'day':
|
||||
weekday_label = dict(BYDAY_SELECTION)[recurrence.byday]
|
||||
@@ -154,7 +157,9 @@ class RecurrenceRule(models.Model):
|
||||
for recurrence in self:
|
||||
recurrence.dtstart = start_mapping.get(recurrence.id)
|
||||
|
||||
@api.depends('byday', 'until', 'rrule_type', 'month_by', 'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'su', 'day', 'weekday')
|
||||
@api.depends(
|
||||
'byday', 'until', 'rrule_type', 'month_by', 'interval', 'count', 'end_type',
|
||||
'mo', 'tu', 'we', 'th', 'fr', 'sa', 'su', 'day', 'weekday')
|
||||
def _compute_rrule(self):
|
||||
for recurrence in self:
|
||||
recurrence.rrule = recurrence._rrule_serialize()
|
||||
@@ -168,7 +173,8 @@ class RecurrenceRule(models.Model):
|
||||
def _reconcile_events(self, ranges):
|
||||
"""
|
||||
:param ranges: iterable of tuples (datetime_start, datetime_stop)
|
||||
:return: tuple (events of the recurrence already in sync with ranges, and ranges not covered by any events)
|
||||
:return: tuple (events of the recurrence already in sync with ranges,
|
||||
and ranges not covered by any events)
|
||||
"""
|
||||
ranges = set(ranges)
|
||||
|
||||
@@ -328,16 +334,17 @@ class RecurrenceRule(models.Model):
|
||||
|
||||
def _get_start_of_period(self, dt):
|
||||
if self.rrule_type == 'daily':
|
||||
return dt
|
||||
start = dt
|
||||
elif self.rrule_type == 'weekly':
|
||||
lang = self.env['res.lang']._lang_get(self.env.user.lang)
|
||||
week_start = int(lang.week_start) # lang.week_start ranges from '1' to '7'
|
||||
week_start = rrule.weekday(week_start - 1) # expects an int from 0 to 6
|
||||
return dt + relativedelta(weekday=week_start(-1))
|
||||
start = dt + relativedelta(weekday=week_start(-1))
|
||||
elif self.rrule_type == 'monthly':
|
||||
return dt + relativedelta(day=1)
|
||||
start = dt + relativedelta(day=1)
|
||||
elif self.rrule_type == 'yearly':
|
||||
return dt
|
||||
start = dt
|
||||
return start
|
||||
|
||||
def _get_first_event(self, include_outliers=False):
|
||||
if not self.calendar_event_ids:
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from odoo import models
|
||||
from odoo.http import request
|
||||
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
|
||||
class IrHttp(models.AbstractModel):
|
||||
_inherit = 'ir.http'
|
||||
|
||||
@@ -9,7 +9,8 @@ from odoo import api, fields, models
|
||||
class Partner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
calendar_last_notif_ack = fields.Datetime('Last notification marked as read from base Calendar', default=fields.Datetime.now)
|
||||
calendar_last_notif_ack = fields.Datetime(
|
||||
'Last notification marked as read from base Calendar', default=fields.Datetime.now)
|
||||
|
||||
def get_attendee_detail(self, meeting_id):
|
||||
""" Return a list of tuple (id, name, status)
|
||||
@@ -32,6 +33,5 @@ class Partner(models.Model):
|
||||
|
||||
@api.model
|
||||
def _set_calendar_last_notif_ack(self):
|
||||
partner = self.env['res.users'].browse(self.env.context.get('uid',self.env.uid)).partner_id
|
||||
partner = self.env['res.users'].browse(self.env.context.get('uid', self.env.uid)).partner_id
|
||||
partner.write({'calendar_last_notif_ack': datetime.now()})
|
||||
return
|
||||
|
||||
@@ -23,9 +23,13 @@ class Users(models.Model):
|
||||
|
||||
return ['&', '|',
|
||||
'&',
|
||||
'|', ['start', '>=', fields.Datetime.to_string(start_dt)], ['stop', '>=', fields.Datetime.to_string(start_dt)],
|
||||
'|',
|
||||
['start', '>=', fields.Datetime.to_string(start_dt)],
|
||||
['stop', '>=', fields.Datetime.to_string(start_dt)],
|
||||
['start', '<=', fields.Datetime.to_string(end_dt)],
|
||||
'&', ['allday', '=', True], ['start_date', '=', fields.Date.to_string(start_date)],
|
||||
'&',
|
||||
['allday', '=', True],
|
||||
['start_date', '=', fields.Date.to_string(start_date)],
|
||||
('attendee_ids.partner_id', '=', self.env.user.partner_id.id)]
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -5,21 +5,21 @@
|
||||
<record id="calendar_event_rule_my" model="ir.rule">
|
||||
<field name="name">Own events</field>
|
||||
<field ref="model_calendar_event" name="model_id"/>
|
||||
<field name="domain_force">[('partner_ids','in',user.partner_id.id)]</field>
|
||||
<field name="domain_force">[('partner_ids', 'in', user.partner_id.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="calendar_event_rule_employee" model="ir.rule">
|
||||
<field ref="model_calendar_event" name="model_id"/>
|
||||
<field name="name">All Calendar Event for employees</field>
|
||||
<field name="domain_force">[(1,'=',1)]</field>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field eval="[(4,ref('base.group_user'))]" name="groups"/>
|
||||
</record>
|
||||
|
||||
<record id="calendar_attendee_rule_my" model="ir.rule">
|
||||
<field name="name">Own attendees</field>
|
||||
<field ref="model_calendar_attendee" name="model_id"/>
|
||||
<field name="domain_force">[(1,'=',1)]</field>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ var WebClient = require('web.WebClient');
|
||||
|
||||
var FieldMany2ManyTags = relationalFields.FieldMany2ManyTags;
|
||||
|
||||
|
||||
var CalendarNotification = Notification.extend({
|
||||
template: "CalendarNotification",
|
||||
xmlDependencies: (Notification.prototype.xmlDependencies || [])
|
||||
|
||||
@@ -22,7 +22,7 @@ odoo.define('calendar.CalendarController', function (require) {
|
||||
},
|
||||
}],
|
||||
}).open();
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
// TODO factorize duplicated code
|
||||
|
||||
@@ -15,10 +15,7 @@ odoo.define('calendar.CalendarModel', function (require) {
|
||||
'recurrence_update': event.recurrenceUpdate,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
return CalendarModel;
|
||||
|
||||
});
|
||||
|
||||
@@ -251,7 +251,7 @@ class TestCalendar(SavepointCaseWithUserDemo):
|
||||
Check that mail are NOT sent to the attendees when detaching a recurring event
|
||||
"""
|
||||
|
||||
def _test_one_mail_per_attendee(self, m, partners):
|
||||
def _test_one_mail_per_attendee(self, partners):
|
||||
# check that every attendee receive a (single) mail for the event
|
||||
for partner in partners:
|
||||
mail = self.env['mail.message'].sudo().search([
|
||||
@@ -260,9 +260,9 @@ class TestCalendar(SavepointCaseWithUserDemo):
|
||||
self.assertEqual(len(mail), 1)
|
||||
|
||||
partners = [
|
||||
self.env['res.partner'].create({'name':'testuser0','email': u'bob@example.com'}),
|
||||
self.env['res.partner'].create({'name':'testuser1','email': u'alice@example.com'}),
|
||||
]
|
||||
self.env['res.partner'].create({'name': 'testuser0', 'email': u'bob@example.com'}),
|
||||
self.env['res.partner'].create({'name': 'testuser1', 'email': u'alice@example.com'}),
|
||||
]
|
||||
partner_ids = [(6, False, [p.id for p in partners]),]
|
||||
now = fields.Datetime.now()
|
||||
m = self.CalendarEvent.create({
|
||||
@@ -276,13 +276,13 @@ class TestCalendar(SavepointCaseWithUserDemo):
|
||||
})
|
||||
|
||||
# every partner should have 1 mail sent
|
||||
_test_one_mail_per_attendee(self, m, partners)
|
||||
_test_one_mail_per_attendee(self, partners)
|
||||
|
||||
# adding more partners to the event
|
||||
partners.extend([
|
||||
self.env['res.partner'].create({'name':'testuser2','email': u'marc@example.com'}),
|
||||
self.env['res.partner'].create({'name':'testuser3','email': u'carl@example.com'}),
|
||||
self.env['res.partner'].create({'name':'testuser4','email': u'alain@example.com'}),
|
||||
self.env['res.partner'].create({'name': 'testuser2', 'email': u'marc@example.com'}),
|
||||
self.env['res.partner'].create({'name': 'testuser3', 'email': u'carl@example.com'}),
|
||||
self.env['res.partner'].create({'name': 'testuser4', 'email': u'alain@example.com'}),
|
||||
])
|
||||
partner_ids = [(6, False, [p.id for p in partners]),]
|
||||
m.write({
|
||||
@@ -291,4 +291,4 @@ class TestCalendar(SavepointCaseWithUserDemo):
|
||||
})
|
||||
|
||||
# more email should be sent
|
||||
_test_one_mail_per_attendee(self, m, partners)
|
||||
_test_one_mail_per_attendee(self, partners)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
||||
|
||||
|
||||
@@ -8,10 +8,7 @@ import logging
|
||||
import requests
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo import api, fields, models, registry, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.http import request
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -113,7 +110,7 @@ class GoogleService(models.AbstractModel):
|
||||
'redirect_uri': base_url + '/google_account/authentication'
|
||||
}
|
||||
try:
|
||||
dummy, response, dummy = self._do_request(GOOGLE_TOKEN_ENDPOINT, params=data, headers=headers, type='POST', preuri='')
|
||||
dummy, response, dummy = self._do_request(GOOGLE_TOKEN_ENDPOINT, params=data, headers=headers, method='POST', preuri='')
|
||||
access_token = response.get('access_token')
|
||||
refresh_token = response.get('refresh_token')
|
||||
ttl = response.get('expires_in')
|
||||
@@ -122,14 +119,13 @@ class GoogleService(models.AbstractModel):
|
||||
error_msg = _("Something went wrong during your token generation. Maybe your Authorization Code is invalid")
|
||||
raise self.env['res.config.settings'].get_config_warning(error_msg)
|
||||
|
||||
# TODO JEM : remove preuri param, and rename type into method
|
||||
@api.model
|
||||
def _do_request(self, uri, params=None, headers=None, type='POST', preuri="https://www.googleapis.com", timeout=TIMEOUT):
|
||||
def _do_request(self, uri, params=None, headers=None, method='POST', preuri="https://www.googleapis.com", timeout=TIMEOUT):
|
||||
""" Execute the request to Google API. Return a tuple ('HTTP_CODE', 'HTTP_RESPONSE')
|
||||
:param uri : the url to contact
|
||||
:param params : dict or already encoded parameters for the request to make
|
||||
:param headers : headers of request
|
||||
:param type : the method to use to make the request
|
||||
:param method : the method to use to make the request
|
||||
:param preuri : pre url to prepend to param uri.
|
||||
"""
|
||||
if params is None:
|
||||
@@ -137,16 +133,16 @@ class GoogleService(models.AbstractModel):
|
||||
if headers is None:
|
||||
headers = {}
|
||||
|
||||
_logger.debug("Uri: %s - Type : %s - Headers: %s - Params : %s !", (uri, type, headers, params))
|
||||
_logger.debug("Uri: %s - Type : %s - Headers: %s - Params : %s !", (uri, method, headers, params))
|
||||
|
||||
ask_time = fields.Datetime.now()
|
||||
try:
|
||||
if type.upper() in ('GET', 'DELETE'):
|
||||
res = requests.request(type.lower(), preuri + uri, params=params, timeout=timeout)
|
||||
elif type.upper() in ('POST', 'PATCH', 'PUT'):
|
||||
res = requests.request(type.lower(), preuri + uri, data=params, headers=headers, timeout=timeout)
|
||||
if method.upper() in ('GET', 'DELETE'):
|
||||
res = requests.request(method.lower(), preuri + uri, params=params, timeout=timeout)
|
||||
elif method.upper() in ('POST', 'PATCH', 'PUT'):
|
||||
res = requests.request(method.lower(), preuri + uri, data=params, headers=headers, timeout=timeout)
|
||||
else:
|
||||
raise Exception(_('Method not supported [%s] not in [GET, POST, PUT, PATCH or DELETE]!') % (type))
|
||||
raise Exception(_('Method not supported [%s] not in [GET, POST, PUT, PATCH or DELETE]!') % (method))
|
||||
res.raise_for_status()
|
||||
status = res.status_code
|
||||
|
||||
@@ -160,8 +156,6 @@ class GoogleService(models.AbstractModel):
|
||||
except:
|
||||
pass
|
||||
except requests.HTTPError as error:
|
||||
# if error.response.status_code == 401:
|
||||
# raise InvalidToken
|
||||
if error.response.status_code in (204, 404):
|
||||
status = error.response.status_code
|
||||
response = ""
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
Google Calendar
|
||||
---------------
|
||||
|
||||
Get your meetings, your leaves... Get your calendar anywhere and never
|
||||
forget an event, fully synchronized with the Odoo <a href="https://www.odoo.com/page/crm">Open Source CRM</a>.
|
||||
|
||||
Keep an eye on your events
|
||||
--------------------------
|
||||
|
||||
See easily the purpose of the meeting, the start time and also the
|
||||
attendee(s)... All that without click on anything...
|
||||
|
||||
Create so easily an event
|
||||
-------------------------
|
||||
|
||||
In just one click you can create an event... You can drag and drop your event
|
||||
if you want moved it to another timing. You can shrink or extend the event if
|
||||
you need to change the start's hours or the duration of your meeting.
|
||||
|
||||
Create recurrent event
|
||||
----------------------
|
||||
|
||||
You can also create recurrent events with only one event. You need to create
|
||||
an event each monday of the week ? With only one it's possible, you could
|
||||
specify the recurrence and if one of this event is moved, or deleted, it's not
|
||||
a problem, you can untie your event from the others recurrences.
|
||||
|
||||
See all events you wants
|
||||
------------------------
|
||||
|
||||
See in your calendar, the event from others peoples where your are attendee,
|
||||
but also their events by simply adding your favorites coworkers. Every
|
||||
coworker will have their own color in your calendar, and every attendee will
|
||||
have their avatar in the event...
|
||||
|
||||
Get an email
|
||||
------------
|
||||
|
||||
You will receive an email at creation of an event where you are attendee, but
|
||||
also when this event is updated for some fields as date start, ...
|
||||
|
||||
Be notified
|
||||
-----------
|
||||
|
||||
You can ask to have a alarm of type 'notification' in your Odoo. You will have
|
||||
a notification in you Odoo which ever the page you are.
|
||||
|
||||
Google Calendar
|
||||
---------------
|
||||
|
||||
With this plugin, you can synchronize your Odoo calendar with Google Calendar.
|
||||
|
||||
@@ -10,9 +10,12 @@ class GoogleCalendarController(http.Controller):
|
||||
|
||||
@http.route('/google_calendar/sync_data', type='json', auth='user')
|
||||
def sync_data(self, model, **kw):
|
||||
""" This route/function is called when we want to synchronize Odoo calendar with Google Calendar
|
||||
Function return a dictionary with the status : need_config_from_admin, need_auth, need_refresh, success if not calendar_event
|
||||
The dictionary may contains an url, to allow Odoo Client to redirect user on this URL for authorization for example
|
||||
""" This route/function is called when we want to synchronize Odoo
|
||||
calendar with Google Calendar.
|
||||
Function return a dictionary with the status : need_config_from_admin, need_auth,
|
||||
need_refresh, success if not calendar_event
|
||||
The dictionary may contains an url, to allow Odoo Client to redirect user on
|
||||
this URL for authorization for example
|
||||
"""
|
||||
if model == 'calendar.event':
|
||||
GoogleCal = GoogleCalendarService(request.env['google.service'])
|
||||
|
||||
@@ -12,7 +12,8 @@ class Meeting(models.Model):
|
||||
_name = 'calendar.event'
|
||||
_inherit = ['calendar.event', 'google.calendar.sync']
|
||||
|
||||
google_id = fields.Char('Google Calendar Event Id', compute='_compute_google_id', store=True, readonly=False)
|
||||
google_id = fields.Char(
|
||||
'Google Calendar Event Id', compute='_compute_google_id', store=True, readonly=False)
|
||||
|
||||
@api.depends('recurrence_id.google_id')
|
||||
def _compute_google_id(self):
|
||||
@@ -65,9 +66,6 @@ class Meeting(models.Model):
|
||||
'recurrency': google_event.is_recurrent()
|
||||
}
|
||||
|
||||
if google_event.reminders and google_event.reminders.get('useDefault'):
|
||||
values
|
||||
|
||||
if not google_event.is_recurrence():
|
||||
values['google_id'] = google_event.id
|
||||
if google_event.start.get('dateTime'):
|
||||
@@ -91,7 +89,9 @@ class Meeting(models.Model):
|
||||
emails = [a.get('email') for a in google_event.attendees]
|
||||
existing_attendees = self.env['calendar.attendee']
|
||||
if google_event.exists(self.env):
|
||||
existing_attendees = self.env['calendar.attendee'].search([('event_id', '=', google_event.odoo_id(self.env)), ('email', 'in', emails)])
|
||||
existing_attendees = self.env['calendar.attendee'].search([
|
||||
('event_id', '=', google_event.odoo_id(self.env)),
|
||||
('email', 'in', emails)])
|
||||
attendees_by_emails = {a.email: a for a in existing_attendees}
|
||||
for attendee in google_event.attendees:
|
||||
email = attendee.get('email')
|
||||
|
||||
@@ -8,7 +8,6 @@ from odoo import api, fields, models
|
||||
from addons.google_calendar.utils.google_calendar import GoogleCalendarService
|
||||
|
||||
|
||||
|
||||
class RecurrenceRule(models.Model):
|
||||
_name = 'calendar.recurrence'
|
||||
_inherit = ['calendar.recurrence', 'google.calendar.sync']
|
||||
@@ -121,4 +120,4 @@ class RecurrenceRule(models.Model):
|
||||
'%s_odoo_id' % self.env.cr.dbname: self.id,
|
||||
},
|
||||
}
|
||||
return values
|
||||
return values
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
from contextlib import contextmanager
|
||||
from functools import wraps
|
||||
import requests
|
||||
import pytz
|
||||
from functools import wraps
|
||||
from dateutil.parser import parse
|
||||
from contextlib import contextmanager
|
||||
|
||||
from odoo import api, fields, models, registry, _
|
||||
from odoo.tools import ormcache_context
|
||||
@@ -52,7 +53,7 @@ def google_calendar_token(user):
|
||||
# Rollback manually first to avoid concurrent access errors/deadlocks.
|
||||
user.env.cr.rollback()
|
||||
with user.pool.cursor() as cr:
|
||||
env= user.env(cr=cr)
|
||||
env = user.env(cr=cr)
|
||||
user.with_env(env)._set_auth_tokens(False, False, 0)
|
||||
raise e
|
||||
|
||||
@@ -155,7 +156,7 @@ class GoogleSync(models.AbstractModel):
|
||||
cancelled_odoo._cancel()
|
||||
|
||||
synced_records = new_odoo + cancelled_odoo
|
||||
for gevent in existing - cancelled :
|
||||
for gevent in existing - cancelled:
|
||||
# Last updated wins.
|
||||
# This could be dangerous if google server time and odoo server time are different
|
||||
updated = parse(gevent.updated)
|
||||
@@ -169,7 +170,6 @@ class GoogleSync(models.AbstractModel):
|
||||
|
||||
@after_commit
|
||||
def _google_delete(self, google_service: GoogleCalendarService, google_id, timeout=TIMEOUT):
|
||||
print('DELETE', google_id)
|
||||
with google_calendar_token(self.env.user.sudo()) as token:
|
||||
if token:
|
||||
google_service.delete(google_id, token=token, timeout=timeout)
|
||||
@@ -177,7 +177,6 @@ class GoogleSync(models.AbstractModel):
|
||||
|
||||
@after_commit
|
||||
def _google_patch(self, google_service: GoogleCalendarService, google_id, values, timeout=TIMEOUT):
|
||||
print('Patch', google_id)
|
||||
with google_calendar_token(self.env.user.sudo()) as token:
|
||||
if token:
|
||||
google_service.patch(google_id, values, token=token, timeout=timeout)
|
||||
@@ -185,7 +184,6 @@ class GoogleSync(models.AbstractModel):
|
||||
|
||||
@after_commit
|
||||
def _google_insert(self, google_service: GoogleCalendarService, values, timeout=TIMEOUT):
|
||||
print('insert')
|
||||
if not values:
|
||||
return
|
||||
with google_calendar_token(self.env.user.sudo()) as token:
|
||||
|
||||
@@ -62,7 +62,7 @@ class User(models.Model):
|
||||
}
|
||||
|
||||
try:
|
||||
dummy, response, dummy = self.env['google.service']._do_request(GOOGLE_TOKEN_ENDPOINT, params=data, headers=headers, type='POST', preuri='')
|
||||
dummy, response, dummy = self.env['google.service']._do_request(GOOGLE_TOKEN_ENDPOINT, params=data, headers=headers, method='POST', preuri='')
|
||||
ttl = response.get('expires_in')
|
||||
self.write({
|
||||
'google_calendar_token': response.get('access_token'),
|
||||
|
||||
@@ -262,7 +262,7 @@ class TestSyncGoogle2Odoo(SavepointCase):
|
||||
'reminders': {'useDefault': True},
|
||||
'updated': self.now,
|
||||
},
|
||||
{ # Third event has been moved
|
||||
{ # Third event has been moved
|
||||
'id': '%s_20200113' % recurrence_id,
|
||||
'summary': 'Pricing new update',
|
||||
'start': {'date': '2020-01-18'},
|
||||
@@ -308,4 +308,3 @@ class TestSyncGoogle2Odoo(SavepointCase):
|
||||
self.assertEqual(events[1].start_date, date(2020, 1, 15))
|
||||
self.assertEqual(events[0].google_id, '%s_20200108' % recurrence_id)
|
||||
self.assertEqual(events[1].google_id, '%s_20200115' % recurrence_id)
|
||||
|
||||
|
||||
@@ -184,7 +184,6 @@ class TestSyncOdoo2Google(SavepointCase):
|
||||
'recurrency': True,
|
||||
'rrule': 'FREQ=WEEKLY;COUNT=2;BYDAY=WE',
|
||||
})
|
||||
event.recurrence_id
|
||||
to_delete = self.env['calendar.event'].with_context(active_test=False).search([('google_id', '=', google_id)])
|
||||
self.assertTrue(to_delete)
|
||||
self.assertFalse(to_delete.active)
|
||||
|
||||
@@ -37,25 +37,20 @@ class GoogleCalendarService():
|
||||
if sync_token:
|
||||
params['syncToken'] = sync_token
|
||||
try:
|
||||
status, data, time = self.google_service._do_request(url, params, headers, type='GET', timeout=timeout)
|
||||
status, data, time = self.google_service._do_request(url, params, headers, method='GET', timeout=timeout)
|
||||
except requests.HTTPError as e:
|
||||
if e.response.status_code == 410 and 'fullSyncRequired' in str(e.response.content):
|
||||
raise InvalidSyncToken("Invalid sync token. Full sync required")
|
||||
raise e
|
||||
|
||||
import pprint
|
||||
pprint.pprint(data)
|
||||
|
||||
events = data.get('items', [])
|
||||
next_page_token = data.get('nextPageToken')
|
||||
while next_page_token:
|
||||
params = {'access_token': token, 'pageToken': next_page_token}
|
||||
status, data, time = self.google_service._do_request(url, params, headers, type='GET', timeout=timeout)
|
||||
status, data, time = self.google_service._do_request(url, params, headers, method='GET', timeout=timeout)
|
||||
next_page_token = data.get('nextPageToken')
|
||||
events += data.get('items', [])
|
||||
|
||||
pprint.pprint(data)
|
||||
|
||||
next_sync_token = data.get('nextSyncToken')
|
||||
default_reminders = data.get('defaultReminders')
|
||||
|
||||
@@ -63,31 +58,26 @@ class GoogleCalendarService():
|
||||
|
||||
@requires_auth_token
|
||||
def insert(self, values, token=None, timeout=TIMEOUT):
|
||||
print("INSERT")
|
||||
url = "/calendar/v3/calendars/primary/events"
|
||||
headers = {'Content-type': 'application/json', 'Authorization': 'Bearer %s' % token}
|
||||
if not values.get('id'):
|
||||
values['id'] = uuid4().hex
|
||||
import pprint
|
||||
pprint.pprint(values)
|
||||
self.google_service._do_request(url, json.dumps(values), headers, type='POST', timeout=timeout)
|
||||
self.google_service._do_request(url, json.dumps(values), headers, method='POST', timeout=timeout)
|
||||
return values['id']
|
||||
|
||||
@requires_auth_token
|
||||
def patch(self, event_id, values, token=None, timeout=TIMEOUT):
|
||||
print("PATCH")
|
||||
url = "/calendar/v3/calendars/primary/events/%s" % event_id
|
||||
headers = {'Content-type': 'application/json', 'Authorization': 'Bearer %s' % token}
|
||||
self.google_service._do_request(url, json.dumps(values), headers, type='PUT', timeout=timeout)
|
||||
self.google_service._do_request(url, json.dumps(values), headers, method='PUT', timeout=timeout)
|
||||
|
||||
@requires_auth_token
|
||||
def delete(self, event_id, token=None, timeout=TIMEOUT):
|
||||
print("DELETE")
|
||||
url = "/calendar/v3/calendars/primary/events/%s" % event_id
|
||||
headers = {'Content-type': 'application/json'}
|
||||
params = {'access_token': token}
|
||||
try:
|
||||
self.google_service._do_request(url, params, headers=headers, type='DELETE', timeout=timeout)
|
||||
self.google_service._do_request(url, params, headers=headers, method='DELETE', timeout=timeout)
|
||||
except requests.HTTPError as e:
|
||||
# For some unknown reason Google can also return a 403 response when the event is already cancelled.
|
||||
if e.response.status_code not in (410, 403):
|
||||
|
||||
@@ -169,6 +169,6 @@ class GoogleEvent(abc.Set):
|
||||
def _get_model(self, env):
|
||||
if all([e.is_recurrence() for e in self]):
|
||||
return env['calendar.recurrence']
|
||||
elif all([not e.is_recurrence() for e in self]):
|
||||
if all([not e.is_recurrence() for e in self]):
|
||||
return env['calendar.event']
|
||||
raise TypeError("Mixing Google events and Google recurrences")
|
||||
raise TypeError("Mixing Google events and Google recurrences")
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import reset_account
|
||||
|
||||
@@ -12,13 +12,13 @@ class ResetGoogleAccount(models.TransientModel):
|
||||
_description = 'Google Calendar Account Reset'
|
||||
|
||||
user_id = fields.Many2one('res.users', required=True)
|
||||
delete_policy = fields.Selection([
|
||||
('dont_delete', "Leave them untouched"),
|
||||
('delete_google', "Delete from the current Google Calendar account"),
|
||||
('delete_odoo', "Delete from Odoo"),
|
||||
('delete_both', "Delete from both"),
|
||||
], string="User's Existing Events", required=True, default='dont_delete',
|
||||
help="This will only affect events for which the user is the owner")
|
||||
delete_policy = fields.Selection(
|
||||
[('dont_delete', "Leave them untouched"),
|
||||
('delete_google', "Delete from the current Google Calendar account"),
|
||||
('delete_odoo', "Delete from Odoo"),
|
||||
('delete_both', "Delete from both"),
|
||||
], string="User's Existing Events", required=True, default='dont_delete',
|
||||
help="This will only affect events for which the user is the owner")
|
||||
sync_policy = fields.Selection([
|
||||
('new', "Synchronize only new events"),
|
||||
('all', "Synchronize all existing events"),
|
||||
@@ -27,13 +27,15 @@ class ResetGoogleAccount(models.TransientModel):
|
||||
def reset_account(self):
|
||||
google = GoogleCalendarService(self.env['google.service'])
|
||||
|
||||
events = self.env['calendar.event'].search([('user_id', '=', self.user_id.id), ('google_id', '!=', False)])
|
||||
if self.delete_policy in ('delete_google' , 'delete_both'):
|
||||
events = self.env['calendar.event'].search([
|
||||
('user_id', '=', self.user_id.id),
|
||||
('google_id', '!=', False)])
|
||||
if self.delete_policy in ('delete_google', 'delete_both'):
|
||||
with google_calendar_token(self.user_id) as token:
|
||||
for event in events:
|
||||
google.delete(event.google_id, token=token)
|
||||
|
||||
if self.delete_policy in ('delete_odoo' , 'delete_both'):
|
||||
if self.delete_policy in ('delete_odoo', 'delete_both'):
|
||||
events.google_id = False
|
||||
events.unlink()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user