calendar/tests/test_event_recurrence.py

973 lines
42 KiB
Python
Raw Permalink Normal View History

2024-05-03 12:43:00 +03:00
# -*- coding: utf-8 -*-
2024-05-21 15:09:16 +03:00
# Part of Talisman . See LICENSE file for full copyright and licensing details.
2024-05-03 12:43:00 +03:00
import pytz
from datetime import datetime, date
from dateutil.relativedelta import relativedelta
from odoo.exceptions import UserError
from odoo.tests.common import TransactionCase, Form
from freezegun import freeze_time
class TestRecurrentEvents(TransactionCase):
@classmethod
def setUpClass(cls):
super(TestRecurrentEvents, cls).setUpClass()
lang = cls.env['res.lang']._lang_get(cls.env.user.lang)
lang.week_start = '1' # Monday
def assertEventDates(self, events, dates):
events = events.sorted('start')
self.assertEqual(len(events), len(dates), "Wrong number of events in the recurrence")
self.assertTrue(all(events.mapped('active')), "All events should be active")
for event, dates in zip(events, dates):
start, stop = dates
self.assertEqual(event.start, start)
self.assertEqual(event.stop, stop)
class TestCreateRecurrentEvents(TestRecurrentEvents):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.event = cls.env['calendar.event'].create({
'name': 'Recurrent Event',
'start': datetime(2019, 10, 21, 8, 0),
'stop': datetime(2019, 10, 23, 18, 0),
'recurrency': True,
})
def test_weekly_count(self):
""" Every week, on Tuesdays, for 3 occurences """
detached_events = self.event._apply_recurrence_values({
'rrule_type': 'weekly',
'tue': True,
'interval': 1,
'count': 3,
'event_tz': 'UTC',
})
self.assertEqual(detached_events, self.event, "It should be detached from the recurrence")
self.assertFalse(self.event.recurrence_id, "It should be detached from the recurrence")
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
events = recurrence.calendar_event_ids
self.assertEqual(len(events), 3, "It should have 3 events in the recurrence")
self.assertEventDates(events, [
(datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)),
(datetime(2019, 10, 29, 8, 0), datetime(2019, 10, 31, 18, 0)),
(datetime(2019, 11, 5, 8, 0), datetime(2019, 11, 7, 18, 0)),
])
def test_weekly_interval_2(self):
self.event._apply_recurrence_values({
'interval': 2,
'rrule_type': 'weekly',
'tue': True,
'count': 2,
'event_tz': 'UTC',
})
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
events = recurrence.calendar_event_ids
self.assertEventDates(events, [
(datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)),
(datetime(2019, 11, 5, 8, 0), datetime(2019, 11, 7, 18, 0)),
])
def test_weekly_interval_2_week_start_sunday(self):
lang = self.env['res.lang']._lang_get(self.env.user.lang)
lang.week_start = '7' # Sunday
self.event._apply_recurrence_values({
'interval': 2,
'rrule_type': 'weekly',
'tue': True,
'count': 2,
'event_tz': 'UTC',
})
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
events = recurrence.calendar_event_ids
self.assertEventDates(events, [
(datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)),
(datetime(2019, 11, 5, 8, 0), datetime(2019, 11, 7, 18, 0)),
])
lang.week_start = '1' # Monday
def test_weekly_until(self):
self.event._apply_recurrence_values({
'rrule_type': 'weekly',
'tue': True,
'interval': 2,
'end_type': 'end_date',
'until': datetime(2019, 11, 15),
'event_tz': 'UTC',
})
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
events = recurrence.calendar_event_ids
self.assertEqual(len(events), 2, "It should have 2 events in the recurrence")
self.assertEventDates(events, [
(datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)),
(datetime(2019, 11, 5, 8, 0), datetime(2019, 11, 7, 18, 0)),
])
def test_monthly_count_by_date(self):
self.event._apply_recurrence_values({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'date',
'day': 27,
'end_type': 'count',
'count': 3,
'event_tz': 'UTC',
})
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
events = recurrence.calendar_event_ids
self.assertEqual(len(events), 3, "It should have 3 events in the recurrence")
self.assertEventDates(events, [
(datetime(2019, 10, 27, 8, 0), datetime(2019, 10, 29, 18, 0)),
(datetime(2019, 12, 27, 8, 0), datetime(2019, 12, 29, 18, 0)),
(datetime(2020, 2, 27, 8, 0), datetime(2020, 2, 29, 18, 0)),
])
def test_monthly_count_by_date_31(self):
self.event._apply_recurrence_values({
'rrule_type': 'monthly',
'interval': 1,
'month_by': 'date',
'day': 31,
'end_type': 'count',
'count': 3,
'event_tz': 'UTC',
})
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
events = recurrence.calendar_event_ids
self.assertEqual(len(events), 3, "It should have 3 events in the recurrence")
self.assertEventDates(events, [
(datetime(2019, 10, 31, 8, 0), datetime(2019, 11, 2, 18, 0)),
# Missing 31th in November
(datetime(2019, 12, 31, 8, 0), datetime(2020, 1, 2, 18, 0)),
(datetime(2020, 1, 31, 8, 0), datetime(2020, 2, 2, 18, 0)),
])
def test_monthly_until_by_day(self):
""" Every 2 months, on the third Tuesday, until 27th March 2020 """
self.event.start = datetime(2019, 10, 1, 8, 0)
self.event.stop = datetime(2019, 10, 3, 18, 0)
self.event._apply_recurrence_values({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'day',
'byday': '3',
'weekday': 'TUE',
'end_type': 'end_date',
'until': date(2020, 3, 27),
'event_tz': 'UTC',
})
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
events = recurrence.calendar_event_ids
self.assertEqual(len(events), 3, "It should have 3 events in the recurrence")
self.assertEventDates(events, [
(datetime(2019, 10, 15, 8, 0), datetime(2019, 10, 17, 18, 0)),
(datetime(2019, 12, 17, 8, 0), datetime(2019, 12, 19, 18, 0)),
(datetime(2020, 2, 18, 8, 0), datetime(2020, 2, 20, 18, 0)),
])
def test_monthly_until_by_day_last(self):
""" Every 2 months, on the last Wednesday, until 15th January 2020 """
self.event._apply_recurrence_values({
'interval': 2,
'rrule_type': 'monthly',
'month_by': 'day',
'weekday': 'WED',
'byday': '-1',
'end_type': 'end_date',
'until': date(2020, 1, 15),
'event_tz': 'UTC',
})
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
events = recurrence.calendar_event_ids
self.assertEqual(len(events), 2, "It should have 3 events in the recurrence")
self.assertEventDates(events, [
(datetime(2019, 10, 30, 8, 0), datetime(2019, 11, 1, 18, 0)),
(datetime(2019, 12, 25, 8, 0), datetime(2019, 12, 27, 18, 0)),
])
def test_yearly_count(self):
self.event._apply_recurrence_values({
'interval': 2,
'rrule_type': 'yearly',
'count': 2,
'event_tz': 'UTC',
})
events = self.event.recurrence_id.calendar_event_ids
self.assertEqual(len(events), 2, "It should have 3 events in the recurrence")
self.assertEventDates(events, [
(self.event.start, self.event.stop),
(self.event.start + relativedelta(years=2), self.event.stop + relativedelta(years=2)),
])
def test_dst_timezone(self):
""" Test hours stays the same, regardless of DST changes """
self.event.start = datetime(2002, 10, 28, 10, 0)
self.event.stop = datetime(2002, 10, 28, 12, 0)
self.event._apply_recurrence_values({
'interval': 2,
'rrule_type': 'weekly',
'mon': True,
'count': '2',
'event_tz': 'America/New_York', # DST change on 2002/10/27
})
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
self.assertEventDates(recurrence.calendar_event_ids, [
(datetime(2002, 10, 28, 10, 0), datetime(2002, 10, 28, 12, 0)),
(datetime(2002, 11, 11, 10, 0), datetime(2002, 11, 11, 12, 0)),
])
def test_ambiguous_dst_time_winter(self):
""" Test hours stays the same, regardless of DST changes """
eastern = pytz.timezone('America/New_York')
dt = eastern.localize(datetime(2002, 10, 20, 1, 30, 00)).astimezone(pytz.utc).replace(tzinfo=None)
# Next occurence happens at 1:30am on 27th Oct 2002 which happened twice in the America/New_York
# timezone when the clocks where put back at the end of Daylight Saving Time
self.event.start = dt
self.event.stop = dt + relativedelta(hours=1)
self.event._apply_recurrence_values({
'interval': 1,
'rrule_type': 'weekly',
'sun': True,
'count': '2',
'event_tz': 'America/New_York' # DST change on 2002/4/7
})
events = self.event.recurrence_id.calendar_event_ids
self.assertEqual(events.mapped('duration'), [1, 1])
self.assertEventDates(events, [
(datetime(2002, 10, 20, 5, 30), datetime(2002, 10, 20, 6, 30)),
(datetime(2002, 10, 27, 6, 30), datetime(2002, 10, 27, 7, 30)),
])
def test_ambiguous_dst_time_spring(self):
""" Test hours stays the same, regardless of DST changes """
eastern = pytz.timezone('America/New_York')
dt = eastern.localize(datetime(2002, 3, 31, 2, 30, 00)).astimezone(pytz.utc).replace(tzinfo=None)
# Next occurence happens 2:30am on 7th April 2002 which never happened at all in the
# America/New_York timezone, as the clocks where put forward at 2:00am skipping the entire hour
self.event.start = dt
self.event.stop = dt + relativedelta(hours=1)
self.event._apply_recurrence_values({
'interval': 1,
'rrule_type': 'weekly',
'sun': True,
'count': '2',
'event_tz': 'America/New_York' # DST change on 2002/4/7
})
events = self.event.recurrence_id.calendar_event_ids
self.assertEqual(events.mapped('duration'), [1, 1])
# The event begins at "the same time" (i.e. 2h30 after midnight), but that day, 2h30 after midnight happens to be at 3:30 am
self.assertEventDates(events, [
(datetime(2002, 3, 31, 7, 30), datetime(2002, 3, 31, 8, 30)),
(datetime(2002, 4, 7, 7, 30), datetime(2002, 4, 7, 8, 30)),
])
def test_ambiguous_full_day(self):
""" Test date stays the same, regardless of DST changes """
self.event.write({
'start': datetime(2020, 3, 23, 0, 0),
'stop': datetime(2020, 3, 23, 23, 59),
})
self.event.allday = True
self.event._apply_recurrence_values({
'interval': 1,
'rrule_type': 'weekly',
'mon': True,
'count': 2,
'event_tz': 'Europe/Brussels' # DST change on 2020/3/23
})
events = self.event.recurrence_id.calendar_event_ids
self.assertEventDates(events, [
(datetime(2020, 3, 23, 0, 0), datetime(2020, 3, 23, 23, 59)),
(datetime(2020, 3, 30, 0, 0), datetime(2020, 3, 30, 23, 59)),
])
def test_videocall_recurrency(self):
self.event._set_discuss_videocall_location()
self.event._apply_recurrence_values({
'interval': 1,
'rrule_type': 'weekly',
'mon': True,
'count': 2,
})
recurrent_events = self.event.recurrence_id.calendar_event_ids
detached_events = self.event.recurrence_id.calendar_event_ids - self.event
rec_events_videocall_locations = recurrent_events.mapped('videocall_location')
self.assertEqual(len(rec_events_videocall_locations), len(set(rec_events_videocall_locations)), 'Recurrent events should have different videocall locations')
self.assertEqual(not any(recurrent_events.videocall_channel_id), True, 'No channel should be set before the route is accessed')
# create the first channel
detached_events[0]._create_videocall_channel()
# after channel is created, all other events should have the same channel
self.assertEqual(detached_events[0].videocall_channel_id.id, self.event.videocall_channel_id.id)
@freeze_time('2023-03-27')
def test_backward_pass_dst(self):
"""
When we apply the rule to compute the period of the recurrence,
we take an earlier date (in `_get_start_of_period` method).
However, it is possible that this earlier date has a different DST.
This causes time difference problems.
"""
# In Europe/Brussels: 26 March 2023 from winter to summer (from no DST to DST)
# We are in the case where we create a recurring event after the time change (there is the DST).
timezone = 'Europe/Brussels'
tz = pytz.timezone(timezone)
dt = tz.localize(datetime(2023, 3, 27, 9, 0, 00)).astimezone(pytz.utc).replace(tzinfo=None)
self.event.start = dt
self.event.stop = dt + relativedelta(hours=1)
# Check before apply the recurrence
self.assertEqual(self.event.start, datetime(2023, 3, 27, 7, 0, 00)) # Because 2023-03-27 in Europe/Brussels is UTC+2
self.event._apply_recurrence_values({
'rrule_type': 'monthly', # Because we will take the first day of the month (jump back)
'interval': 1,
'end_type': 'count',
'count': 2, # To have the base event and the unique recurrence event
'month_by': 'date',
'day': 27,
'event_tz': timezone,
})
# What we expect:
# - start date of base event: datetime(2023, 3, 27, 7, 0, 00)
# - start date of the unique recurrence event: datetime(2023, 4, 27, 7, 0, 00)
# With the FIX, we replace the following lines with
# `events = self.event.recurrence_id.calendar_event_ids`
recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)])
events = recurrence.calendar_event_ids
self.assertEqual(len(events), 2, "It should have 2 events in the recurrence")
self.assertIn(self.event, events)
self.assertEventDates(events, [
(datetime(2023, 3, 27, 7, 00), datetime(2023, 3, 27, 8, 00)),
(datetime(2023, 4, 27, 7, 00), datetime(2023, 4, 27, 8, 00)),
])
def test_all_day_date(self):
recurrence = self.env['calendar.event'].with_context(
default_start=datetime(2019, 10, 22),
default_stop=datetime(2019, 10, 22),
default_start_date=date(2019, 10, 22),
default_stop_date=date(2019, 10, 22),
).create({
'name': 'Recurrent Event',
'start': datetime(2019, 10, 22, 8, 0),
'stop': datetime(2019, 10, 22, 18, 0),
'start_date': date(2019, 10, 22),
'stop_date': date(2019, 10, 22),
'recurrency': True,
'rrule_type': 'weekly',
'tue': True,
'interval': 1,
'count': 2,
'event_tz': 'UTC',
'allday': True,
}).recurrence_id
events = recurrence.calendar_event_ids
self.assertEqual(events[0].start_date, date(2019, 10, 22), "The first event has the initial start date")
self.assertEqual(events[1].start_date, date(2019, 10, 29), "The start date of the second event is one week later")
def test_recurrency_with_this_event(self):
"""
1) Create an event with a recurrence set on it
2) Try updating the event with a different recurrence without specifying 'recurrence_update'
3) Update the recurrence of one of the events, this time using the 'recurrence_update' as future_events
4) Finally, check that the updated event correctly reflects the recurrence
"""
event = self.env['calendar.event'].create({
'name': "Test Event",
'allday': False,
'rrule': u'FREQ=DAILY;INTERVAL=1;COUNT=10',
'recurrency': True,
'start': datetime(2023, 7, 28, 1, 0),
'stop': datetime(2023, 7, 29, 18, 0),
})
events = self.env['calendar.recurrence'].search([('base_event_id', '=', event.id)]).calendar_event_ids
self.assertEqual(len(events), 10, "It should have 10 events in the recurrence")
# Update the recurrence without without specifying 'recurrence_update'
with self.assertRaises(UserError):
event.write({'rrule': u'FREQ=DAILY;INTERVAL=2;COUNT=5'})
# Update the recurrence of the earlier event
events[5].write({
'recurrence_update': 'future_events',
'count': 2,
})
updated_events = self.env['calendar.recurrence'].search([('base_event_id', '=', events[5].id)]).calendar_event_ids
self.assertEqual(len(updated_events), 2, "It should have 2 events in the recurrence")
self.assertTrue(updated_events[1].recurrency, "It should have recurrency in the updated events")
class TestUpdateRecurrentEvents(TestRecurrentEvents):
@classmethod
def setUpClass(cls):
super().setUpClass()
event = cls.env['calendar.event'].create({
'name': 'Recurrent Event',
'start': datetime(2019, 10, 22, 1, 0),
'stop': datetime(2019, 10, 24, 18, 0),
'recurrency': True,
'rrule_type': 'weekly',
'tue': True,
'interval': 1,
'count': 3,
'event_tz': 'Etc/GMT-4',
})
cls.recurrence = event.recurrence_id
cls.events = event.recurrence_id.calendar_event_ids.sorted('start')
def test_shift_future(self):
event = self.events[1]
self.events[1].write({
'recurrence_update': 'future_events',
'start': event.start + relativedelta(days=4),
'stop': event.stop + relativedelta(days=5),
})
self.assertEqual(self.recurrence.end_type, 'end_date')
self.assertEqual(self.recurrence.until, date(2019, 10, 27))
self.assertEventDates(self.recurrence.calendar_event_ids, [
(datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)),
])
new_recurrence = event.recurrence_id
self.assertNotEqual(self.recurrence, new_recurrence)
self.assertEqual(new_recurrence.count, 2)
self.assertEqual(new_recurrence.dtstart, datetime(2019, 11, 2, 1, 0))
self.assertFalse(new_recurrence.tue)
self.assertTrue(new_recurrence.sat)
self.assertEventDates(new_recurrence.calendar_event_ids, [
(datetime(2019, 11, 2, 1, 0), datetime(2019, 11, 5, 18, 0)),
(datetime(2019, 11, 9, 1, 0), datetime(2019, 11, 12, 18, 0)),
])
def test_shift_future_first(self):
event = self.events[0]
self.events[0].write({
'recurrence_update': 'future_events',
'start': event.start + relativedelta(days=4),
'stop': event.stop + relativedelta(days=5),
})
new_recurrence = event.recurrence_id
self.assertFalse(self.recurrence.exists())
self.assertEqual(new_recurrence.count, 3)
self.assertEqual(new_recurrence.dtstart, datetime(2019, 10, 26, 1, 0))
self.assertFalse(new_recurrence.tue)
self.assertTrue(new_recurrence.sat)
self.assertEventDates(new_recurrence.calendar_event_ids, [
(datetime(2019, 10, 26, 1, 0), datetime(2019, 10, 29, 18, 0)),
(datetime(2019, 11, 2, 1, 0), datetime(2019, 11, 5, 18, 0)),
(datetime(2019, 11, 9, 1, 0), datetime(2019, 11, 12, 18, 0)),
])
def test_shift_reapply(self):
event = self.events[2]
self.events[2].write({
'recurrence_update': 'future_events',
'start': event.start + relativedelta(days=4),
'stop': event.stop + relativedelta(days=5),
})
# re-Applying the first recurrence should be idempotent
self.recurrence._apply_recurrence()
self.assertEventDates(self.recurrence.calendar_event_ids, [
(datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)),
(datetime(2019, 10, 29, 1, 0), datetime(2019, 10, 31, 18, 0)),
])
def test_shift_all(self):
event = self.events[1]
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
(datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)),
(datetime(2019, 10, 29, 1, 0), datetime(2019, 10, 31, 18, 0)),
(datetime(2019, 11, 5, 1, 0), datetime(2019, 11, 7, 18, 0)),
])
event.write({
'recurrence_update': 'all_events',
'tue': False,
'fri': False,
'sat': True,
'start': event.start + relativedelta(days=4),
'stop': event.stop + relativedelta(days=5),
})
recurrence = self.env['calendar.recurrence'].search([], limit=1)
self.assertEventDates(recurrence.calendar_event_ids, [
(datetime(2019, 10, 26, 1, 0), datetime(2019, 10, 29, 18, 0)),
(datetime(2019, 11, 2, 1, 0), datetime(2019, 11, 5, 18, 0)),
(datetime(2019, 11, 9, 1, 0), datetime(2019, 11, 12, 18, 0)),
])
def test_shift_stop_all(self):
# testing the case where we only want to update the stop time
event = self.events[0]
event.write({
'recurrence_update': 'all_events',
'stop': event.stop + relativedelta(hours=1),
})
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
(datetime(2019, 10, 22, 2, 0), datetime(2019, 10, 24, 19, 0)),
(datetime(2019, 10, 29, 2, 0), datetime(2019, 10, 31, 19, 0)),
(datetime(2019, 11, 5, 2, 0), datetime(2019, 11, 7, 19, 0)),
])
def test_change_week_day_rrule(self):
recurrence = self.events.recurrence_id
recurrence.rrule = 'FREQ=WEEKLY;COUNT=3;BYDAY=WE' # from TU to WE
self.assertFalse(self.recurrence.tue)
self.assertTrue(self.recurrence.wed)
def test_shift_all_base_inactive(self):
self.recurrence.base_event_id.active = False
event = self.events[1]
event.write({
'recurrence_update': 'all_events',
'start': event.start + relativedelta(days=4),
'stop': event.stop + relativedelta(days=5),
})
self.assertFalse(self.recurrence.exists(), "Inactive event should not create recurrent events")
def test_shift_all_with_outlier(self):
outlier = self.events[1]
outlier.write({
'recurrence_update': 'self_only',
'start': datetime(2019, 10, 31, 1, 0), # Thursday
'stop': datetime(2019, 10, 31, 18, 0),
})
event = self.events[0]
event.write({
'recurrence_update': 'all_events',
'tue': False,
'fri': False,
'sat': True,
'start': event.start + relativedelta(days=4),
'stop': event.stop + relativedelta(days=4),
})
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
(datetime(2019, 10, 26, 1, 0), datetime(2019, 10, 28, 18, 0)),
(datetime(2019, 11, 2, 1, 0), datetime(2019, 11, 4, 18, 0)),
(datetime(2019, 11, 9, 1, 0), datetime(2019, 11, 11, 18, 0))
])
self.assertTrue(outlier.exists(), 'The outlier should have its date and time updated according to the change.')
def test_update_recurrence_future(self):
event = self.events[1]
event.write({
'recurrence_update': 'future_events',
'fri': True, # recurrence is now Tuesday AND Friday
'count': 4,
})
self.assertEventDates(self.recurrence.calendar_event_ids, [
(datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)), # Tu
])
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
(datetime(2019, 10, 29, 1, 0), datetime(2019, 10, 31, 18, 0)), # Tu
(datetime(2019, 11, 1, 1, 0), datetime(2019, 11, 3, 18, 0)), # Fr
(datetime(2019, 11, 5, 1, 0), datetime(2019, 11, 7, 18, 0)), # Tu
(datetime(2019, 11, 8, 1, 0), datetime(2019, 11, 10, 18, 0)), # Fr
])
events = event.recurrence_id.calendar_event_ids.sorted('start')
self.assertEqual(events[0], self.events[1], "Events on Tuesdays should not have changed")
self.assertEqual(events[2].start, self.events[2].start, "Events on Tuesdays should not have changed")
self.assertNotEqual(events.recurrence_id, self.recurrence, "Events should no longer be linked to the original recurrence")
self.assertEqual(events.recurrence_id.count, 4, "The new recurrence should have 4")
self.assertTrue(event.recurrence_id.tue)
self.assertTrue(event.recurrence_id.fri)
def test_update_name_future(self):
# update regular event (not the base event)
old_events = self.events[1:]
old_events[0].write({
'name': 'New name',
'recurrence_update': 'future_events',
'rrule_type': 'daily',
'count': 5,
})
new_recurrence = self.env['calendar.recurrence'].search([('id', '>', self.events[0].recurrence_id.id)])
self.assertTrue(self.events[0].recurrence_id.exists())
self.assertEqual(new_recurrence.count, 5)
self.assertFalse(any(old_event.active for old_event in old_events - old_events[0]))
for event in new_recurrence.calendar_event_ids:
self.assertEqual(event.name, 'New name')
# update the base event
new_events = new_recurrence.calendar_event_ids.sorted('start')
new_events[0].write({
'name': 'Old name',
'recurrence_update': 'future_events'
})
self.assertTrue(new_recurrence.exists())
for event in new_recurrence.calendar_event_ids:
self.assertEqual(event.name, 'Old name')
def test_update_recurrence_all(self):
self.events[1].write({
'recurrence_update': 'all_events',
'mon': True, # recurrence is now Tuesday AND Monday
})
recurrence = self.env['calendar.recurrence'].search([], limit=1)
self.assertEventDates(recurrence.calendar_event_ids, [
(datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)),
(datetime(2019, 10, 28, 1, 0), datetime(2019, 10, 30, 18, 0)),
(datetime(2019, 10, 29, 1, 0), datetime(2019, 10, 31, 18, 0)),
])
def test_shift_single(self):
event = self.events[1]
event.write({
'recurrence_update': 'self_only',
'name': "Updated event",
'start': event.start - relativedelta(hours=2)
})
self.events[0].write({
'recurrence_update': 'future_events',
'start': event.start + relativedelta(hours=4),
'stop': event.stop + relativedelta(hours=5),
})
def test_break_recurrence_future(self):
event = self.events[1]
event.write({
'recurrence_update': 'future_events',
'recurrency': False,
})
self.assertFalse(event.recurrence_id)
self.assertTrue(self.events[0].active)
self.assertTrue(self.events[1].active)
self.assertFalse(self.events[2].exists())
self.assertEqual(self.recurrence.until, date(2019, 10, 27))
self.assertEqual(self.recurrence.end_type, 'end_date')
self.assertEventDates(self.recurrence.calendar_event_ids, [
(datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)),
])
def test_break_recurrence_all(self):
event = self.events[1]
event.write({
'recurrence_update': 'all_events',
'recurrency': False,
'count': 0, # In practice, JS framework sends updated recurrency fields, since they have been recomputed, triggered by the `recurrency` change
})
self.assertFalse(self.events[0].exists())
self.assertTrue(event.active)
self.assertFalse(self.events[2].exists())
self.assertFalse(event.recurrence_id)
self.assertFalse(self.recurrence.exists())
def test_all_day_shift(self):
recurrence = self.env['calendar.event'].create({
'name': 'Recurrent Event',
'start_date': datetime(2019, 10, 22),
'stop_date': datetime(2019, 10, 24),
'recurrency': True,
'rrule_type': 'weekly',
'tue': True,
'interval': 1,
'count': 3,
'event_tz': 'Etc/GMT-4',
'allday': True,
}).recurrence_id
events = recurrence.calendar_event_ids.sorted('start')
event = events[1]
event.write({
'recurrence_update': 'future_events',
'start': event.start + relativedelta(days=4),
'stop': event.stop + relativedelta(days=5),
})
self.assertEqual(recurrence.end_type, 'end_date')
self.assertEqual(recurrence.until, date(2019, 10, 27))
self.assertEventDates(recurrence.calendar_event_ids, [
(datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)),
])
new_recurrence = event.recurrence_id
self.assertNotEqual(recurrence, new_recurrence)
self.assertEqual(new_recurrence.count, 2)
self.assertEqual(new_recurrence.dtstart, datetime(2019, 11, 2, 8, 0))
self.assertFalse(new_recurrence.tue)
self.assertTrue(new_recurrence.sat)
self.assertEventDates(new_recurrence.calendar_event_ids, [
(datetime(2019, 11, 2, 8, 0), datetime(2019, 11, 5, 18, 0)),
(datetime(2019, 11, 9, 8, 0), datetime(2019, 11, 12, 18, 0)),
])
def test_update_name_all(self):
old_recurrence = self.events[0].recurrence_id
old_events = old_recurrence.calendar_event_ids - self.events[0]
self.events[0].write({
'name': 'New name',
'recurrence_update': 'all_events',
'count': '5'
})
new_recurrence = self.env['calendar.recurrence'].search([('id', '>', old_recurrence.id)])
self.assertFalse(old_recurrence.exists())
self.assertEqual(new_recurrence.count, 5)
self.assertFalse(any(old_event.active for old_event in old_events))
for event in new_recurrence.calendar_event_ids:
self.assertEqual(event.name, 'New name')
def test_archive_recurrence_all(self):
self.events[1].action_mass_archive('all_events')
self.assertEqual([False, False, False], self.events.mapped('active'))
def test_archive_recurrence_future(self):
event = self.events[1]
event.action_mass_archive('future_events')
self.assertEqual([True, False, False], self.events.mapped('active'))
def test_unlink_recurrence_all(self):
event = self.events[1]
event.action_mass_deletion('all_events')
self.assertFalse(self.recurrence.exists())
self.assertFalse(self.events.exists())
def test_unlink_recurrence_future(self):
event = self.events[1]
event.action_mass_deletion('future_events')
self.assertTrue(self.recurrence)
self.assertEqual(self.events.exists(), self.events[0])
def test_unlink_recurrence_wizard_next(self):
event = self.events[1]
wizard = self.env['calendar.popover.delete.wizard'].create({'record': event.id})
form = Form(wizard)
form.delete = 'next'
form.save()
wizard.close()
self.assertTrue(self.recurrence)
self.assertEqual(self.events.exists(), self.events[0])
def test_unlink_recurrence_wizard_all(self):
event = self.events[1]
wizard = self.env['calendar.popover.delete.wizard'].create({'record': event.id})
form = Form(wizard)
form.delete = 'all'
form.save()
wizard.close()
self.assertFalse(self.recurrence.exists())
self.assertFalse(self.events.exists())
class TestUpdateMultiDayWeeklyRecurrentEvents(TestRecurrentEvents):
@classmethod
def setUpClass(cls):
super().setUpClass()
event = cls.env['calendar.event'].create({
'name': 'Recurrent Event',
'start': datetime(2019, 10, 22, 1, 0),
'stop': datetime(2019, 10, 24, 18, 0),
'recurrency': True,
'rrule_type': 'weekly',
'tue': True,
'fri': True,
'interval': 1,
'count': 3,
'event_tz': 'Etc/GMT-4',
})
cls.recurrence = event.recurrence_id
cls.events = event.recurrence_id.calendar_event_ids.sorted('start')
# Tuesday datetime(2019, 10, 22, 1, 0)
# Friday datetime(2019, 10, 25, 1, 0)
# Tuesday datetime(2019, 10, 29, 1, 0)
def test_shift_all_multiple_weekdays(self):
event = self.events[0] # Tuesday
# We go from 2 days a week Thuesday and Friday to one day a week, Thursday
event.write({
'recurrence_update': 'all_events',
'tue': False,
'thu': True,
'fri': False,
'start': event.start + relativedelta(days=2),
'stop': event.stop + relativedelta(days=2),
})
recurrence = self.env['calendar.recurrence'].search([], limit=1)
# We don't try to do magic tricks. First event is moved, other remain
self.assertEventDates(recurrence.calendar_event_ids, [
(datetime(2019, 10, 24, 1, 0), datetime(2019, 10, 26, 18, 0)),
(datetime(2019, 10, 31, 1, 0), datetime(2019, 11, 2, 18, 0)),
(datetime(2019, 11, 7, 1, 0), datetime(2019, 11, 9, 18, 0)),
])
def test_shift_all_multiple_weekdays_duration(self):
event = self.events[0] # Tuesday
event.write({
'recurrence_update': 'all_events',
'tue': False,
'thu': True,
'fri': False,
'start': event.start + relativedelta(days=2),
'stop': event.stop + relativedelta(days=3),
})
recurrence = self.env['calendar.recurrence'].search([], limit=1)
self.assertEventDates(recurrence.calendar_event_ids, [
(datetime(2019, 10, 24, 1, 0), datetime(2019, 10, 27, 18, 0)),
(datetime(2019, 10, 31, 1, 0), datetime(2019, 11, 3, 18, 0)),
(datetime(2019, 11, 7, 1, 0), datetime(2019, 11, 10, 18, 0)),
])
def test_shift_future_multiple_weekdays(self):
event = self.events[1] # Friday
event.write({
'recurrence_update': 'future_events',
'start': event.start + relativedelta(days=3),
'stop': event.stop + relativedelta(days=3),
})
self.assertTrue(self.recurrence.fri)
self.assertTrue(self.recurrence.tue)
self.assertTrue(event.recurrence_id.tue)
self.assertTrue(event.recurrence_id.mon)
self.assertFalse(event.recurrence_id.fri)
self.assertEqual(event.recurrence_id.count, 2)
class TestUpdateMonthlyByDay(TestRecurrentEvents):
@classmethod
def setUpClass(cls):
super().setUpClass()
event = cls.env['calendar.event'].create({
'name': 'Recurrent Event',
'start': datetime(2019, 10, 15, 1, 0),
'stop': datetime(2019, 10, 16, 18, 0),
'recurrency': True,
'rrule_type': 'monthly',
'interval': 1,
'count': 3,
'month_by': 'day',
'weekday': 'TUE',
'byday': '3',
'event_tz': 'Etc/GMT-4',
})
cls.recurrence = event.recurrence_id
cls.events = event.recurrence_id.calendar_event_ids.sorted('start')
# datetime(2019, 10, 15, 1, 0)
# datetime(2019, 11, 19, 1, 0)
# datetime(2019, 12, 17, 1, 0)
def test_shift_all(self):
event = self.events[1]
event.write({
'recurrence_update': 'all_events',
'start': event.start + relativedelta(hours=5),
'stop': event.stop + relativedelta(hours=5),
})
recurrence = self.env['calendar.recurrence'].search([], limit=1)
self.assertEventDates(recurrence.calendar_event_ids, [
(datetime(2019, 10, 15, 6, 0), datetime(2019, 10, 16, 23, 0)),
(datetime(2019, 11, 19, 6, 0), datetime(2019, 11, 20, 23, 0)),
(datetime(2019, 12, 17, 6, 0), datetime(2019, 12, 18, 23, 0)),
])
class TestUpdateMonthlyByDate(TestRecurrentEvents):
@classmethod
def setUpClass(cls):
super().setUpClass()
event = cls.env['calendar.event'].create({
'name': 'Recurrent Event',
'start': datetime(2019, 10, 22, 1, 0),
'stop': datetime(2019, 10, 24, 18, 0),
'recurrency': True,
'rrule_type': 'monthly',
'interval': 1,
'count': 3,
'month_by': 'date',
'day': 22,
'event_tz': 'Etc/GMT-4',
})
cls.recurrence = event.recurrence_id
cls.events = event.recurrence_id.calendar_event_ids.sorted('start')
# datetime(2019, 10, 22, 1, 0)
# datetime(2019, 11, 22, 1, 0)
# datetime(2019, 12, 22, 1, 0)
def test_shift_future(self):
event = self.events[1]
event.write({
'recurrence_update': 'future_events',
'start': event.start + relativedelta(days=4),
'stop': event.stop + relativedelta(days=5),
})
self.assertEventDates(self.recurrence.calendar_event_ids, [
(datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)),
])
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
(datetime(2019, 11, 26, 1, 0), datetime(2019, 11, 29, 18, 0)),
(datetime(2019, 12, 26, 1, 0), datetime(2019, 12, 29, 18, 0)),
])
def test_update_all(self):
event = self.events[1]
event.write({
'recurrence_update': 'all_events',
'day': 25,
})
recurrence = self.env['calendar.recurrence'].search([('day', '=', 25)])
self.assertEventDates(recurrence.calendar_event_ids, [
(datetime(2019, 10, 25, 1, 0), datetime(2019, 10, 27, 18, 0)),
(datetime(2019, 11, 25, 1, 0), datetime(2019, 11, 27, 18, 0)),
(datetime(2019, 12, 25, 1, 0), datetime(2019, 12, 27, 18, 0)),
])
def test_recurring_ui_options_daily(self):
with Form(self.env['calendar.event']) as calendar_form:
calendar_form.name = 'test recurrence daily'
calendar_form.recurrency = True
calendar_form.rrule_type_ui = 'daily'
calendar_form.count = 2
calendar_form.start = datetime(2019, 6, 23, 16)
calendar_form.stop = datetime(2019, 6, 23, 17)
event = calendar_form.save()
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
(datetime(2019, 6, 23, 16), datetime(2019, 6, 23, 17)),
(datetime(2019, 6, 24, 16, 0), datetime(2019, 6, 24, 17)),
])
self.assertEqual(event.rrule_type_ui, 'daily')
self.assertEqual(event.count, 2)
def test_recurring_ui_options_monthly(self):
with Form(self.env['calendar.event']) as calendar_form:
calendar_form.name = 'test recurrence monthly'
calendar_form.recurrency = True
calendar_form.rrule_type_ui = 'monthly'
calendar_form.count = 2
calendar_form.start = datetime(2019, 6, 11, 16)
calendar_form.stop = datetime(2019, 6, 11, 17)
calendar_form.day = 11
event = calendar_form.save()
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
(datetime(2019, 6, 11, 16), datetime(2019, 6, 11, 17)),
(datetime(2019, 7, 11, 16), datetime(2019, 7, 11, 17)),
])
self.assertEqual(event.rrule_type_ui, 'monthly')
self.assertEqual(event.count, 2)
def test_recurring_ui_options_yearly(self):
with Form(self.env['calendar.event']) as calendar_form:
calendar_form.name = 'test recurrence yearly'
calendar_form.recurrency = True
calendar_form.rrule_type_ui = 'yearly'
calendar_form.count = 2
calendar_form.start = datetime(2019, 6, 11, 16)
calendar_form.stop = datetime(2019, 6, 11, 17)
event = calendar_form.save()
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
(datetime(2019, 6, 11, 16), datetime(2019, 6, 11, 17)),
(datetime(2020, 6, 11, 16), datetime(2020, 6, 11, 17)),
])
# set to custom because a yearly recurrence, becomes a monthly recurrence every 12 months
self.assertEqual(event.rrule_type_ui, 'custom')
self.assertEqual(event.count, 2)
self.assertEqual(event.interval, 12)
self.assertEqual(event.rrule_type, 'monthly')