diff --git a/ephios/core/forms/events.py b/ephios/core/forms/events.py index e55e578fe..b9ffa5965 100644 --- a/ephios/core/forms/events.py +++ b/ephios/core/forms/events.py @@ -300,3 +300,26 @@ def clean(self): ): raise ValidationError(_("You cannot send an empty mail.")) return super().clean() + + +class EventCancellationForm(forms.Form): + explanation = forms.CharField( + required=False, + widget=forms.Textarea, + label=_("Explanation"), + help_text=_( + "All participants will be notified about the cancellation and this message will be included." + ), + ) + + def __init__(self, *args, **kwargs): + self.event = kwargs.pop("event") + super().__init__(*args, **kwargs) + self.helper = FormHelper(self) + self.helper.layout = Layout( + Field("explanation"), + FormActions( + Submit("submit", _("Cancel event"), css_class="float-end"), + AbortLink(href=self.event.get_absolute_url()), + ), + ) diff --git a/ephios/core/services/notifications/types.py b/ephios/core/services/notifications/types.py index ce798d492..8a399cb5c 100644 --- a/ephios/core/services/notifications/types.py +++ b/ephios/core/services/notifications/types.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional from urllib.parse import urljoin from django.conf import settings @@ -497,6 +497,48 @@ def as_plaintext(cls, notification): return notification.data.get("content") +class EventCancellationNotification(AbstractNotificationHandler): + slug = "ephios_event_cancellation" + title = _("An event has been cancelled") + unsubscribe_allowed = False + + @classmethod + def send(cls, event: Event, explanation: Optional[str]): + participants = set() + for shift in event.shifts.all(): + participants.update(shift.get_participants()) + notifications = [] + for participant in participants: + notifications.append( + Notification( + slug=cls.slug, + data={ + "email": participant.email, + "event_title": event.title, + "start_time": date_format(event.get_start_time(), "SHORT_DATETIME_FORMAT"), + "end_time": date_format(event.get_start_time(), "SHORT_DATETIME_FORMAT"), + "explanation": explanation, + }, + ) + ) + Notification.objects.bulk_create(notifications) + + @classmethod + def get_subject(cls, notification): + return _("{title} has been cancelled").format(title=notification.data.get("event_title")) + + @classmethod + def as_plaintext(cls, notification): + result = _("{title} ({start_time} - {end_time}) has been cancelled.").format( + title=notification.data.get("event_title"), + start_time=notification.data.get("start_time"), + end_time=notification.data.get("end_time"), + ) + if explanation := notification.data.get("explanation"): + result += _(" Further information:\n{explanation}").format(explanation=explanation) + return result + + class ConsequenceApprovedNotification(AbstractNotificationHandler): slug = "ephios_consequence_approved" title = _("Your request has been approved") @@ -549,6 +591,7 @@ def as_plaintext(cls, notification): NewEventNotification, EventReminderNotification, CustomEventParticipantNotification, + EventCancellationNotification, ConsequenceApprovedNotification, ConsequenceDeniedNotification, ] diff --git a/ephios/core/templates/core/event_cancellation.html b/ephios/core/templates/core/event_cancellation.html new file mode 100644 index 000000000..848305403 --- /dev/null +++ b/ephios/core/templates/core/event_cancellation.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% load crispy_forms_tags %} +{% load i18n %} + +{% block title %} + {% translate "Cancel event" %} +{% endblock %} + +{% block content %} +

{% blocktranslate %}Cancel event "{{ event }}"{% endblocktranslate %}

+ {% crispy form %} +{% endblock %} diff --git a/ephios/core/templates/core/event_detail.html b/ephios/core/templates/core/event_detail.html index b98bd4871..8f79937cf 100644 --- a/ephios/core/templates/core/event_detail.html +++ b/ephios/core/templates/core/event_detail.html @@ -56,6 +56,12 @@

{% trans "Add another shift" %} +
  • + + {% translate "Cancel event" %} + +
  • diff --git a/ephios/core/urls.py b/ephios/core/urls.py index 822ce6357..f613aa226 100644 --- a/ephios/core/urls.py +++ b/ephios/core/urls.py @@ -22,6 +22,7 @@ from ephios.core.views.consequences import ConsequenceUpdateView from ephios.core.views.event import ( EventActivateView, + EventCancellationView, EventCopyView, EventCreateView, EventDeleteView, @@ -99,6 +100,7 @@ name="event_notifications", ), path("events//pdf/", pdf.EventDetailPDFView.as_view(), name="event_detail_pdf"), + path("events//cancel/", EventCancellationView.as_view(), name="event_cancel"), path( "events//copy/", EventCopyView.as_view(), diff --git a/ephios/core/views/event.py b/ephios/core/views/event.py index 78914c3bc..17c29c11a 100644 --- a/ephios/core/views/event.py +++ b/ephios/core/views/event.py @@ -33,10 +33,16 @@ from recurrence.forms import RecurrenceField from ephios.core.calendar import ShiftCalendar -from ephios.core.forms.events import EventDuplicationForm, EventForm, EventNotificationForm +from ephios.core.forms.events import ( + EventCancellationForm, + EventDuplicationForm, + EventForm, + EventNotificationForm, +) from ephios.core.models import AbstractParticipation, Event, EventType, Shift from ephios.core.services.notifications.types import ( CustomEventParticipantNotification, + EventCancellationNotification, EventReminderNotification, NewEventNotification, ) @@ -509,3 +515,26 @@ def form_valid(self, form): CustomEventParticipantNotification.send(self.object, form.cleaned_data["mail_content"]) messages.success(self.request, _("Notifications sent succesfully.")) return redirect(self.object.get_absolute_url()) + + +class EventCancellationView(CustomPermissionRequiredMixin, SingleObjectMixin, FormView): + model = Event + permission_required = "core.change_event" + template_name = "core/event_cancellation.html" + form_class = EventCancellationForm + + def get_form_kwargs(self): + return {**super().get_form_kwargs(), "event": self.object} + + def setup(self, request, *args, **kwargs): + super().setup(request, *args, **kwargs) + self.object = self.get_object() + + def form_valid(self, form): + EventCancellationNotification.send(self.object, form.cleaned_data["explanation"]) + event_title = self.object.title + self.object.delete() + messages.info( + self.request, _("Event {title} has been cancelled.").format(title=event_title) + ) + return redirect(reverse("core:event_list"))