manager: handle edition/deletion of recurring event (#41663)
This commit is contained in:
parent
e09281624b
commit
b0d89df301
|
@ -1103,11 +1103,14 @@ class Event(models.Model):
|
|||
today = localtime(now()).date()
|
||||
event_day = localtime(self.start_datetime).date()
|
||||
days_to_event = event_day - today
|
||||
if days_to_event < datetime.timedelta(days=self.agenda.minimal_booking_delay):
|
||||
return False
|
||||
if self.agenda.maximal_booking_delay:
|
||||
if days_to_event >= datetime.timedelta(days=self.agenda.maximal_booking_delay):
|
||||
return False
|
||||
if self.recurrence_rule is not None:
|
||||
# bookable recurrences probably exist
|
||||
return True
|
||||
if days_to_event < datetime.timedelta(days=self.agenda.minimal_booking_delay):
|
||||
return False
|
||||
if self.start_datetime < now():
|
||||
# past the event date, we may want in the future to allow for some
|
||||
# extra late booking but it's forbidden for now.
|
||||
|
@ -1323,6 +1326,11 @@ class Event(models.Model):
|
|||
return None
|
||||
return rrule
|
||||
|
||||
def has_recurrences_booked(self):
|
||||
return Booking.objects.filter(
|
||||
event__primary_event=self, event__start_datetime__gt=now(), cancellation_datetime__isnull=True
|
||||
).exists()
|
||||
|
||||
|
||||
class BookingColor(models.Model):
|
||||
COLOR_COUNT = 8
|
||||
|
|
|
@ -23,6 +23,7 @@ from django import forms
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.exceptions import FieldDoesNotExist
|
||||
from django.db import transaction
|
||||
from django.forms import ValidationError
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.six import StringIO
|
||||
|
@ -177,6 +178,8 @@ class NewEventForm(forms.ModelForm):
|
|||
|
||||
|
||||
class EventForm(forms.ModelForm):
|
||||
protected_fields = ('repeat', 'slug', 'start_datetime')
|
||||
|
||||
class Meta:
|
||||
model = Event
|
||||
widgets = {
|
||||
|
@ -199,6 +202,28 @@ class EventForm(forms.ModelForm):
|
|||
'start_datetime': SplitDateTimeField,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.instance.recurrence_rule and self.instance.has_recurrences_booked():
|
||||
for field in self.protected_fields:
|
||||
self.fields[field].disabled = True
|
||||
self.fields[field].help_text = _(
|
||||
'This field cannot be modified because some recurrences have bookings attached to them.'
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
with transaction.atomic():
|
||||
if any(field for field in self.changed_data if field in self.protected_fields):
|
||||
self.instance.recurrences.all().delete()
|
||||
elif self.instance.recurrence_rule:
|
||||
update_fields = {
|
||||
field: value
|
||||
for field, value in self.cleaned_data.items()
|
||||
if field not in self.protected_fields
|
||||
}
|
||||
self.instance.recurrences.update(**update_fields)
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class AgendaResourceForm(forms.Form):
|
||||
resource = forms.ModelChoiceField(label=_('Resource'), queryset=Resource.objects.none())
|
||||
|
|
|
@ -29,12 +29,16 @@
|
|||
</h2>
|
||||
<span class="actions">
|
||||
{% if user_can_manage %}
|
||||
{% if not object.primary_event %}
|
||||
<a rel="popup" href="{% url 'chrono-manager-event-delete' pk=object.agenda.id event_pk=object.id %}">{% trans 'Delete' %}</a>
|
||||
{% endif %}
|
||||
{% if not event.cancellation_status %}
|
||||
<a rel="popup" href="{% url 'chrono-manager-event-cancel' pk=agenda.pk event_pk=event.pk %}?next={{ request.path }}">{% trans "Cancel" %}</a>
|
||||
{% endif %}
|
||||
{% if not object.primary_event %}
|
||||
<a href="{% url 'chrono-manager-event-edit' pk=agenda.id event_pk=object.id %}">{% trans "Options" %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if object.agenda.booking_form_url %}
|
||||
<a href="{{ object.agenda.get_booking_form_url }}?agenda={{ object.agenda.slug }}&event={{ event.slug }}">{% trans "Booking form" %}</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -1713,6 +1713,7 @@ class EventDeleteView(ManagedAgendaMixin, DeleteView):
|
|||
context['cannot_delete'] = bool(
|
||||
self.object.booking_set.filter(cancellation_datetime__isnull=True).exists()
|
||||
and self.object.start_datetime > now()
|
||||
or self.object.has_recurrences_booked()
|
||||
)
|
||||
return context
|
||||
|
||||
|
|
|
@ -1454,6 +1454,74 @@ def test_edit_event_as_manager(app, manager_user):
|
|||
assert event.publication_date is None
|
||||
|
||||
|
||||
def test_edit_recurring_event(settings, app, admin_user, freezer):
|
||||
freezer.move_to('2021-01-12 12:10')
|
||||
agenda = Agenda.objects.create(
|
||||
label='Foo bar', kind='events', minimal_booking_delay=15, maximal_booking_delay=30
|
||||
)
|
||||
event = Event.objects.create(start_datetime=now(), places=10, agenda=agenda)
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
|
||||
resp.form['repeat'] = 'weekly'
|
||||
resp = resp.form.submit()
|
||||
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
|
||||
assert 'Weekly on Tuesday at 1:10 p.m.' in resp.text
|
||||
# event is bookable regardless of minimal_booking_delay, since it has bookable recurrences
|
||||
assert len(resp.pyquery.find('.bookable')) == 1
|
||||
|
||||
# maximal_booking_delay is accounted for, because no recurrences are bookable
|
||||
freezer.move_to('2020-11-12')
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
|
||||
assert len(resp.pyquery.find('.not-bookable')) == 1
|
||||
|
||||
# editing recurring event updates event recurrences
|
||||
event.refresh_from_db()
|
||||
event_recurrence = event.get_or_create_event_recurrence(event.start_datetime)
|
||||
resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
|
||||
resp.form['places'] = 20
|
||||
resp = resp.form.submit().follow()
|
||||
event_recurrence.refresh_from_db()
|
||||
assert event_recurrence.places == 20
|
||||
|
||||
# changing recurrence attribute removes event recurrences
|
||||
resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
|
||||
resp.form['repeat'] = ''
|
||||
resp = resp.form.submit().follow()
|
||||
assert not Event.objects.filter(primary_event=event).exists()
|
||||
|
||||
# same goes with changing slug
|
||||
event.recurrence = 'weekly'
|
||||
event.save()
|
||||
event_recurrence = event.get_or_create_event_recurrence(event.start_datetime)
|
||||
assert Event.objects.filter(primary_event=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()
|
||||
|
||||
# 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))
|
||||
Booking.objects.create(event=event_recurrence)
|
||||
resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
|
||||
assert 'disabled' in resp.form['repeat'].attrs
|
||||
assert 'disabled' in resp.form['slug'].attrs
|
||||
assert 'disabled' in resp.form['start_datetime_0'].attrs
|
||||
assert 'disabled' in resp.form['start_datetime_1'].attrs
|
||||
|
||||
# changing it anyway doesn't work
|
||||
resp.form['slug'] = 'changed'
|
||||
resp = resp.form.submit()
|
||||
assert not Event.objects.filter(slug='changed').exists()
|
||||
|
||||
# individually editing event recurrence is not supported, except for cancellation
|
||||
resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.id, event_recurrence.id))
|
||||
assert 'Cancel' in resp.text
|
||||
assert 'Options' not in resp.text
|
||||
assert 'Delete' not in resp.text
|
||||
|
||||
|
||||
def test_booked_places(app, admin_user):
|
||||
agenda = Agenda(label=u'Foo bar')
|
||||
agenda.save()
|
||||
|
@ -1543,6 +1611,40 @@ def test_delete_busy_event(app, admin_user):
|
|||
resp = resp.form.submit(status=403)
|
||||
|
||||
|
||||
def test_delete_recurring_event(app, admin_user, freezer):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
||||
start_datetime = now() + datetime.timedelta(days=10)
|
||||
event = Event.objects.create(start_datetime=start_datetime, places=10, agenda=agenda, repeat='weekly')
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
|
||||
resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
|
||||
resp = resp.click('Delete')
|
||||
assert 'Are you sure you want to delete this event?' in resp.text
|
||||
|
||||
event_recurrence = event.get_or_create_event_recurrence(event.start_datetime)
|
||||
booking = Booking.objects.create(event=event_recurrence)
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
|
||||
resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
|
||||
resp = resp.click('Delete')
|
||||
assert 'This cannot be removed' in resp.text
|
||||
|
||||
booking.cancellation_datetime = now()
|
||||
booking.save()
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
|
||||
resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
|
||||
resp = resp.click('Delete')
|
||||
assert 'Are you sure you want to delete this event?' in resp.text
|
||||
|
||||
booking.cancellation_datetime = None
|
||||
booking.save()
|
||||
freezer.move_to(now() + datetime.timedelta(days=11))
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
|
||||
resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
|
||||
resp = resp.click('Delete')
|
||||
assert 'Are you sure you want to delete this event?' in resp.text
|
||||
|
||||
|
||||
def test_delete_event_as_manager(app, manager_user):
|
||||
agenda = Agenda(label=u'Foo bar')
|
||||
agenda.edit_role = manager_user.groups.all()[0]
|
||||
|
|
Loading…
Reference in New Issue