Allow users with can_checkin_orders permission to use the bulk actions of the check-in list view

This commit is contained in:
Raphael Michel
2023-01-04 18:13:24 +01:00
parent 4747a4c480
commit a2f2d25169
5 changed files with 41 additions and 8 deletions
+5 -3
View File
@@ -28,7 +28,7 @@ from celery import states
from celery.result import AsyncResult from celery.result import AsyncResult
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.http import HttpResponse, JsonResponse, QueryDict from django.http import HttpResponse, JsonResponse, QueryDict
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.test import RequestFactory from django.test import RequestFactory
@@ -149,6 +149,8 @@ class AsyncMixin:
return redirect(self.get_success_url(value)) return redirect(self.get_success_url(value))
def error(self, exception): def error(self, exception):
if isinstance(exception, PermissionDenied):
raise exception
messages.error(self.request, self.get_error_message(exception)) messages.error(self.request, self.get_error_message(exception))
if "ajax" in self.request.POST or "ajax" in self.request.GET: if "ajax" in self.request.POST or "ajax" in self.request.GET:
return JsonResponse({ return JsonResponse({
@@ -337,8 +339,8 @@ class AsyncPostView(AsyncMixin, View):
depend on the request object unless specifically supported by this class. File upload is currently also depend on the request object unless specifically supported by this class. File upload is currently also
not supported. not supported.
""" """
known_errortypes = ['ValidationError'] known_errortypes = ['ValidationError', 'PermissionDenied']
expected_exceptions = (ValidationError,) expected_exceptions = (ValidationError, PermissionDenied)
task_base = ProfiledEventTask task_base = ProfiledEventTask
def async_set_progress(self, percentage): def async_set_progress(self, percentage):
@@ -82,8 +82,10 @@
<thead> <thead>
<tr> <tr>
<th> <th>
<label aria-label="{% trans "select all rows for batch-operation" %}" {% if "can_change_orders" in request.eventpermset or "can_checkin_orders" in request.eventpermset %}
class="batch-select-label"><input type="checkbox" data-toggle-table/></label> <label aria-label="{% trans "select all rows for batch-operation" %}"
class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
{% endif %}
</th> </th>
<th>{% trans "Order code" %} <a href="?{% url_replace request 'ordering' '-code'%}"><i class="fa fa-caret-down"></i></a> <th>{% trans "Order code" %} <a href="?{% url_replace request 'ordering' '-code'%}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'code'%}"><i class="fa fa-caret-up"></i></a></th> <a href="?{% url_replace request 'ordering' 'code'%}"><i class="fa fa-caret-up"></i></a></th>
@@ -125,7 +127,7 @@
{% for e in entries %} {% for e in entries %}
<tr> <tr>
<td> <td>
{% if "can_change_orders" in request.eventpermset %} {% if "can_change_orders" in request.eventpermset or "can_checkin_orders" in request.eventpermset %}
<input type="checkbox" name="checkin" id="id_checkin" class="" value="{{ e.pk }}"/> <input type="checkbox" name="checkin" id="id_checkin" class="" value="{{ e.pk }}"/>
{% endif %} {% endif %}
</td> </td>
@@ -198,7 +200,7 @@
</tbody> </tbody>
</table> </table>
</div> </div>
{% if "can_change_orders" in request.eventpermset %} {% if "can_change_orders" in request.eventpermset or "can_checkin_orders" in request.eventpermset %}
<button type="submit" class="btn btn-primary btn-save"> <button type="submit" class="btn btn-primary btn-save">
<span class="fa fa-sign-in" aria-hidden="true"></span> <span class="fa fa-sign-in" aria-hidden="true"></span>
{% trans "Check-In selected attendees" %} {% trans "Check-In selected attendees" %}
@@ -207,6 +209,8 @@
<span class="fa fa-sign-out" aria-hidden="true"></span> <span class="fa fa-sign-out" aria-hidden="true"></span>
{% trans "Check-Out selected attendees" %} {% trans "Check-Out selected attendees" %}
</button> </button>
{% endif %}
{% if "can_change_orders" in request.eventpermset %}
<button type="submit" class="btn btn-danger btn-save" name="revert" value="true"> <button type="submit" class="btn btn-danger btn-save" name="revert" value="true">
<span class="fa fa-trash" aria-hidden="true"></span> <span class="fa fa-trash" aria-hidden="true"></span>
{% trans "Delete all check-ins of selected attendees" %} {% trans "Delete all check-ins of selected attendees" %}
+7 -1
View File
@@ -34,6 +34,7 @@
import dateutil.parser import dateutil.parser
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.db import transaction from django.db import transaction
from django.db.models import Exists, Max, OuterRef, Prefetch, Subquery from django.db.models import Exists, Max, OuterRef, Prefetch, Subquery
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
@@ -171,7 +172,7 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, CheckInList
class CheckInListBulkActionView(CheckInListQueryMixin, EventPermissionRequiredMixin, AsyncPostView): class CheckInListBulkActionView(CheckInListQueryMixin, EventPermissionRequiredMixin, AsyncPostView):
template_name = 'pretixcontrol/organizers/device_bulk_edit.html' template_name = 'pretixcontrol/organizers/device_bulk_edit.html'
permission = 'can_change_orders' permission = ('can_change_orders', 'can_checkin_orders')
context_object_name = 'device' context_object_name = 'device'
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
@@ -181,11 +182,16 @@ class CheckInListBulkActionView(CheckInListQueryMixin, EventPermissionRequiredMi
def get_queryset(self): def get_queryset(self):
return super().get_queryset().prefetch_related(None).order_by() return super().get_queryset().prefetch_related(None).order_by()
def get_error_url(self):
return self.get_success_url(None)
@transaction.atomic() @transaction.atomic()
def async_post(self, request, *args, **kwargs): def async_post(self, request, *args, **kwargs):
self.list = get_object_or_404(request.event.checkin_lists.all(), pk=kwargs.get("list")) self.list = get_object_or_404(request.event.checkin_lists.all(), pk=kwargs.get("list"))
positions = self.get_queryset() positions = self.get_queryset()
if request.POST.get('revert') == 'true': if request.POST.get('revert') == 'true':
if not request.user.has_event_permission(request.organizer, request.event, 'can_change_orders', request=request):
raise PermissionDenied()
for op in positions: for op in positions:
if op.order.status == Order.STATUS_PAID or (self.list.include_pending and op.order.status == Order.STATUS_PENDING): if op.order.status == Order.STATUS_PAID or (self.list.include_pending and op.order.status == Order.STATUS_PENDING):
Checkin.objects.filter(position=op, list=self.list).delete() Checkin.objects.filter(position=op, list=self.list).delete()
+20
View File
@@ -307,6 +307,26 @@ def test_manual_checkins(client, checkin_list_env):
).exists() ).exists()
@pytest.mark.django_db
def test_manual_checkins_revert_requires_order_change_permission(client, checkin_list_env):
client.login(email='dummy@dummy.dummy', password='dummy')
with scopes_disabled():
assert not checkin_list_env[5][3].checkins.exists()
Team.objects.update(can_change_orders=False, can_checkin_orders=True)
client.post('/control/event/dummy/dummy/checkinlists/{}/bulk_action'.format(checkin_list_env[6].pk), {
'checkin': [checkin_list_env[5][3].pk]
})
with scopes_disabled():
assert checkin_list_env[5][3].checkins.exists()
r = client.post('/control/event/dummy/dummy/checkinlists/{}/bulk_action'.format(checkin_list_env[6].pk), {
'checkin': [checkin_list_env[5][3].pk],
'revert': 'true'
})
assert r.status_code == 403
with scopes_disabled():
assert checkin_list_env[5][3].checkins.exists()
@pytest.mark.django_db @pytest.mark.django_db
def test_manual_checkins_revert(client, checkin_list_env): def test_manual_checkins_revert(client, checkin_list_env):
client.login(email='dummy@dummy.dummy', password='dummy') client.login(email='dummy@dummy.dummy', password='dummy')
+1
View File
@@ -386,6 +386,7 @@ event_permission_urls = [
("can_view_orders", "checkinlists/", 200, HTTP_GET), ("can_view_orders", "checkinlists/", 200, HTTP_GET),
("can_view_orders", "checkinlists/1/", 404, HTTP_GET), ("can_view_orders", "checkinlists/1/", 404, HTTP_GET),
("can_change_orders", "checkinlists/1/bulk_action", 404, HTTP_POST), ("can_change_orders", "checkinlists/1/bulk_action", 404, HTTP_POST),
("can_checkin_orders", "checkinlists/1/bulk_action", 404, HTTP_POST),
("can_change_event_settings", "checkinlists/add", 200, HTTP_GET), ("can_change_event_settings", "checkinlists/add", 200, HTTP_GET),
("can_change_event_settings", "checkinlists/1/change", 404, HTTP_GET), ("can_change_event_settings", "checkinlists/1/change", 404, HTTP_GET),
("can_change_event_settings", "checkinlists/1/delete", 404, HTTP_GET), ("can_change_event_settings", "checkinlists/1/delete", 404, HTTP_GET),