From 86085d93680b014c5fbea7df3218de2f24f4cf72 Mon Sep 17 00:00:00 2001
From: Raphael Michel
Date: Fri, 10 Jun 2022 12:14:44 +0200
Subject: [PATCH] Allow to bulk-select many tickets to check in or out (#2678)
* Allow to bulk-select many tickets to check in or out
* Update tests
* Add permission test
* Update src/pretix/control/templates/pretixcontrol/checkin/index.html
Co-authored-by: Richard Schreiber
* Update src/pretix/static/pretixbase/js/asynctask.js
Co-authored-by: Richard Schreiber
* Remove console.warn
* Simplify stuff
* minor refactor
* fix missing checked-out success message
Co-authored-by: Richard Schreiber
Co-authored-by: Richard Schreiber
---
src/pretix/base/views/tasks.py | 94 ++++++++++++++++++-
.../pretixcontrol/checkin/index.html | 31 +++++-
src/pretix/control/urls.py | 1 +
src/pretix/control/views/checkin.py | 70 +++++++++-----
src/pretix/control/views/organizer.py | 2 +-
src/pretix/static/pretixbase/js/asynctask.js | 11 ++-
src/tests/control/test_checkins.py | 6 +-
src/tests/control/test_permissions.py | 2 +
8 files changed, 183 insertions(+), 34 deletions(-)
diff --git a/src/pretix/base/views/tasks.py b/src/pretix/base/views/tasks.py
index bc3206c11c..023ea779a1 100644
--- a/src/pretix/base/views/tasks.py
+++ b/src/pretix/base/views/tasks.py
@@ -29,12 +29,13 @@ from celery.result import AsyncResult
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ValidationError
-from django.http import JsonResponse, QueryDict
+from django.http import HttpResponse, JsonResponse, QueryDict
from django.shortcuts import redirect, render
from django.test import RequestFactory
from django.utils import timezone, translation
from django.utils.timezone import get_current_timezone
from django.utils.translation import get_language, gettext as _
+from django.views import View
from django.views.generic import FormView
from redis import ResponseError
@@ -312,3 +313,94 @@ class AsyncFormView(AsyncMixin, FormView):
else:
return self.error(res.info)
return redirect(self.get_check_url(res.id, False))
+
+
+class AsyncPostView(AsyncMixin, View):
+ """
+ View variant in which instead of ``post``, an ``async_post`` is executed in a celery task.
+ Note that this places some severe limitations on the form and the view, e.g. ``async_post`` may not
+ depend on the request object unless specifically supported by this class. File upload is currently also
+ not supported.
+ """
+ known_errortypes = ['ValidationError']
+ expected_exceptions = (ValidationError,)
+ task_base = ProfiledEventTask
+
+ def __init_subclass__(cls):
+ def async_execute(self, *, request_path, url_args, url_kwargs, query_string, post_data, locale, tz,
+ organizer=None, event=None, user=None, session_key=None):
+ view_instance = cls()
+ req = RequestFactory().post(
+ request_path + '?' + query_string,
+ data=post_data,
+ content_type='application/x-www-form-urlencoded'
+ )
+ view_instance.request = req
+ if event:
+ view_instance.request.event = event
+ view_instance.request.organizer = event.organizer
+ elif organizer:
+ view_instance.request.organizer = organizer
+ if user:
+ view_instance.request.user = User.objects.get(pk=user) if isinstance(user, int) else user
+ if session_key:
+ engine = import_module(settings.SESSION_ENGINE)
+ self.SessionStore = engine.SessionStore
+ view_instance.request.session = self.SessionStore(session_key)
+
+ with translation.override(locale), timezone.override(pytz.timezone(tz)):
+ return view_instance.async_post(view_instance.request, *url_args, **url_kwargs)
+
+ cls.async_execute = app.task(
+ base=cls.task_base,
+ bind=True,
+ name=cls.__module__ + '.' + cls.__name__ + '.async_execute',
+ throws=cls.expected_exceptions
+ )(async_execute)
+
+ def async_post(self, request, *args, **kwargs):
+ pass
+
+ def get(self, request, *args, **kwargs):
+ if 'async_id' in request.GET and settings.HAS_CELERY:
+ return self.get_result(request)
+ return HttpResponse(status=405)
+
+ def post(self, request, *args, **kwargs):
+ if request.FILES:
+ raise TypeError('File upload currently not supported in AsyncPostView')
+ kwargs = {
+ 'request_path': self.request.path,
+ 'query_string': self.request.GET.urlencode(),
+ 'post_data': self.request.POST.urlencode(),
+ 'locale': get_language(),
+ 'url_args': args,
+ 'url_kwargs': kwargs,
+ 'tz': get_current_timezone().zone,
+ }
+ if hasattr(self.request, 'organizer'):
+ kwargs['organizer'] = self.request.organizer.pk
+ if self.request.user.is_authenticated:
+ kwargs['user'] = self.request.user.pk
+ if hasattr(self.request, 'event'):
+ kwargs['event'] = self.request.event.pk
+ if hasattr(self.request, 'session'):
+ kwargs['session_key'] = self.request.session.session_key
+
+ try:
+ res = type(self).async_execute.apply_async(kwargs=kwargs)
+ except ConnectionError:
+ # Task very likely not yet sent, due to redis restarting etc. Let's try once again
+ res = type(self).async_execute.apply_async(kwargs=kwargs)
+
+ if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
+ data = self._return_ajax_result(res)
+ data['check_url'] = self.get_check_url(res.id, True)
+ return JsonResponse(data)
+ else:
+ if res.ready():
+ if res.successful() and not isinstance(res.info, Exception):
+ return self.success(res.info)
+ else:
+ return self.error(res.info)
+ return redirect(self.get_check_url(res.id, False))
diff --git a/src/pretix/control/templates/pretixcontrol/checkin/index.html b/src/pretix/control/templates/pretixcontrol/checkin/index.html
index b0a3bd62f7..8bf1c71169 100644
--- a/src/pretix/control/templates/pretixcontrol/checkin/index.html
+++ b/src/pretix/control/templates/pretixcontrol/checkin/index.html
@@ -71,13 +71,20 @@
{% else %}
-