manager: forbid multiple events on same day in partial bookings agenda (#79112)
gitea/chrono/pipeline/head This commit looks good
Details
gitea/chrono/pipeline/head This commit looks good
Details
This commit is contained in:
parent
06af90608f
commit
4834743c6d
|
@ -1114,6 +1114,34 @@ class Agenda(models.Model):
|
|||
|
||||
return True
|
||||
|
||||
def event_overlaps(self, start_datetime, recurrence_days, recurrence_end_date, instance=None):
|
||||
qs = self.event_set
|
||||
if hasattr(instance, 'pk'):
|
||||
qs = qs.exclude(pk=instance.pk)
|
||||
|
||||
if recurrence_days:
|
||||
qs = qs.filter(
|
||||
# check overlap with other recurring events
|
||||
Q(recurrence_end_date__isnull=True) | Q(recurrence_end_date__gte=start_datetime),
|
||||
Q(recurrence_days__overlap=recurrence_days)
|
||||
# check overlap with normal events
|
||||
| Q(start_datetime__gte=start_datetime, start_datetime__iso_week_day__in=recurrence_days),
|
||||
)
|
||||
if recurrence_end_date:
|
||||
qs = qs.filter(start_datetime__lte=recurrence_end_date)
|
||||
else:
|
||||
qs = qs.filter(
|
||||
# check overlap with other normal events
|
||||
Q(start_datetime__date=start_datetime.date())
|
||||
# check overlap with recurring events
|
||||
| Q(
|
||||
Q(recurrence_end_date__isnull=True) | Q(recurrence_end_date__gte=start_datetime),
|
||||
recurrence_days__contains=[localtime(start_datetime).isoweekday()],
|
||||
)
|
||||
)
|
||||
|
||||
return qs.exists()
|
||||
|
||||
def get_min_datetime(self, start_datetime=None):
|
||||
if self.minimal_booking_delay is None:
|
||||
return start_datetime
|
||||
|
|
|
@ -221,6 +221,7 @@ class NewEventForm(forms.ModelForm):
|
|||
super().__init__(*args, **kwargs)
|
||||
if self.instance.agenda.partial_bookings:
|
||||
del self.fields['duration']
|
||||
del self.fields['recurrence_week_interval']
|
||||
else:
|
||||
del self.fields['end_time']
|
||||
|
||||
|
@ -234,6 +235,14 @@ class NewEventForm(forms.ModelForm):
|
|||
if end_time and self.cleaned_data['start_datetime'].time() > end_time:
|
||||
self.add_error('end_time', _('End time must be greater than start time.'))
|
||||
|
||||
if self.instance.agenda.partial_bookings and self.instance.agenda.event_overlaps(
|
||||
start_datetime=self.cleaned_data['start_datetime'],
|
||||
recurrence_days=self.cleaned_data['recurrence_days'],
|
||||
recurrence_end_date=self.cleaned_data['recurrence_end_date'],
|
||||
instance=self.instance,
|
||||
):
|
||||
raise ValidationError(_('There can only be one event per day.'))
|
||||
|
||||
def clean_start_datetime(self):
|
||||
start_datetime = self.cleaned_data['start_datetime']
|
||||
if start_datetime.year < 2000:
|
||||
|
|
|
@ -1497,13 +1497,13 @@ class AgendaDayView(AgendaDateView, DayArchiveView):
|
|||
return context
|
||||
|
||||
def fill_partial_bookings_context(self, context):
|
||||
events = self.agenda.event_set.filter(start_datetime__date=self.date.date())
|
||||
if not events.exists():
|
||||
try:
|
||||
event = self.agenda.event_set.get(start_datetime__date=self.date.date())
|
||||
except Event.DoesNotExist:
|
||||
return
|
||||
|
||||
event_times = events.aggregate(Min('start_datetime'), Max('end_time'))
|
||||
min_time = localtime(event_times['start_datetime__min']).time()
|
||||
max_time = event_times['end_time__max']
|
||||
min_time = localtime(event.start_datetime).time()
|
||||
max_time = event.end_time
|
||||
|
||||
start_time = datetime.time(max(min_time.hour - 1, 0), 0)
|
||||
end_time = datetime.time(min(max_time.hour + 1, 23), 0)
|
||||
|
@ -1512,9 +1512,8 @@ class AgendaDayView(AgendaDateView, DayArchiveView):
|
|||
def get_time_ratio(t1, t2):
|
||||
return 100 * ((t1.hour - t2.hour) * 60 + t1.minute - t2.minute) // 60
|
||||
|
||||
bookings = Booking.objects.filter(event__in=events)
|
||||
bookings_by_user = collections.defaultdict(list)
|
||||
for booking in bookings:
|
||||
for booking in event.booking_set.all():
|
||||
booking.css_left = get_time_ratio(booking.start_time, start_time)
|
||||
booking.css_width = get_time_ratio(booking.end_time, booking.start_time)
|
||||
bookings_by_user[booking.user_external_id].append(booking)
|
||||
|
|
|
@ -37,6 +37,7 @@ def test_manager_partial_bookings_add_event(app, admin_user):
|
|||
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
resp = resp.click('New Event')
|
||||
assert 'duration' not in resp.form.fields
|
||||
assert 'recurrence_week_interval' not in resp.form.fields
|
||||
|
||||
resp.form['start_datetime_0'] = '2023-02-15'
|
||||
resp.form['start_datetime_1'] = '08:00'
|
||||
|
@ -52,6 +53,18 @@ def test_manager_partial_bookings_add_event(app, admin_user):
|
|||
assert 'duration' not in resp.form.fields
|
||||
assert resp.form['end_time'].value == '18:00'
|
||||
|
||||
resp.form['end_time'] = '17:00'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
resp = resp.click('New Event')
|
||||
resp.form['start_datetime_0'] = '2023-02-15'
|
||||
resp.form['start_datetime_1'] = '10:00'
|
||||
resp.form['end_time'] = '12:00'
|
||||
resp.form['places'] = 10
|
||||
resp = resp.form.submit()
|
||||
assert 'There can only be one event per day.' in resp.text
|
||||
|
||||
|
||||
def test_manager_partial_bookings_day_view(app, admin_user, freezer):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
||||
|
|
|
@ -3783,3 +3783,136 @@ virtual Agenda 30
|
|||
paris('2023-04-04 14:30 30m'),
|
||||
paris('2023-04-06 15:00 30m'),
|
||||
]
|
||||
|
||||
|
||||
def test_agenda_event_overlaps():
|
||||
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
||||
|
||||
start_datetime = make_aware(datetime.datetime(2023, 5, 1, 14, 00))
|
||||
event_kwargs = {
|
||||
'start_datetime': start_datetime,
|
||||
'recurrence_days': None,
|
||||
'recurrence_end_date': None,
|
||||
}
|
||||
assert agenda.event_overlaps(**event_kwargs) is False
|
||||
|
||||
event = Event.objects.create(start_datetime=start_datetime, agenda=agenda, places=1)
|
||||
|
||||
assert agenda.event_overlaps(**event_kwargs) is True
|
||||
assert agenda.event_overlaps(instance=event, **event_kwargs) is False
|
||||
|
||||
event_kwargs['start_datetime'] = start_datetime + datetime.timedelta(days=1)
|
||||
assert agenda.event_overlaps(**event_kwargs) is False
|
||||
|
||||
event_kwargs['start_datetime'] = start_datetime.replace(month=2)
|
||||
assert agenda.event_overlaps(**event_kwargs) is False
|
||||
|
||||
# recurring event
|
||||
event_kwargs = {
|
||||
'start_datetime': start_datetime,
|
||||
'recurrence_days': [1, 2],
|
||||
'recurrence_end_date': None,
|
||||
}
|
||||
assert agenda.event_overlaps(**event_kwargs) is True
|
||||
|
||||
# same weekday, starts after
|
||||
event_kwargs['start_datetime'] = start_datetime + datetime.timedelta(days=7)
|
||||
assert agenda.event_overlaps(**event_kwargs) is False
|
||||
|
||||
# same weekday, starts before
|
||||
event_kwargs['start_datetime'] = start_datetime - datetime.timedelta(days=7)
|
||||
assert agenda.event_overlaps(**event_kwargs) is True
|
||||
|
||||
# same weekday, starts before
|
||||
event_kwargs['start_datetime'] = start_datetime - datetime.timedelta(days=6)
|
||||
assert agenda.event_overlaps(**event_kwargs) is True
|
||||
|
||||
# different weekday, starts before
|
||||
event_kwargs['recurrence_days'] = [3]
|
||||
assert agenda.event_overlaps(**event_kwargs) is False
|
||||
|
||||
# same weekday, starts before, ends before
|
||||
event_kwargs = {
|
||||
'start_datetime': start_datetime - datetime.timedelta(days=30),
|
||||
'recurrence_days': [1, 2],
|
||||
'recurrence_end_date': start_datetime - datetime.timedelta(days=15),
|
||||
}
|
||||
assert agenda.event_overlaps(**event_kwargs) is False
|
||||
|
||||
# same weekday, starts before, ends after
|
||||
event_kwargs['recurrence_end_date'] = start_datetime + datetime.timedelta(days=15)
|
||||
assert agenda.event_overlaps(**event_kwargs) is True
|
||||
|
||||
|
||||
def test_agenda_event_overlaps_recurring():
|
||||
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
||||
|
||||
event_kwargs = {
|
||||
'start_datetime': make_aware(datetime.datetime(2023, 5, 1, 14, 00)),
|
||||
'recurrence_days': [1, 2],
|
||||
'recurrence_end_date': datetime.date(2023, 6, 1),
|
||||
}
|
||||
assert agenda.event_overlaps(**event_kwargs) is False
|
||||
|
||||
event = Event.objects.create(agenda=agenda, places=1, **event_kwargs)
|
||||
|
||||
assert agenda.event_overlaps(**event_kwargs) is True
|
||||
assert agenda.event_overlaps(instance=event, **event_kwargs) is False
|
||||
|
||||
# starts before, ends during existing event
|
||||
event_kwargs = {
|
||||
'start_datetime': make_aware(datetime.datetime(2023, 4, 1, 14, 00)),
|
||||
'recurrence_days': [1],
|
||||
'recurrence_end_date': datetime.date(2023, 5, 15),
|
||||
}
|
||||
assert agenda.event_overlaps(**event_kwargs) is True
|
||||
assert agenda.event_overlaps(instance=event, **event_kwargs) is False
|
||||
|
||||
# starts during, ends after existing event
|
||||
event_kwargs = {
|
||||
'start_datetime': make_aware(datetime.datetime(2023, 5, 15, 14, 00)),
|
||||
'recurrence_days': [2],
|
||||
'recurrence_end_date': datetime.date(2023, 6, 15),
|
||||
}
|
||||
assert agenda.event_overlaps(**event_kwargs) is True
|
||||
|
||||
# not the same day
|
||||
event_kwargs['recurrence_days'] = [3, 4]
|
||||
assert agenda.event_overlaps(**event_kwargs) is False
|
||||
|
||||
# starts after
|
||||
event_kwargs = {
|
||||
'start_datetime': make_aware(datetime.datetime(2023, 6, 15, 14, 00)),
|
||||
'recurrence_days': [2],
|
||||
'recurrence_end_date': datetime.date(2023, 7, 15),
|
||||
}
|
||||
assert agenda.event_overlaps(**event_kwargs) is False
|
||||
|
||||
# remove recurrence_end_date from event
|
||||
event.recurrence_end_date = None
|
||||
event.save()
|
||||
assert agenda.event_overlaps(**event_kwargs) is True
|
||||
|
||||
# starts before, ends before
|
||||
event_kwargs = {
|
||||
'start_datetime': make_aware(datetime.datetime(2023, 4, 1, 14, 00)),
|
||||
'recurrence_days': [2],
|
||||
'recurrence_end_date': datetime.date(2023, 4, 20),
|
||||
}
|
||||
assert agenda.event_overlaps(**event_kwargs) is False
|
||||
|
||||
# starts before, not end
|
||||
event_kwargs['recurrence_end_date'] = None
|
||||
assert agenda.event_overlaps(**event_kwargs) is True
|
||||
|
||||
# normal event, before
|
||||
event_kwargs['recurrence_days'] = None
|
||||
assert agenda.event_overlaps(**event_kwargs) is False
|
||||
|
||||
# normal event, after on a recurrence day
|
||||
event_kwargs['start_datetime'] = make_aware(datetime.datetime(2023, 5, 9, 14, 00))
|
||||
assert agenda.event_overlaps(**event_kwargs) is True
|
||||
|
||||
# normal event, after not on a recurrence day
|
||||
event_kwargs['start_datetime'] = make_aware(datetime.datetime(2023, 5, 10, 14, 00))
|
||||
assert agenda.event_overlaps(**event_kwargs) is False
|
||||
|
|
Loading…
Reference in New Issue