manager: create event recurrences when end date is specified (#51218)

This commit is contained in:
Valentin Deniaud 2021-01-19 15:35:31 +01:00
parent cfe2742d1f
commit 86018e908e
5 changed files with 85 additions and 16 deletions

View File

@ -590,7 +590,7 @@ class Agenda(models.Model):
else:
recurring_events = self.event_set.filter(recurrence_rule__isnull=False)
for event in recurring_events:
events.extend(event.get_recurrences(min_start, max_start, excluded_datetimes))
events.extend(event.get_recurrences(min_start, max_start, excluded_datetimes, slug_separator=':'))
events.sort(key=lambda x: [getattr(x, field) for field in Event._meta.ordering])
return events
@ -1196,10 +1196,13 @@ class Event(models.Model):
)
data = clean_import_data(cls, data)
if data.get('slug'):
cls.objects.update_or_create(slug=data['slug'], defaults=data)
return
event = cls(**data)
event.save()
event, _ = cls.objects.update_or_create(slug=data['slug'], defaults=data)
else:
event = cls(**data)
event.save()
if event.recurrence_rule and event.recurrence_end_date:
event.refresh_from_db()
event.create_all_recurrences()
def export_json(self):
recurrence_end_date = (
@ -1253,8 +1256,6 @@ class Event(models.Model):
raise ValueError('Multiple events found for specified datetime.')
event = events[0]
event.slug = event.slug.replace(':', '--')
with transaction.atomic():
try:
return Event.objects.get(agenda=self.agenda, slug=event.slug)
@ -1262,7 +1263,7 @@ class Event(models.Model):
event.save()
return event
def get_recurrences(self, min_datetime, max_datetime, excluded_datetimes=None):
def get_recurrences(self, min_datetime, max_datetime, excluded_datetimes=None, slug_separator='--'):
recurrences = []
rrule_set = rruleset()
# do not generate recurrences for existing events
@ -1298,7 +1299,11 @@ class Event(models.Model):
event = copy.copy(event_base)
# add timezone back
aware_start_datetime = make_aware(start_datetime)
event.slug = '%s:%s' % (event.slug, aware_start_datetime.strftime('%Y-%m-%d-%H%M'))
event.slug = '%s%s%s' % (
event.slug,
slug_separator,
aware_start_datetime.strftime('%Y-%m-%d-%H%M'),
)
event.start_datetime = aware_start_datetime.astimezone(utc)
recurrences.append(event)
@ -1340,6 +1345,11 @@ class Event(models.Model):
event__primary_event=self, event__start_datetime__gt=now(), cancellation_datetime__isnull=True
).exists()
def create_all_recurrences(self, excluded_datetimes=None):
max_datetime = datetime.datetime.combine(self.recurrence_end_date, datetime.time(0, 0))
recurrences = self.get_recurrences(localtime(now()), make_aware(max_datetime), excluded_datetimes)
Event.objects.bulk_create(recurrences)
class BookingColor(models.Model):
COLOR_COUNT = 8

View File

@ -27,8 +27,7 @@ from django.db import transaction
from django.forms import ValidationError
from django.utils.encoding import force_text
from django.utils.six import StringIO
from django.utils.timezone import make_aware
from django.utils.timezone import now
from django.utils.timezone import now, localtime, make_aware, make_naive
from django.utils.translation import ugettext_lazy as _
from chrono.agendas.models import (
@ -224,7 +223,16 @@ class EventForm(forms.ModelForm):
if field not in self.protected_fields
}
self.instance.recurrences.update(**update_fields)
return super().save(*args, **kwargs)
event = super().save(*args, **kwargs)
if event.recurrence_end_date:
self.instance.recurrences.filter(start_datetime__gt=event.recurrence_end_date).delete()
excluded_datetimes = [
make_naive(dt)
for dt in self.instance.recurrences.values_list('start_datetime', flat=True)
]
event.create_all_recurrences(excluded_datetimes)
return event
class AgendaResourceForm(forms.Form):

View File

@ -1867,7 +1867,7 @@ def test_recurring_events(freezer):
assert len(recurrences) == 3
first_event = recurrences[0]
assert first_event.slug == event.slug + ':2021-01-06-1300'
assert first_event.slug == event.slug + '--2021-01-06-1300'
event_json = event.export_json()
first_event_json = first_event.export_json()
@ -1877,7 +1877,7 @@ def test_recurring_events(freezer):
second_event = recurrences[1]
assert second_event.start_datetime == first_event.start_datetime + datetime.timedelta(days=7)
assert second_event.start_datetime.weekday() == first_event.start_datetime.weekday()
assert second_event.slug == 'event:2021-01-13-1300'
assert second_event.slug == 'event--2021-01-13-1300'
different_fields = ['slug', 'start_datetime']
second_event_json = second_event.export_json()
@ -1901,8 +1901,8 @@ def test_recurring_events_dst(freezer, settings):
recurrences = event.get_recurrences(dt, dt + datetime.timedelta(days=8))
event_before_dst, event_after_dst = recurrences
assert event_before_dst.start_datetime.hour + 1 == event_after_dst.start_datetime.hour
assert event_before_dst.slug == 'agenda-event:2020-10-24-1400'
assert event_after_dst.slug == 'agenda-event:2020-10-31-1400'
assert event_before_dst.slug == 'agenda-event--2020-10-24-1400'
assert event_after_dst.slug == 'agenda-event--2020-10-31-1400'
freezer.move_to('2020-11-24 12:00')
new_recurrences = event.get_recurrences(dt, dt + datetime.timedelta(days=8))

View File

@ -200,6 +200,7 @@ def test_import_export_recurring_event(app, freezer):
start_datetime=now(),
repeat='daily',
places=10,
slug='test',
)
event.refresh_from_db()
event.get_or_create_event_recurrence(event.start_datetime + datetime.timedelta(days=3))
@ -221,6 +222,20 @@ def test_import_export_recurring_event(app, freezer):
assert event.repeat == 'daily'
assert event.recurrence_rule == {'freq': DAILY}
# importing event with end recurrence date creates recurrences
event.recurrence_end_date = now() + datetime.timedelta(days=7)
event.save()
output = get_output_of_command('export_site')
import_site(data={}, clean=True)
with tempfile.NamedTemporaryFile() as f:
f.write(force_bytes(output))
f.flush()
call_command('import_site', f.name)
event = Event.objects.get(slug='test')
assert Event.objects.filter(primary_event=event).count() == 7
def test_import_export_permissions(app):
meetings_agenda = Agenda.objects.create(label='Foo Bar', kind='meetings')

View File

@ -1522,6 +1522,42 @@ def test_edit_recurring_event(settings, app, admin_user, freezer):
assert 'Delete' not in resp.text
def test_edit_recurring_event_with_end_date(settings, app, admin_user, freezer):
freezer.move_to('2021-01-12 12:10')
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(start_datetime=now(), places=10, repeat='daily', agenda=agenda)
app = login(app)
resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
resp.form['recurrence_end_date'] = (localtime() + datetime.timedelta(days=5)).strftime('%Y-%m-%d')
resp = resp.form.submit()
# recurrences are created automatically
event = Event.objects.get(recurrence_rule__isnull=False)
assert Event.objects.filter(primary_event=event).count() == 5
assert Event.objects.filter(primary_event=event, start_datetime=now()).exists()
resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
resp.form['start_datetime_1'] = (localtime() + datetime.timedelta(hours=1)).strftime('%H:%M')
resp = resp.form.submit()
assert Event.objects.filter(primary_event=event).count() == 5
assert Event.objects.filter(
primary_event=event, start_datetime=now() + datetime.timedelta(hours=1)
).exists()
# old recurrences were deleted
assert not Event.objects.filter(primary_event=event, start_datetime=now()).exists()
resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
resp.form['recurrence_end_date'] = (localtime() + datetime.timedelta(days=6)).strftime('%Y-%m-%d')
resp = resp.form.submit()
assert Event.objects.filter(primary_event=event).count() == 6
resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
resp.form['recurrence_end_date'] = (localtime() + datetime.timedelta(days=4)).strftime('%Y-%m-%d')
resp = resp.form.submit()
assert Event.objects.filter(primary_event=event).count() == 4
def test_booked_places(app, admin_user):
agenda = Agenda(label=u'Foo bar')
agenda.save()