From 3161f47cd1676339412d196883304e423110e75b Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Tue, 14 Nov 2023 16:12:08 +0100 Subject: [PATCH] manager: detect partial booking checks overlap using only form data (#82231) --- chrono/agendas/models.py | 15 --------------- chrono/manager/forms.py | 3 --- chrono/manager/views.py | 23 ++++++++++++++++++++++- tests/manager/test_partial_bookings.py | 17 +++++++++++++++-- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index 4c3ddb80..0fe00773 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -3254,21 +3254,6 @@ class BookingCheck(models.Model): return False return True - def overlaps_existing_check(self, start_time, end_time): - booking_checks = BookingCheck.objects.filter(booking=self.booking).exclude(pk=self.pk) - if not booking_checks: - return False - - if len(booking_checks) > 1: - raise ValueError('too many booking checks') # should not happen - - booking_check = booking_checks[0] - - if not start_time or not end_time: - return bool(booking_check.start_time < (start_time or end_time) < booking_check.end_time) - - return bool(start_time < booking_check.end_time and end_time > booking_check.start_time) - OpeningHour = collections.namedtuple('OpeningHour', ['begin', 'end']) diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py index 23c7286c..2fb4f8b4 100644 --- a/chrono/manager/forms.py +++ b/chrono/manager/forms.py @@ -648,9 +648,6 @@ class PartialBookingCheckForm(forms.ModelForm): if start_time and end_time and end_time <= start_time: raise ValidationError(_('Arrival must be before departure.')) - if self.instance.overlaps_existing_check(start_time, end_time): - raise ValidationError(_('Booking check hours overlap existing check.')) - if self.cleaned_data['presence'] is not None: kind = 'presence' if self.cleaned_data['presence'] else 'absence' if f'{kind}_check_type' in self.cleaned_data: diff --git a/chrono/manager/views.py b/chrono/manager/views.py index 2841085e..2e46ee2f 100644 --- a/chrono/manager/views.py +++ b/chrono/manager/views.py @@ -4637,13 +4637,34 @@ class PartialBookingCheckView(ViewableAgendaMixin, TemplateView): forms = self.get_forms() all_valid = all(form.is_valid() for form in forms) - if all_valid: + if all_valid and not self.checks_overlap(forms): for form in forms: form.save() return HttpResponseRedirect(self.get_success_url()) return self.render_to_response(self.get_context_data(forms=forms)) + def checks_overlap(self, forms): + Interval = collections.namedtuple('Interval', ['start', 'end', 'form']) + intervals = [ + Interval( + # if check has no start/end time, take (time, time) as an interval + form.cleaned_data['start_time'] or form.cleaned_data['end_time'], + form.cleaned_data['end_time'] or form.cleaned_data['start_time'], + form, + ) + for form in forms + if form.cleaned_data['presence'] is not None + ] + intervals.sort(key=lambda x: x.start) + + for i in range(1, len(intervals)): + if intervals[i - 1].end > intervals[i].start: + intervals[i].form.add_error(None, _('Booking check hours are overlapping.')) + return True + + return False + def get_success_url(self): date = self.event.start_datetime return reverse( diff --git a/tests/manager/test_partial_bookings.py b/tests/manager/test_partial_bookings.py index f2d9c37a..8064b2ab 100644 --- a/tests/manager/test_partial_bookings.py +++ b/tests/manager/test_partial_bookings.py @@ -552,11 +552,11 @@ def test_manager_partial_bookings_multiple_checks(app, admin_user): resp.form['check-2-start_time'] = '11:30' resp.form['check-2-end_time'] = '' resp = resp.form.submit() - assert 'Booking check hours overlap existing check.' in resp.text + assert 'Booking check hours are overlapping.' in resp.text resp.form['check-2-end_time'] = '17:30' resp = resp.form.submit() - assert 'Booking check hours overlap existing check.' in resp.text + assert 'Booking check hours are overlapping.' in resp.text resp.form['check-2-start_time'] = '12:30' resp.form['check-2-presence'] = '' @@ -578,6 +578,19 @@ def test_manager_partial_bookings_multiple_checks(app, admin_user): assert 'Arrival must be before departure.' in resp.text assert 'gadjo-folded' not in resp.text + # overlap is detected when 2 checks are created at the same time + BookingCheck.objects.all().delete() + + resp.form['presence'] = 'True' + resp.form['start_time'] = '10:00' + resp.form['end_time'] = '11:00' + resp.form['check-2-presence'] = 'False' + resp.form['check-2-start_time'] = '10:30' + resp.form['check-2-end_time'] = '11:30' + resp = resp.form.submit() + + assert 'Booking check hours are overlapping.' in resp.text + @pytest.mark.parametrize('subscription_only', (True, False)) def test_manager_partial_bookings_incomplete_check(subscription_only, app, admin_user):