diff --git a/addons/calendar/models/calendar_event.py b/addons/calendar/models/calendar_event.py index 84a71758551..a960b0e4adf 100644 --- a/addons/calendar/models/calendar_event.py +++ b/addons/calendar/models/calendar_event.py @@ -26,7 +26,6 @@ _logger = logging.getLogger(__name__) SORT_ALIASES = { 'start': 'sort_start', 'start_date': 'sort_start', - 'start_datetime': 'sort_start', } def get_weekday_occurence(date): @@ -115,13 +114,13 @@ class Meeting(models.Model): @api.model def _get_time_fields(self): - return {'start', 'stop', 'start_date', 'stop_date', 'start_datetime', 'stop_datetime'} + return {'start', 'stop', 'start_date', 'stop_date'} @api.model def _get_public_fields(self): return self._get_recurrent_fields() | { - 'id', 'active', 'allday', 'start', 'stop', 'display_start', - 'display_stop', 'duration', 'user_id', 'interval', + 'id', 'active', 'allday', 'start', 'stop', + 'duration', 'user_id', 'interval', 'count', 'rrule', 'recurrence_id', 'show_as'} @api.model @@ -170,12 +169,10 @@ class Meeting(models.Model): def _get_duration(self, start, stop): """ Get the duration value between the 2 given dates. """ - if start and stop: - diff = fields.Datetime.from_string(stop) - fields.Datetime.from_string(start) - if diff: - duration = float(diff.days) * 24 + (float(diff.seconds) / 3600) - return round(duration, 2) - return 0.0 + if not start or not stop: + return 0 + duration = (stop - start).total_seconds() / 3600 + return round(duration, 2) def _compute_is_highlighted(self): if self.env.context.get('active_model') == 'res.partner': @@ -194,17 +191,15 @@ class Meeting(models.Model): is_attendee = fields.Boolean('Attendee', 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') - display_start = fields.Char('Date', compute='_compute_display_start', store=True) - start = fields.Datetime('Start', required=True, help="Start date of an event, without time for full days events") - stop = fields.Datetime('Stop', required=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) - start_datetime = fields.Datetime('Start DateTime', 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) - stop_datetime = fields.Datetime('End Datetime', compute='_compute_dates', inverse='_inverse_dates', store=True, tracking=True) # old date_deadline 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_dates', inverse='_inverse_duration', compute_sudo=True) + 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) location = fields.Char('Location', tracking=True, help="Location of Event") @@ -284,11 +279,6 @@ class Meeting(models.Model): for meeting in self: meeting.display_time = self._get_display_time(meeting.start, meeting.stop, meeting.duration, meeting.allday) - @api.depends('allday', 'start_date', 'start_datetime') - def _compute_display_start(self): - for meeting in self: - meeting.display_start = meeting.start_date if meeting.allday else meeting.start_datetime - @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 @@ -297,18 +287,33 @@ class Meeting(models.Model): for meeting in self: if meeting.allday and meeting.start and meeting.stop: meeting.start_date = meeting.start.date() - meeting.start_datetime = False meeting.stop_date = meeting.stop.date() - meeting.stop_datetime = False - - meeting.duration = 0.0 else: meeting.start_date = False - meeting.start_datetime = meeting.start meeting.stop_date = False - meeting.stop_datetime = meeting.stop - meeting.duration = self._get_duration(meeting.start, meeting.stop) + @api.depends('stop', 'start') + def _compute_duration(self): + for event in self.with_context(dont_notify=True): + event.duration = self._get_duration(event.start, event.stop) + + @api.depends('start', 'duration') + def _compute_stop(self): + # stop and duration fields both depends on the start field. + # But they also depends on each other. + # When start is updated, we want to update the stop datetime based on + # the *current* duration. In other words, we want: change start => keep the duration fixed and + # recompute stop accordingly. + # However, while computing stop, duration is marked to be recomputed. Calling `event.duration` would trigger + # its recomputation. To avoid this we manually mark the field as computed. + duration_field = self._fields['duration'] + self.env.remove_to_compute(duration_field, self) + for event in self.filtered('duration'): + # Round the duration (in hours) to the minute to avoid weird situations where the event + # stops at 4:19:59, later displayed as 4:19. + event.stop = event.start + timedelta(minutes=round(event.duration * 60)) + if event.allday: + event.stop -= timedelta(seconds=1) def _inverse_dates(self): for meeting in self: @@ -330,21 +335,14 @@ class Meeting(models.Model): 'start': startdate.replace(tzinfo=None), 'stop': enddate.replace(tzinfo=None) }) - else: - meeting.write({'start': meeting.start_datetime, - 'stop': meeting.stop_datetime}) - def _inverse_duration(self): - for event in self: - event.stop = event.start + relativedelta(hours=event.duration) - - @api.constrains('start_datetime', 'stop_datetime', 'start_date', 'stop_date') + @api.constrains('start', 'stop', 'start_date', 'stop_date') def _check_closing_date(self): for meeting in self: - if meeting.start_datetime and meeting.stop_datetime and meeting.stop_datetime < meeting.start_datetime: + if meeting.start and meeting.stop and meeting.stop < meeting.start: raise ValidationError( _('The ending date and time cannot be earlier than the starting date and time.') + '\n' + - _("Meeting '%s' starts '%s' and ends '%s'") % (meeting.name, meeting.start_datetime, meeting.stop_datetime) + _("Meeting '%s' starts '%s' and ends '%s'") % (meeting.name, meeting.start, meeting.stop) ) if meeting.start_date and meeting.stop_date and meeting.stop_date < meeting.start_date: raise ValidationError( @@ -352,27 +350,6 @@ class Meeting(models.Model): _("Meeting '%s' starts '%s' and ends '%s'") % (meeting.name, meeting.start_date, meeting.stop_date) ) - @api.onchange('start_datetime', 'duration') - def _onchange_duration(self): - if self.start_datetime: - start = self.start_datetime - self.start = self.start_datetime - # Round the duration (in hours) to the minute to avoid weird situations where the event - # stops at 4:19:59, later displayed as 4:19. - self.stop = start + timedelta(minutes=round(self.duration * 60)) - if self.allday: - self.stop -= timedelta(seconds=1) - - @api.onchange('start_date') - def _onchange_start_date(self): - if self.start_date: - self.start = datetime.datetime.combine(self.start_date, datetime.time.min) - - @api.onchange('stop_date') - def _onchange_stop_date(self): - if self.stop_date: - self.stop = datetime.datetime.combine(self.stop_date, datetime.time.max) - #################################################### # Calendar Business, Reccurency, ... #################################################### diff --git a/addons/calendar/tests/test_calendar.py b/addons/calendar/tests/test_calendar.py index 8c8ca29d20d..6bcda2bd007 100644 --- a/addons/calendar/tests/test_calendar.py +++ b/addons/calendar/tests/test_calendar.py @@ -27,20 +27,6 @@ class TestCalendar(SavepointCaseWithUserDemo): 'name': 'Technical Presentation' }) - def test_calender_simple_event(self): - m = self.CalendarEvent.create({ - 'name': "Test compute", - 'start': '2017-07-12 14:30:00', - 'allday': False, - 'stop': '2017-07-12 15:00:00', - }) - - self.assertEqual( - (str(m.start_datetime), str(m.stop_datetime)), - (u'2017-07-12 14:30:00', u'2017-07-12 15:00:00'), - "Sanity check" - ) - def test_event_order(self): """ check the ordering of events when searching """ def create_event(name, date): @@ -48,7 +34,6 @@ class TestCalendar(SavepointCaseWithUserDemo): 'name': name, 'start': date + ' 12:00:00', 'stop': date + ' 14:00:00', - 'duration': 2.0, }) foo1 = create_event('foo', '2011-04-01') foo2 = create_event('foo', '2011-06-01') @@ -162,13 +147,11 @@ class TestCalendar(SavepointCaseWithUserDemo): 'name': 'All Day', 'start': "2018-10-16 00:00:00", 'start_date': "2018-10-16", - 'start_datetime': False, 'stop': "2018-10-18 00:00:00", 'stop_date': "2018-10-18", - 'stop_datetime': False, 'allday': True, }) - + event.invalidate_cache() self.assertEqual(str(event.start), '2018-10-16 08:00:00') self.assertEqual(str(event.stop), '2018-10-18 18:00:00') @@ -243,10 +226,8 @@ class TestCalendar(SavepointCaseWithUserDemo): 'name': 'All Day', 'start': "2018-10-16 00:00:00", 'start_date': "2018-10-16", - 'start_datetime': False, 'stop': "2018-10-18 00:00:00", 'stop_date': "2018-10-18", - 'stop_datetime': False, 'allday': True, 'activity_ids': [(6, False, activity_id.ids)], }) diff --git a/addons/calendar/tests/test_event_recurrence.py b/addons/calendar/tests/test_event_recurrence.py index e4519be71c0..c622cdd6a2e 100644 --- a/addons/calendar/tests/test_event_recurrence.py +++ b/addons/calendar/tests/test_event_recurrence.py @@ -249,8 +249,10 @@ class TestCreateRecurrentEvents(TestRecurrentEvents): def test_ambiguous_full_day(self): """ Test date stays the same, regardless of DST changes """ - self.event.start = datetime(2020, 3, 23, 0, 0) - self.event.stop = datetime(2020, 3, 23, 23, 59) + 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, diff --git a/addons/calendar/views/calendar_views.xml b/addons/calendar/views/calendar_views.xml index f4b51ca22b3..04420f72b18 100644 --- a/addons/calendar/views/calendar_views.xml +++ b/addons/calendar/views/calendar_views.xml @@ -117,15 +117,12 @@ - - - - - + +