From 22f3412ad0961eee1e10455c69106af66715b0b4 Mon Sep 17 00:00:00 2001
From: Raphael Michel
Date: Wed, 30 Mar 2022 18:03:05 +0200
Subject: [PATCH] Allow users to see the number of checkins (#2561)
Co-authored-by: Richard Schreiber
---
src/pretix/base/settings.py | 15 ++++++-
src/pretix/control/forms/event.py | 1 +
.../pretixcontrol/event/settings.html | 1 +
.../pretixpresale/event/fragment_cart.html | 19 +++++++++
src/pretix/presale/views/order.py | 41 +++++++++++++++----
5 files changed, 67 insertions(+), 10 deletions(-)
diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py
index 897986150..1b1c3cbce 100644
--- a/src/pretix/base/settings.py
+++ b/src/pretix/base/settings.py
@@ -1195,7 +1195,20 @@ DEFAULTS = {
help_text=_("If you ask for a phone number, explain why you do so and what you will use the phone number for.")
)
},
-
+ 'show_checkin_number_user': {
+ 'default': 'False',
+ 'type': bool,
+ 'serializer_class': serializers.BooleanField,
+ 'form_class': forms.BooleanField,
+ 'form_kwargs': dict(
+ label=_("Show number of check-ins to customer"),
+ help_text=_('With this option enabled, your customers will be able how many times they entered '
+ 'the event. This is usually not necessary, but might be useful in combination with tickets '
+ 'that are usable a specific number of times, so customers can see how many times they have '
+ 'already been used. Exits or failed scans will not be counted, and the user will not see '
+ 'the different check-in lists.'),
+ )
+ },
'ticket_download': {
'default': 'False',
'type': bool,
diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py
index 746b3268d..87a1ee247 100644
--- a/src/pretix/control/forms/event.py
+++ b/src/pretix/control/forms/event.py
@@ -523,6 +523,7 @@ class EventSettingsForm(SettingsForm):
'last_order_modification_date',
'allow_modifications_after_checkin',
'checkout_show_copy_answers_button',
+ 'show_checkin_number_user',
'primary_color',
'theme_color_success',
'theme_color_danger',
diff --git a/src/pretix/control/templates/pretixcontrol/event/settings.html b/src/pretix/control/templates/pretixcontrol/event/settings.html
index 09624f482..1c2784f65 100644
--- a/src/pretix/control/templates/pretixcontrol/event/settings.html
+++ b/src/pretix/control/templates/pretixcontrol/event/settings.html
@@ -221,6 +221,7 @@
{% bootstrap_field sform.show_items_outside_presale_period layout="control" %}
{% bootstrap_field sform.last_order_modification_date layout="control" %}
{% bootstrap_field sform.allow_modifications_after_checkin layout="control" %}
+ {% bootstrap_field sform.show_checkin_number_user layout="control" %}
{% if line.seat or line.voucher or line.subevent or line.used_membership%}
+ {% elif event.settings.show_checkin_number_user and line.checkin_count %}
+
{% endif %}
{% if line.seat %}
@@ -94,8 +96,25 @@
{% endif %}
+ {% if event.settings.show_checkin_number_user and line.checkin_count %}
+
+
{% trans "Usage:" context "ticket_checkins" %}
+
+
+
+ {% blocktrans trimmed count count=line.checkin_count %}
+ This ticket has been used once.
+ {% plural %}
+ This ticket has been used {{ count }} times.
+ {% endblocktrans %}
+
+
+
+ {% endif %}
{% if line.seat or line.voucher or line.subevent or line.used_membership%}
+ {% elif event.settings.show_checkin_number_user and line.checkin_count %}
+
{% endif %}
{% if line.issued_gift_cards.exists %}
diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py
index 4e149fca6..e3435a6d2 100644
--- a/src/pretix/presale/views/order.py
+++ b/src/pretix/presale/views/order.py
@@ -47,7 +47,7 @@ from django.contrib import messages
from django.core.exceptions import ValidationError
from django.core.files import File
from django.db import transaction
-from django.db.models import Exists, OuterRef, Q, Sum
+from django.db.models import Count, Exists, OuterRef, Q, Subquery, Sum
from django.http import (
FileResponse, Http404, HttpResponseRedirect, JsonResponse,
)
@@ -60,7 +60,8 @@ from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import TemplateView, View
from pretix.base.models import (
- CachedTicket, GiftCard, Invoice, Order, OrderPosition, Quota, TaxRule,
+ CachedTicket, Checkin, GiftCard, Invoice, Order, OrderPosition, Quota,
+ TaxRule,
)
from pretix.base.models.orders import (
CachedCombinedTicket, InvoiceAddress, OrderFee, OrderPayment, OrderRefund,
@@ -124,12 +125,13 @@ class OrderDetailMixin(NoSearchIndexViewMixin):
class OrderPositionDetailMixin(NoSearchIndexViewMixin):
@cached_property
def position(self):
- p = OrderPosition.objects.filter(
+ qs = OrderPosition.objects.filter(
order__event=self.request.event,
addon_to__isnull=True,
order__code=self.kwargs['order'],
positionid=self.kwargs['position']
- ).select_related('order', 'order__event').first()
+ ).select_related('order', 'order__event')
+ p = qs.first()
if p:
if p.web_secret.lower() == self.kwargs['secret'].lower():
return p
@@ -229,9 +231,21 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TicketPageMixin,
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
+
+ qs = self.order.positions.prefetch_related('issued_gift_cards').select_related('tax_rule')
+ if self.request.event.settings.show_checkin_number_user:
+ qs = qs.annotate(
+ checkin_count=Subquery(
+ Checkin.objects.filter(
+ successful=True, type=Checkin.TYPE_ENTRY, position_id=OuterRef('pk')
+ ).order_by().values('position').annotate(c=Count('*')).values('c')
+ )
+ )
+
ctx['cart'] = self.get_cart(
- answers=True, downloads=ctx['can_download'],
- queryset=self.order.positions.prefetch_related('issued_gift_cards').select_related('tax_rule'),
+ answers=True,
+ downloads=ctx['can_download'],
+ queryset=qs,
order=self.order
)
ctx['tickets_with_download'] = [p for p in ctx['cart']['positions'] if p.generate_ticket]
@@ -328,14 +342,23 @@ class OrderPositionDetails(EventViewMixin, OrderPositionDetailMixin, CartMixin,
return buttons
def get_context_data(self, **kwargs):
+ qs = self.order.positions.select_related('tax_rule').filter(
+ Q(pk=self.position.pk) | Q(addon_to__id=self.position.pk)
+ )
+ if self.request.event.settings.show_checkin_number_user:
+ qs = qs.annotate(
+ checkin_count=Subquery(
+ Checkin.objects.filter(
+ successful=True, type=Checkin.TYPE_ENTRY, position_id=OuterRef('pk')
+ ).order_by().values('position').annotate(c=Count('*')).values('c')
+ )
+ )
ctx = super().get_context_data(**kwargs)
ctx['can_download_multi'] = False
ctx['position'] = self.position
ctx['cart'] = self.get_cart(
answers=True, downloads=ctx['can_download'],
- queryset=self.order.positions.select_related('tax_rule').filter(
- Q(pk=self.position.pk) | Q(addon_to__id=self.position.pk)
- ),
+ queryset=qs,
order=self.order
)
ctx['tickets_with_download'] = [p for p in ctx['cart']['positions'] if p.generate_ticket]