diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index 12c3174d68..3be4acf29e 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -704,6 +704,16 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer): if 'answers.question' in self.context['expand']: self.fields['answers'].child.fields['question'] = QuestionSerializer(read_only=True) + if 'addons' in self.context['expand']: + # Experimental feature, undocumented on purpose for now in case we need to remove it again + # for performance reasons + subl = CheckinListOrderPositionSerializer(read_only=True, many=True, context={ + **self.context, + 'expand': [v for v in self.context['expand'] if v != 'addons'], + 'pdf_data': False, + }) + self.fields['addons'] = subl + class OrderPaymentTypeField(serializers.Field): # TODO: Remove after pretix 2.2 diff --git a/src/pretix/api/views/checkin.py b/src/pretix/api/views/checkin.py index 7f51c2403b..7a7d50e97a 100644 --- a/src/pretix/api/views/checkin.py +++ b/src/pretix/api/views/checkin.py @@ -381,15 +381,21 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr qs = qs.filter(reduce(operator.or_, lists_qs)) + prefetch_related = [ + Prefetch( + lookup='checkins', + queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device') + ), + Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')), + 'answers', 'answers__options', 'answers__question', + ] + select_related = [ + 'item', 'variation', 'order', 'addon_to', 'order__invoice_address', 'order', 'seat' + ] + if pdf_data: qs = qs.prefetch_related( - Prefetch( - lookup='checkins', - queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device') - ), - Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')), - 'answers', 'answers__options', 'answers__question', - Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')), + # Don't add to list, we don't want to propagate to addons Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related( Prefetch( 'event', @@ -404,32 +410,39 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr ) ) )) - ).select_related( - 'item', 'variation', 'item__category', 'addon_to', 'order', 'order__invoice_address', 'seat' ) - else: - qs = qs.prefetch_related( - Prefetch( - lookup='checkins', - queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device') - ), - Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')), - 'answers', 'answers__options', 'answers__question', - Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')) - ).select_related('item', 'variation', 'order', 'addon_to', 'order__invoice_address', 'order', 'seat') if expand and 'subevent' in expand: - qs = qs.prefetch_related( + prefetch_related += [ 'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set', 'subevent__seat_category_mappings', 'subevent__meta_values' - ) + ] if expand and 'item' in expand: - qs = qs.prefetch_related('item', 'item__addons', 'item__bundles', 'item__meta_values', - 'item__variations').select_related('item__tax_rule') + prefetch_related += [ + 'item', 'item__addons', 'item__bundles', 'item__meta_values', + 'item__variations', + ] + select_related.append('item__tax_rule') if expand and 'variation' in expand: - qs = qs.prefetch_related('variation', 'variation__meta_values') + prefetch_related += [ + 'variation', 'variation__meta_values', + ] + + if expand and 'addons' in expand: + prefetch_related += [ + Prefetch('addons', OrderPosition.objects.prefetch_related(*prefetch_related).select_related(*select_related)), + ] + else: + prefetch_related += [ + Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')) + ] + + if pdf_data: + select_related.remove("order") # Don't need it twice on this queryset + + qs = qs.prefetch_related(*prefetch_related).select_related(*select_related) return qs diff --git a/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/app.vue b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/app.vue index 6e75db0f4f..f9e0936582 100644 --- a/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/app.vue +++ b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/app.vue @@ -38,6 +38,7 @@
{{ checkResult.position.order }}-{{ checkResult.position.positionid }}

{{ checkResult.position.attendee_name }}

+
{{ checkResultAddons }}
{{ checkResultSubevent }}
{{ checkResult.position.secret }}
{{ checkResult.position.seat.name }}
@@ -265,6 +266,16 @@ export default { const date = moment.utc(this.checkinlist.subevent.date_from).tz(this.$root.timezone).format(this.$root.datetime_format) return `${name} · ${date}` }, + checkResultAddons() { + if (!this.checkResult) return '' + if (!this.checkResult.position.addons) return '' + return this.checkResult.position.addons.map((addon) => { + if (addon.variation) { + return `+ ${addon.item.internal_name || i18nstring_localize(addon.item.name)} – ${i18nstring_localize(addon.variation.value)}` + } + return "+ " + (addon.item.internal_name || i18nstring_localize(addon.item.name)); + }).join("\n") + }, checkResultSubevent() { if (!this.checkResult) return '' if (!this.checkResult.position.subevent) return '' @@ -369,7 +380,7 @@ export default { this.$refs.input.blur() }) - let url = this.$root.api.lists + this.checkinlist.id + '/positions/' + encodeURIComponent(id) + '/redeem/?expand=item&expand=subevent&expand=variation&expand=answers.question' + let url = this.$root.api.lists + this.checkinlist.id + '/positions/' + encodeURIComponent(id) + '/redeem/?expand=item&expand=subevent&expand=variation&expand=answers.question&expand=addons' if (untrusted) { url += '&untrusted_input=true' } diff --git a/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/scss/main.scss b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/scss/main.scss index 1ba7b37a34..c4ea8e6dd2 100644 --- a/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/scss/main.scss +++ b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/scss/main.scss @@ -92,6 +92,9 @@ a.searchresult, .check-result { word-break: break-word; color: $text-muted; } + .addons { + white-space: pre-line; + } } .check-result-status { diff --git a/src/tests/api/test_checkinrpc.py b/src/tests/api/test_checkinrpc.py index 95ce87a47f..9fb79cfa67 100644 --- a/src/tests/api/test_checkinrpc.py +++ b/src/tests/api/test_checkinrpc.py @@ -737,6 +737,19 @@ def test_question_expand(token_client, organizer, clist, event, order, question) assert resp.data["position"]["answers"][0]["question"]["question"]["en"] == "Size" +@pytest.mark.django_db +def test_addons_expand(token_client, organizer, clist, event, order, question, other_item): + with scopes_disabled(): + p = order.positions.first() + question[0].save() + p.answers.create(question=question[0], answer="3") + + resp = _redeem(token_client, organizer, clist, p.secret, {"answers": {question[0].pk: ""}}, query="?expand=addons&expand=item") + assert resp.status_code == 201 + assert resp.data["status"] == "ok" + assert resp.data["position"]["addons"][0]["item"]["id"] == other_item.pk + + @pytest.mark.django_db def test_store_failed(token_client, organizer, clist, event, order): with scopes_disabled():