forked from CGM_Public/pretix_original
Waiting list: Bulk deletion
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Delete entries" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Delete entries" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% if allowed %}
|
||||
<p>{% blocktrans %}Are you sure you want to delete the following entries?{% endblocktrans %}</p>
|
||||
<ul>
|
||||
{% for s in allowed %}
|
||||
<li>
|
||||
{{ s }}
|
||||
<input type="hidden" name="entry" value="{{ s.pk }}">
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if forbidden %}
|
||||
<p>{% blocktrans trimmed %}The following entries can't be deleted as they already have a voucher attached.{% endblocktrans %}</p>
|
||||
<ul>
|
||||
{% for s in forbidden %}
|
||||
<li>
|
||||
{{ s }}
|
||||
<input type="hidden" name="entry" value="{{ s.pk }}">
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<div class="form-group submit-group">
|
||||
<a href="{% url "control:event.orders.waitinglist" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default btn-cancel">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-danger btn-save" value="delete_confirm" name="action">
|
||||
{% trans "Delete" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -126,12 +126,20 @@
|
||||
{% trans "Download list" %}</a>
|
||||
</form>
|
||||
</p>
|
||||
<form method="post" action="?next={{ request.get_full_path|urlencode }}">
|
||||
<form action="{% url "control:event.orders.waitinglist.action" organizer=request.event.organizer.slug event=request.event.slug %}?next={{ request.get_full_path|urlencode }}" method="post">
|
||||
{% csrf_token %}
|
||||
<input name="subevent" type="hidden" value="{{ request.GET.subevent }}">
|
||||
<input name="item" type="hidden" value="{{ request.GET.item }}">
|
||||
<input name="status" type="hidden" value="{{ request.GET.status }}">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{% if "can_change_orders" in request.eventpermset %}
|
||||
<label aria-label="{% trans "select all rows for batch-operation" %}" class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
|
||||
{% endif %}
|
||||
</th>
|
||||
{% if request.event.settings.waiting_list_names_asked %}
|
||||
<th>{% trans "Name" %}</th>
|
||||
{% endif %}
|
||||
@@ -148,10 +156,27 @@
|
||||
<th>{% trans "Voucher" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{% if "can_change_orders" in request.eventpermset and page_obj.paginator.num_pages > 1 %}
|
||||
<tr class="table-select-all warning hidden">
|
||||
<td>
|
||||
<input type="checkbox" name="__ALL" id="__all" data-results-total="{{ page_obj.paginator.count }}">
|
||||
</td>
|
||||
<td colspan="5">
|
||||
<label for="__all">
|
||||
{% trans "Select all results on other pages as well" %}
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for e in entries %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if "can_change_orders" in request.eventpermset %}
|
||||
<label aria-label="{% trans "select row for batch-operation" %}" class="batch-select-label"><input type="checkbox" name="entry" class="batch-select-checkbox" value="{{ s.pk }}"/></label>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if request.event.settings.waiting_list_names_asked %}
|
||||
<td>{{ e.name|default:"" }}</td>
|
||||
{% endif %}
|
||||
@@ -233,6 +258,13 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if "can_change_orders" in request.eventpermset %}
|
||||
<div class="batch-select-actions">
|
||||
<button type="submit" class="btn btn-danger btn-save" name="action" value="delete">
|
||||
<i class="fa fa-trash"></i>{% trans "Delete selected" %}
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% include "pretixcontrol/pagination.html" %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -358,6 +358,7 @@ urlpatterns = [
|
||||
re_path(r'^shredder/download/(?P<file>[^/]+)/$', shredder.ShredDownloadView.as_view(), name='event.shredder.download'),
|
||||
re_path(r'^shredder/shred', shredder.ShredDoView.as_view(), name='event.shredder.shred'),
|
||||
re_path(r'^waitinglist/$', waitinglist.WaitingListView.as_view(), name='event.orders.waitinglist'),
|
||||
re_path(r'^waitinglist/action$', waitinglist.WaitingListActionView.as_view(), name='event.orders.waitinglist.action'),
|
||||
re_path(r'^waitinglist/auto_assign$', waitinglist.AutoAssign.as_view(), name='event.orders.waitinglist.auto'),
|
||||
re_path(r'^waitinglist/(?P<entry>\d+)/delete$', waitinglist.EntryDelete.as_view(),
|
||||
name='event.orders.waitinglist.delete'),
|
||||
|
||||
@@ -40,8 +40,9 @@ 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
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.http import is_safe_url
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _, pgettext
|
||||
@@ -79,16 +80,86 @@ class AutoAssign(EventPermissionRequiredMixin, AsyncAction, View):
|
||||
self.request.POST.get('subevent'))
|
||||
|
||||
|
||||
class WaitingListView(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
class WaitingListQuerySetMixin:
|
||||
|
||||
@cached_property
|
||||
def request_data(self):
|
||||
if self.request.method == "POST":
|
||||
return self.request.POST
|
||||
return self.request.GET
|
||||
|
||||
def get_queryset(self):
|
||||
qs = WaitingListEntry.objects.filter(
|
||||
event=self.request.event
|
||||
).select_related('item', 'variation', 'voucher').prefetch_related(
|
||||
'item__quotas', 'variation__quotas'
|
||||
)
|
||||
|
||||
s = self.request_data.get("status", "")
|
||||
if s == 's':
|
||||
qs = qs.filter(voucher__isnull=False)
|
||||
elif s == 'a':
|
||||
pass
|
||||
elif s == 'r':
|
||||
qs = qs.filter(
|
||||
voucher__isnull=False,
|
||||
voucher__redeemed__gte=F('voucher__max_usages'),
|
||||
)
|
||||
elif s == 'v':
|
||||
qs = qs.filter(
|
||||
voucher__isnull=False,
|
||||
voucher__redeemed__lt=F('voucher__max_usages'),
|
||||
).filter(Q(voucher__valid_until__isnull=True) | Q(voucher__valid_until__gt=now()))
|
||||
elif s == 'e':
|
||||
qs = qs.filter(
|
||||
voucher__isnull=False,
|
||||
voucher__redeemed__lt=F('voucher__max_usages'),
|
||||
voucher__valid_until__isnull=False,
|
||||
voucher__valid_until__lte=now()
|
||||
)
|
||||
else:
|
||||
qs = qs.filter(voucher__isnull=True)
|
||||
|
||||
if self.request_data.get("item", "") != "":
|
||||
i = self.request_data.get("item", "")
|
||||
qs = qs.filter(item_id=i)
|
||||
|
||||
if self.request_data.get("subevent", "") != "":
|
||||
s = self.request_data.get("subevent", "")
|
||||
qs = qs.filter(subevent_id=s)
|
||||
|
||||
if 'entry' in self.request_data and '__ALL' not in self.request_data:
|
||||
qs = qs.filter(
|
||||
id__in=self.request_data.getlist('entry')
|
||||
)
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
class WaitingListActionView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, View):
|
||||
model = WaitingListEntry
|
||||
context_object_name = 'entries'
|
||||
template_name = 'pretixcontrol/waitinglist/index.html'
|
||||
permission = 'can_view_orders'
|
||||
permission = 'can_change_orders'
|
||||
|
||||
def _redirect_back(self):
|
||||
if "next" in self.request.GET and is_safe_url(self.request.GET.get("next"), allowed_hosts=None):
|
||||
return redirect(self.request.GET.get("next"))
|
||||
return redirect(reverse('control:event.orders.waitinglist', kwargs={
|
||||
'event': self.request.event.slug,
|
||||
'organizer': self.request.event.organizer.slug
|
||||
}))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if not request.user.has_event_permission(request.organizer, request.event, 'can_change_orders',
|
||||
request=request):
|
||||
messages.error(request, _('You do not have permission to do this'))
|
||||
if request.POST.get('action') == 'delete':
|
||||
return render(request, 'pretixcontrol/waitinglist/delete_bulk.html', {
|
||||
'allowed': self.get_queryset().filter(voucher__isnull=True),
|
||||
'forbidden': self.get_queryset().filter(voucher__isnull=False),
|
||||
})
|
||||
elif request.POST.get('action') == 'delete_confirm':
|
||||
for obj in self.get_queryset():
|
||||
if not obj.voucher_id:
|
||||
obj.log_action('pretix.event.orders.waitinglist.deleted', user=self.request.user)
|
||||
obj.delete()
|
||||
messages.success(request, _('The selected entries have been deleted.'))
|
||||
return self._redirect_back()
|
||||
|
||||
if 'assign' in request.POST:
|
||||
@@ -135,55 +206,12 @@ class WaitingListView(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
return self._redirect_back()
|
||||
return self._redirect_back()
|
||||
|
||||
def _redirect_back(self):
|
||||
if "next" in self.request.GET and is_safe_url(self.request.GET.get("next"), allowed_hosts=None):
|
||||
return redirect(self.request.GET.get("next"))
|
||||
return redirect(reverse('control:event.orders.waitinglist', kwargs={
|
||||
'event': self.request.event.slug,
|
||||
'organizer': self.request.event.organizer.slug
|
||||
}))
|
||||
|
||||
def get_queryset(self):
|
||||
qs = WaitingListEntry.objects.filter(
|
||||
event=self.request.event
|
||||
).select_related('item', 'variation', 'voucher').prefetch_related(
|
||||
'item__quotas', 'variation__quotas'
|
||||
)
|
||||
|
||||
s = self.request.GET.get("status", "")
|
||||
if s == 's':
|
||||
qs = qs.filter(voucher__isnull=False)
|
||||
elif s == 'a':
|
||||
pass
|
||||
elif s == 'r':
|
||||
qs = qs.filter(
|
||||
voucher__isnull=False,
|
||||
voucher__redeemed__gte=F('voucher__max_usages'),
|
||||
)
|
||||
elif s == 'v':
|
||||
qs = qs.filter(
|
||||
voucher__isnull=False,
|
||||
voucher__redeemed__lt=F('voucher__max_usages'),
|
||||
).filter(Q(voucher__valid_until__isnull=True) | Q(voucher__valid_until__gt=now()))
|
||||
elif s == 'e':
|
||||
qs = qs.filter(
|
||||
voucher__isnull=False,
|
||||
voucher__redeemed__lt=F('voucher__max_usages'),
|
||||
voucher__valid_until__isnull=False,
|
||||
voucher__valid_until__lte=now()
|
||||
)
|
||||
else:
|
||||
qs = qs.filter(voucher__isnull=True)
|
||||
|
||||
if self.request.GET.get("item", "") != "":
|
||||
i = self.request.GET.get("item", "")
|
||||
qs = qs.filter(item_id=i)
|
||||
|
||||
if self.request.GET.get("subevent", "") != "":
|
||||
s = self.request.GET.get("subevent", "")
|
||||
qs = qs.filter(subevent_id=s)
|
||||
|
||||
return qs
|
||||
class WaitingListView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, PaginationMixin, ListView):
|
||||
model = WaitingListEntry
|
||||
context_object_name = 'entries'
|
||||
template_name = 'pretixcontrol/waitinglist/index.html'
|
||||
permission = 'can_view_orders'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
|
||||
@@ -163,6 +163,7 @@ event_urls = [
|
||||
"checkinlists/1/delete",
|
||||
"waitinglist/",
|
||||
"waitinglist/auto_assign",
|
||||
"waitinglist/action",
|
||||
"invoice/1",
|
||||
]
|
||||
|
||||
@@ -347,6 +348,7 @@ event_permission_urls = [
|
||||
("can_change_vouchers", "vouchers/1234/delete", 404),
|
||||
("can_view_orders", "waitinglist/", 200),
|
||||
("can_change_orders", "waitinglist/auto_assign", 405),
|
||||
("can_change_orders", "waitinglist/action", 405),
|
||||
("can_view_orders", "checkinlists/", 200),
|
||||
("can_view_orders", "checkinlists/1/", 404),
|
||||
("can_change_event_settings", "checkinlists/add", 200),
|
||||
|
||||
@@ -133,7 +133,7 @@ def test_assign_single(client, env):
|
||||
with scopes_disabled():
|
||||
wle = WaitingListEntry.objects.filter(voucher__isnull=True).last()
|
||||
|
||||
client.post('/control/event/dummy/dummy/waitinglist/', {
|
||||
client.post('/control/event/dummy/dummy/waitinglist/action', {
|
||||
'assign': wle.pk
|
||||
})
|
||||
wle.refresh_from_db()
|
||||
@@ -147,17 +147,17 @@ def test_priority_single(client, env):
|
||||
wle = WaitingListEntry.objects.filter(voucher__isnull=True).last()
|
||||
assert wle.priority == 0
|
||||
|
||||
client.post('/control/event/dummy/dummy/waitinglist/', {
|
||||
client.post('/control/event/dummy/dummy/waitinglist/action', {
|
||||
'move_top': wle.pk
|
||||
})
|
||||
wle.refresh_from_db()
|
||||
assert wle.priority == 1
|
||||
client.post('/control/event/dummy/dummy/waitinglist/', {
|
||||
client.post('/control/event/dummy/dummy/waitinglist/action', {
|
||||
'move_top': wle.pk
|
||||
})
|
||||
wle.refresh_from_db()
|
||||
assert wle.priority == 2
|
||||
client.post('/control/event/dummy/dummy/waitinglist/', {
|
||||
client.post('/control/event/dummy/dummy/waitinglist/action', {
|
||||
'move_end': wle.pk
|
||||
})
|
||||
wle.refresh_from_db()
|
||||
@@ -176,6 +176,21 @@ def test_delete_single(client, env):
|
||||
WaitingListEntry.objects.get(id=wle.id)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_bulk(client, env):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
with scopes_disabled():
|
||||
wle = WaitingListEntry.objects.first()
|
||||
|
||||
client.post('/control/event/dummy/dummy/waitinglist/action', data={
|
||||
'entry': wle.pk,
|
||||
'action': 'delete_confirm',
|
||||
})
|
||||
with pytest.raises(WaitingListEntry.DoesNotExist):
|
||||
with scopes_disabled():
|
||||
WaitingListEntry.objects.get(id=wle.id)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_dashboard(client, env):
|
||||
with scopes_disabled():
|
||||
|
||||
Reference in New Issue
Block a user