From 3b1cd8e659316d46ae24f63dd92c69ce7a054c50 Mon Sep 17 00:00:00 2001 From: Bentrex95 <44967954+Bentrex95@users.noreply.github.com> Date: Wed, 23 Nov 2022 16:11:23 +0100 Subject: [PATCH] Waiting list: Allow transfer to other subevent (#2811) Co-authored-by: Richard Schreiber Co-authored-by: Raphael Michel --- src/pretix/control/forms/waitinglist.py | 56 +++++++++++++++++++ src/pretix/control/logdisplay.py | 1 + .../pretixcontrol/waitinglist/index.html | 11 +++- .../pretixcontrol/waitinglist/transfer.html | 23 ++++++++ src/pretix/control/urls.py | 2 + src/pretix/control/views/waitinglist.py | 42 +++++++++++++- 6 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 src/pretix/control/forms/waitinglist.py create mode 100644 src/pretix/control/templates/pretixcontrol/waitinglist/transfer.html diff --git a/src/pretix/control/forms/waitinglist.py b/src/pretix/control/forms/waitinglist.py new file mode 100644 index 0000000000..3a1b7f3bfb --- /dev/null +++ b/src/pretix/control/forms/waitinglist.py @@ -0,0 +1,56 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.urls import reverse +from django_scopes.forms import SafeModelChoiceField + +from pretix.base.forms import I18nModelForm +from pretix.base.models import WaitingListEntry +from pretix.control.forms.widgets import Select2 + + +class WaitingListEntryTransferForm(I18nModelForm): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + if self.event.has_subevents: + self.fields['subevent'].required = True + self.fields['subevent'].queryset = self.event.subevents.all() + self.fields['subevent'].widget = Select2( + attrs={ + 'data-model-select2': 'event', + 'data-select2-url': reverse('control:event.subevents.select2', kwargs={ + 'event': self.event.slug, + 'organizer': self.event.organizer.slug, + }), + } + ) + self.fields['subevent'].widget.choices = self.fields['subevent'].choices + + class Meta: + model = WaitingListEntry + fields = [ + 'subevent', + ] + field_classes = { + 'subevent': SafeModelChoiceField, + } diff --git a/src/pretix/control/logdisplay.py b/src/pretix/control/logdisplay.py index 118415ead1..89b26e782a 100644 --- a/src/pretix/control/logdisplay.py +++ b/src/pretix/control/logdisplay.py @@ -487,6 +487,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs): 'pretix.event.permissions.deleted': _('A user has been removed from the event team.'), 'pretix.waitinglist.voucher': _('A voucher has been sent to a person on the waiting list.'), 'pretix.event.orders.waitinglist.deleted': _('An entry has been removed from the waiting list.'), + 'pretix.event.orders.waitinglist.transferred': _('An entry has been transferred to another waiting list.'), 'pretix.event.orders.waitinglist.changed': _('An entry has been changed on the waiting list.'), 'pretix.event.orders.waitinglist.added': _('An entry has been added to the waiting list.'), 'pretix.team.created': _('The team has been created.'), diff --git a/src/pretix/control/templates/pretixcontrol/waitinglist/index.html b/src/pretix/control/templates/pretixcontrol/waitinglist/index.html index 5ffe065cb3..b34416d854 100644 --- a/src/pretix/control/templates/pretixcontrol/waitinglist/index.html +++ b/src/pretix/control/templates/pretixcontrol/waitinglist/index.html @@ -242,6 +242,13 @@ data-toggle="tooltip" title="{% trans "Move to the end of the list" %}"> + {% if request.event.has_subevents %} + + + + {% endif %} {% else %} + {% endif %} {% include "pretixcontrol/pagination.html" %} diff --git a/src/pretix/control/templates/pretixcontrol/waitinglist/transfer.html b/src/pretix/control/templates/pretixcontrol/waitinglist/transfer.html new file mode 100644 index 0000000000..ac44983166 --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/waitinglist/transfer.html @@ -0,0 +1,23 @@ +{% extends "pretixcontrol/event/base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% block title %}{% trans "Transfer entry" %}{% endblock %} +{% block content %} +

{% trans "Transfer entry" %}

+
+ {% csrf_token %} +

{% blocktrans trimmed context "subevent" %} + Please select the date to which the following waiting list entry should be + transferred: {{ entry }}? + {% endblocktrans %}

+ {% bootstrap_field form.subevent layout="control" %} +
+ + {% trans "Cancel" %} + + +
+
+{% endblock %} diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index 32ea9b9d9d..4bfdc24951 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -401,6 +401,8 @@ urlpatterns = [ re_path(r'^waitinglist/auto_assign$', waitinglist.AutoAssign.as_view(), name='event.orders.waitinglist.auto'), re_path(r'^waitinglist/(?P\d+)/delete$', waitinglist.EntryDelete.as_view(), name='event.orders.waitinglist.delete'), + re_path(r'^waitinglist/(?P\d+)/transfer$', waitinglist.EntryTransfer.as_view(), + name='event.orders.waitinglist.transfer'), re_path(r'^checkins/$', checkin.CheckinListView.as_view(), name='event.orders.checkins'), re_path(r'^checkinlists/$', checkin.CheckinListList.as_view(), name='event.orders.checkinlists'), re_path(r'^checkinlists/add$', checkin.CheckinListCreate.as_view(), name='event.orders.checkinlists.add'), diff --git a/src/pretix/control/views/waitinglist.py b/src/pretix/control/views/waitinglist.py index bb8c88d98a..4881aba7a1 100644 --- a/src/pretix/control/views/waitinglist.py +++ b/src/pretix/control/views/waitinglist.py @@ -40,7 +40,7 @@ from django.db import transaction from django.db.models import F, Max, Min, Q, Sum from django.db.models.functions import Coalesce from django.http import Http404, HttpResponse, HttpResponseRedirect -from django.shortcuts import redirect, render +from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.functional import cached_property from django.utils.http import is_safe_url @@ -54,9 +54,12 @@ from pretix.base.models import Item, Quota, WaitingListEntry from pretix.base.models.waitinglist import WaitingListException from pretix.base.services.waitinglist import assign_automatically from pretix.base.views.tasks import AsyncAction +from pretix.control.forms.waitinglist import WaitingListEntryTransferForm from pretix.control.permissions import EventPermissionRequiredMixin from pretix.control.views import PaginationMixin +from . import UpdateView + class AutoAssign(EventPermissionRequiredMixin, AsyncAction, View): task = assign_automatically @@ -366,3 +369,40 @@ class EntryDelete(EventPermissionRequiredMixin, DeleteView): 'event': self.request.event.slug, 'organizer': self.request.event.organizer.slug }) + + +class EntryTransfer(EventPermissionRequiredMixin, UpdateView): + model = WaitingListEntry + template_name = 'pretixcontrol/waitinglist/transfer.html' + permission = 'can_change_orders' + form_class = WaitingListEntryTransferForm + context_object_name = 'entry' + + def dispatch(self, request, *args, **kwargs): + if not self.request.event.has_subevents: + raise Http404(_("This is not an event series.")) + return super().dispatch(request, *args, **kwargs) + + def get_object(self, queryset=None) -> WaitingListEntry: + return get_object_or_404(WaitingListEntry, pk=self.kwargs['entry'], event=self.request.event, voucher__isnull=True) + + @transaction.atomic + def form_valid(self, form): + messages.success(self.request, _('The waitinglist entry has been transferred.')) + if form.has_changed(): + self.object.log_action( + 'pretix.event.order.waitinglist.transferred', user=self.request.user, data={ + k: form.cleaned_data.get(k) for k in form.changed_data + } + ) + return super().form_valid(form) + + def form_invalid(self, form): + messages.error(self.request, _('We could not save your changes. See below for details.')) + return super().form_invalid(form) + + def get_success_url(self) -> str: + return reverse('control:event.orders.waitinglist', kwargs={ + 'event': self.request.event.slug, + 'organizer': self.request.event.organizer.slug + })