agendas: always create event recurrences (#62635)
This commit is contained in:
parent
939c1b0cff
commit
ba38629af3
|
@ -13,6 +13,11 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='recurrence_end_date',
|
||||
field=models.DateField(blank=True, null=True, verbose_name='Recurrence end date'),
|
||||
field=models.DateField(
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name='Recurrence end date',
|
||||
help_text='If left blank, a one-year maximal booking delay will be applied for this event.',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -900,9 +900,7 @@ class Agenda(models.Model):
|
|||
|
||||
# add recurrences
|
||||
excluded_datetimes = [event.datetime_slug for event in recurrences]
|
||||
Event.create_events_recurrences(
|
||||
recurring_events.filter(recurrence_end_date__isnull=False), excluded_datetimes
|
||||
)
|
||||
Event.create_events_recurrences(recurring_events, excluded_datetimes)
|
||||
|
||||
def get_booking_form_url(self):
|
||||
if not self.booking_form_url:
|
||||
|
@ -1548,7 +1546,12 @@ class Event(models.Model):
|
|||
null=True,
|
||||
)
|
||||
recurrence_week_interval = models.IntegerField(_('Repeat'), choices=INTERVAL_CHOICES, default=1)
|
||||
recurrence_end_date = models.DateField(_('Recurrence end date'), null=True, blank=True)
|
||||
recurrence_end_date = models.DateField(
|
||||
_('Recurrence end date'),
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text=_('If left blank, a one-year maximal booking delay will be applied for this event.'),
|
||||
)
|
||||
primary_event = models.ForeignKey('self', null=True, on_delete=models.CASCADE, related_name='recurrences')
|
||||
duration = models.PositiveIntegerField(_('Duration (in minutes)'), default=None, null=True, blank=True)
|
||||
publication_datetime = models.DateTimeField(_('Publication date/time'), blank=True, null=True)
|
||||
|
@ -1618,8 +1621,9 @@ class Event(models.Model):
|
|||
self.recurrences.update(**update_fields)
|
||||
yield
|
||||
|
||||
if self.recurrence_end_date:
|
||||
self.recurrences.filter(start_datetime__gt=self.recurrence_end_date).delete()
|
||||
if self.recurrence_days:
|
||||
if self.recurrence_end_date:
|
||||
self.recurrences.filter(start_datetime__gt=self.recurrence_end_date).delete()
|
||||
excluded_datetimes = [evt.datetime_slug for evt in self.recurrences.all()]
|
||||
self.create_all_recurrences(excluded_datetimes)
|
||||
|
||||
|
@ -1739,9 +1743,10 @@ class Event(models.Model):
|
|||
else:
|
||||
event = cls(**data)
|
||||
event.save()
|
||||
if event.recurrence_days and event.recurrence_end_date:
|
||||
if event.recurrence_days:
|
||||
event.refresh_from_db()
|
||||
event.recurrences.filter(start_datetime__gt=event.recurrence_end_date).delete()
|
||||
if event.recurrence_end_date:
|
||||
event.recurrences.filter(start_datetime__gt=event.recurrence_end_date).delete()
|
||||
update_fields = {
|
||||
field: getattr(event, field)
|
||||
for field in [
|
||||
|
@ -1975,6 +1980,8 @@ class Event(models.Model):
|
|||
recurrence_rule['until'] = datetime.datetime.combine(
|
||||
self.recurrence_end_date, datetime.time(0, 0)
|
||||
)
|
||||
else:
|
||||
recurrence_rule['until'] = make_naive(now() + datetime.timedelta(days=365))
|
||||
return recurrence_rule
|
||||
|
||||
def has_recurrences_booked(self, after=None):
|
||||
|
@ -1991,7 +1998,10 @@ class Event(models.Model):
|
|||
def create_events_recurrences(cls, events, excluded_datetimes=None):
|
||||
recurrences = []
|
||||
for event in events:
|
||||
max_datetime = datetime.datetime.combine(event.recurrence_end_date, datetime.time(0, 0))
|
||||
if event.recurrence_end_date:
|
||||
max_datetime = datetime.datetime.combine(event.recurrence_end_date, datetime.time(0, 0))
|
||||
else:
|
||||
max_datetime = make_naive(now() + datetime.timedelta(days=365))
|
||||
recurrences.extend(
|
||||
event.get_recurrences(
|
||||
localtime(event.start_datetime), make_aware(max_datetime), excluded_datetimes
|
||||
|
|
|
@ -2470,7 +2470,7 @@ class EventsAPI(APIView):
|
|||
raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
|
||||
payload = serializer.validated_data
|
||||
event = Event.objects.create(agenda=agenda, **payload)
|
||||
if event.recurrence_days and event.recurrence_end_date:
|
||||
if event.recurrence_days:
|
||||
event.create_all_recurrences()
|
||||
return Response({'err': 0, 'data': get_event_detail(request, event)})
|
||||
|
||||
|
|
|
@ -235,7 +235,7 @@ class NewEventForm(forms.ModelForm):
|
|||
def save(self, *args, **kwargs):
|
||||
with transaction.atomic():
|
||||
event = super().save(*args, **kwargs)
|
||||
if event.recurrence_end_date:
|
||||
if event.recurrence_days:
|
||||
event.create_all_recurrences()
|
||||
return event
|
||||
|
||||
|
|
|
@ -185,11 +185,12 @@ def test_string_or_list_serialiser(app, user, days_in, days_out, err_msg):
|
|||
'start_datetime': '2022-02-03 16:00',
|
||||
'places': '1',
|
||||
'recurrence_days': days_in,
|
||||
'recurrence_end_date': '2022-02-13',
|
||||
}
|
||||
if not err_msg:
|
||||
resp = app.post_json(api_url, params=params)
|
||||
assert not resp.json['err']
|
||||
assert Event.objects.get().recurrence_days == days_out
|
||||
assert Event.objects.get(primary_event__isnull=True).recurrence_days == days_out
|
||||
else:
|
||||
resp = app.post_json(api_url, params=params, status=400)
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
|
@ -316,13 +317,13 @@ def test_add_event(app, user):
|
|||
params = {
|
||||
'start_datetime': '2021-11-15 15:38',
|
||||
'places': 12,
|
||||
'recurrence_days': '0,3,5',
|
||||
'recurrence_days': '3',
|
||||
'recurrence_week_interval': '2',
|
||||
'description': 'A recurrent event',
|
||||
}
|
||||
assert Event.objects.filter(agenda=agenda).count() == 2
|
||||
resp = app.post_json(api_url, params=params)
|
||||
assert Event.objects.filter(agenda=agenda).count() == 3
|
||||
assert Event.objects.filter(agenda=agenda).count() == 28
|
||||
assert not resp.json['err']
|
||||
assert resp.json['data']['id'] == 'foo-bar-event-1'
|
||||
assert {'api', 'disabled', 'places'}.isdisjoint(resp.json['data'].keys())
|
||||
|
@ -331,7 +332,7 @@ def test_add_event(app, user):
|
|||
)
|
||||
event = Event.objects.filter(agenda=agenda).get(slug='foo-bar-event-1')
|
||||
assert event.description == 'A recurrent event'
|
||||
assert event.recurrence_days == [0, 3, 5]
|
||||
assert event.recurrence_days == [3]
|
||||
assert event.recurrence_week_interval == 2
|
||||
assert event.recurrence_end_date is None
|
||||
|
||||
|
@ -352,7 +353,7 @@ def test_add_event(app, user):
|
|||
resp.json['data'].keys()
|
||||
)
|
||||
event = Event.objects.filter(agenda=agenda).get(slug='foo-bar-event-2')
|
||||
assert Event.objects.filter(agenda=agenda).count() == 13
|
||||
assert Event.objects.filter(agenda=agenda).count() == 38
|
||||
assert event.description == 'A recurrent event having recurrences'
|
||||
assert event.recurrence_days == [0, 3, 5]
|
||||
assert event.recurrence_week_interval == 2
|
||||
|
|
|
@ -93,8 +93,9 @@ def test_add_recurring_event(app, admin_user):
|
|||
resp.form['frequency'] = 'recurring'
|
||||
resp.form.submit().follow()
|
||||
|
||||
event = Event.objects.get()
|
||||
event = Event.objects.get(primary_event__isnull=True)
|
||||
assert event.recurrence_days == [1]
|
||||
assert Event.objects.filter(primary_event=event).count() == 49
|
||||
event.delete()
|
||||
|
||||
# add recurring event with end date
|
||||
|
@ -284,6 +285,17 @@ def test_edit_recurring_event(settings, app, admin_user, freezer):
|
|||
resp.form['recurrence_days'] = [localtime().weekday()]
|
||||
resp = resp.form.submit()
|
||||
|
||||
# no end date, events are created for the year to come
|
||||
assert Event.objects.count() == 54
|
||||
assert Event.objects.last().start_datetime.strftime('%Y-%m-%d') == '2022-01-11'
|
||||
|
||||
# specifying end date removes events
|
||||
resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
|
||||
resp.form['recurrence_end_date'] = '2021-06-01'
|
||||
resp = resp.form.submit()
|
||||
assert Event.objects.count() == 21
|
||||
assert Event.objects.last().start_datetime.strftime('%Y-%m-%d') == '2021-05-25'
|
||||
|
||||
# detail page doesn't exist
|
||||
resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.id, event.id), status=404)
|
||||
|
||||
|
@ -317,14 +329,14 @@ def test_edit_recurring_event(settings, app, admin_user, freezer):
|
|||
assert not Event.objects.filter(primary_event=event).exists()
|
||||
|
||||
# same goes with changing slug
|
||||
event.recurrence = 'weekly'
|
||||
event.recurrence_days = [1]
|
||||
event.save()
|
||||
event_recurrence = event.get_or_create_event_recurrence(event.start_datetime)
|
||||
assert Event.objects.filter(primary_event=event).exists()
|
||||
assert Event.objects.filter(primary_event=event, slug__startswith='foo-bar-event').exists()
|
||||
resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
|
||||
resp.form['slug'] = 'hop'
|
||||
resp = resp.form.submit().follow()
|
||||
assert not Event.objects.filter(primary_event=event).exists()
|
||||
assert not Event.objects.filter(primary_event=event, slug__startswith='foo-bar-event').exists()
|
||||
|
||||
# changing recurring attribute or slug is forbidden if there are bookings for future recurrences
|
||||
event_recurrence = event.get_or_create_event_recurrence(event.start_datetime + datetime.timedelta(days=7))
|
||||
|
|
|
@ -2400,21 +2400,17 @@ def test_recurring_events_exceptions_update_recurrences(freezer):
|
|||
places=5,
|
||||
recurrence_end_date=datetime.date(year=2021, month=6, day=1),
|
||||
)
|
||||
Event.create_events_recurrences([daily_event, weekly_event])
|
||||
|
||||
daily_event_no_end_date = Event.objects.create(
|
||||
weekly_event_no_end_date = Event.objects.create(
|
||||
agenda=agenda,
|
||||
start_datetime=now() + datetime.timedelta(hours=2),
|
||||
recurrence_days=list(range(7)),
|
||||
recurrence_days=[now().weekday()],
|
||||
places=5,
|
||||
)
|
||||
daily_event_no_end_date.refresh_from_db()
|
||||
# create one recurrence on 07/05
|
||||
daily_event_no_end_date.get_or_create_event_recurrence(now() + datetime.timedelta(days=6, hours=2))
|
||||
Event.create_events_recurrences([daily_event, weekly_event, weekly_event_no_end_date])
|
||||
|
||||
assert Event.objects.filter(primary_event=daily_event).count() == 7
|
||||
assert Event.objects.filter(primary_event=weekly_event).count() == 5
|
||||
assert Event.objects.filter(primary_event=daily_event_no_end_date).count() == 1
|
||||
assert Event.objects.filter(primary_event=weekly_event_no_end_date).count() == 53
|
||||
|
||||
time_period_exception = TimePeriodException.objects.create(
|
||||
desk=desk,
|
||||
|
@ -2424,25 +2420,30 @@ def test_recurring_events_exceptions_update_recurrences(freezer):
|
|||
agenda.update_event_recurrences()
|
||||
assert Event.objects.filter(primary_event=daily_event).count() == 4
|
||||
assert Event.objects.filter(primary_event=weekly_event).count() == 4
|
||||
assert Event.objects.filter(primary_event=daily_event_no_end_date).count() == 0
|
||||
assert Event.objects.filter(primary_event=weekly_event_no_end_date).count() == 52
|
||||
|
||||
time_period_exception.delete()
|
||||
agenda.update_event_recurrences()
|
||||
assert Event.objects.filter(primary_event=daily_event).count() == 7
|
||||
assert Event.objects.filter(primary_event=weekly_event).count() == 5
|
||||
assert Event.objects.filter(primary_event=daily_event_no_end_date).count() == 0
|
||||
assert Event.objects.filter(primary_event=weekly_event_no_end_date).count() == 53
|
||||
|
||||
event = daily_event_no_end_date.get_or_create_event_recurrence(
|
||||
now() + datetime.timedelta(days=6, hours=2)
|
||||
event = weekly_event_no_end_date.get_or_create_event_recurrence(
|
||||
now() + datetime.timedelta(days=7, hours=2)
|
||||
)
|
||||
Booking.objects.create(event=event)
|
||||
time_period_exception.save()
|
||||
|
||||
agenda.update_event_recurrences()
|
||||
assert Booking.objects.count() == 1
|
||||
assert Event.objects.filter(primary_event=daily_event_no_end_date).count() == 1
|
||||
assert Event.objects.filter(primary_event=weekly_event_no_end_date).count() == 53
|
||||
assert agenda.recurrence_exceptions_report.events.get() == event
|
||||
|
||||
# no recurrence end date means new events are created as time moves on
|
||||
freezer.move_to('2021-06-01 12:00')
|
||||
agenda.update_event_recurrences()
|
||||
assert Event.objects.filter(primary_event=weekly_event_no_end_date).count() == 57
|
||||
|
||||
|
||||
def test_recurring_events_exceptions_update_recurrences_start_datetime_modified(freezer):
|
||||
freezer.move_to('2021-09-06 12:00') # Monday
|
||||
|
|
|
@ -227,14 +227,12 @@ def test_import_export_recurring_event(app, freezer):
|
|||
event = Event.objects.create(
|
||||
agenda=agenda,
|
||||
start_datetime=now(),
|
||||
recurrence_days=list(range(7)),
|
||||
recurrence_days=[now().weekday()],
|
||||
recurrence_week_interval=2,
|
||||
places=10,
|
||||
slug='test',
|
||||
)
|
||||
event.refresh_from_db()
|
||||
event.get_or_create_event_recurrence(event.start_datetime + datetime.timedelta(days=3))
|
||||
assert Event.objects.count() == 2
|
||||
assert Event.objects.count() == 1
|
||||
|
||||
output = get_output_of_command('export_site')
|
||||
assert len(json.loads(output)['agendas']) == 1
|
||||
|
@ -246,13 +244,12 @@ def test_import_export_recurring_event(app, freezer):
|
|||
call_command('import_site', f.name)
|
||||
|
||||
assert Agenda.objects.count() == 1
|
||||
assert Event.objects.count() == 1
|
||||
event = Agenda.objects.get(label='Foo Bar').event_set.first()
|
||||
assert event.primary_event is None
|
||||
assert event.recurrence_days == list(range(7))
|
||||
assert Event.objects.count() == 28
|
||||
event = Agenda.objects.get(label='Foo Bar').event_set.filter(primary_event__isnull=True).get()
|
||||
assert event.recurrence_days == [now().weekday()]
|
||||
assert event.recurrence_week_interval == 2
|
||||
|
||||
# importing event with end recurrence date creates recurrences
|
||||
# importing event with end recurrence date removes recurrences
|
||||
event.recurrence_end_date = now() + datetime.timedelta(days=7)
|
||||
event.recurrence_week_interval = 1
|
||||
event.save()
|
||||
|
@ -265,7 +262,7 @@ def test_import_export_recurring_event(app, freezer):
|
|||
call_command('import_site', f.name)
|
||||
|
||||
event = Event.objects.get(slug='test')
|
||||
assert Event.objects.filter(primary_event=event).count() == 7
|
||||
assert Event.objects.filter(primary_event=event).count() == 1
|
||||
|
||||
# import again
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
|
@ -274,7 +271,7 @@ def test_import_export_recurring_event(app, freezer):
|
|||
call_command('import_site', f.name)
|
||||
|
||||
event = Event.objects.get(slug='test')
|
||||
assert Event.objects.filter(primary_event=event).count() == 7
|
||||
assert Event.objects.filter(primary_event=event).count() == 1
|
||||
|
||||
# import again but change places
|
||||
payload = json.loads(output)
|
||||
|
@ -286,7 +283,7 @@ def test_import_export_recurring_event(app, freezer):
|
|||
|
||||
event = Event.objects.get(slug='test')
|
||||
assert event.places == 42
|
||||
assert Event.objects.filter(primary_event=event, places=42).count() == 7
|
||||
assert Event.objects.filter(primary_event=event, places=42).count() == 1
|
||||
|
||||
|
||||
def test_import_export_meetings_agenda_options(app):
|
||||
|
|
Loading…
Reference in New Issue