From 804b048dbb3596f98d03823c43c72db7a8fac2dd Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Mon, 31 Mar 2025 12:26:46 +0200 Subject: [PATCH] =?UTF-8?q?Check-in=20API:=20Return=20order=20locale,=20an?= =?UTF-8?q?d=20allow=20to=20use=20order=20locale=20for=20=E2=80=A6=20(#496?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Check-in API: Return order locale, and allow to use order locale for reasons * Fix failing tests --- doc/api/resources/checkin.rst | 9 +++++++- src/pretix/api/serializers/checkin.py | 1 + src/pretix/api/serializers/order.py | 3 ++- src/pretix/api/views/checkin.py | 9 ++++++-- src/tests/api/test_checkin.py | 3 +++ src/tests/api/test_checkinrpc.py | 31 +++++++++++++++++++++++++-- 6 files changed, 50 insertions(+), 6 deletions(-) diff --git a/doc/api/resources/checkin.rst b/doc/api/resources/checkin.rst index 36549fe108..7f6861da8c 100644 --- a/doc/api/resources/checkin.rst +++ b/doc/api/resources/checkin.rst @@ -54,6 +54,11 @@ Checking a ticket in this request twice with the same nonce, the second request will also succeed but will always create only one check-in object even when the previous request was successful as well. This allows for a certain level of idempotency and enables you to re-try after a connection failure. + :json string status: ``"ok"``, ``"incomplete"``, or ``"error"`` :>json string reason: Reason code, only set on status ``"error"``, see below for possible values. :>json string reason_explanation: Human-readable explanation, only set on status ``"error"`` and reason ``"rules"``, can be null. @@ -62,7 +67,9 @@ Checking a ticket in will only include check-ins for the selected list. (2) An additional boolean property ``require_attention`` will inform you whether either the order or the item have the ``checkin_attention`` flag set. (3) If ``attendee_name`` is empty, it may automatically fall - back to values from a parent product or from invoice addresses. + back to values from a parent product or from invoice addresses. (4) Additional properties + ``order__status``, ``order__valid_if_pending``, ``order__require_approval``, and + ``order__locale`` are included with details form the order for convenience. :>json boolean require_attention: Whether or not the ``require_attention`` flag is set on the item or order. :>json list checkin_texts: List of additional texts to show to the user. :>json object list: Excerpt of information about the matching :ref:`check-in list ` (if any was found), diff --git a/src/pretix/api/serializers/checkin.py b/src/pretix/api/serializers/checkin.py index c13a14a62e..564665f3a6 100644 --- a/src/pretix/api/serializers/checkin.py +++ b/src/pretix/api/serializers/checkin.py @@ -84,6 +84,7 @@ class CheckinRPCRedeemInputSerializer(serializers.Serializer): type = serializers.ChoiceField(choices=Checkin.CHECKIN_TYPES, default=Checkin.TYPE_ENTRY) ignore_unpaid = serializers.BooleanField(default=False, required=False) questions_supported = serializers.BooleanField(default=True, required=False) + use_order_locale = serializers.BooleanField(default=False, required=False) nonce = serializers.CharField(required=False, allow_null=True) datetime = serializers.DateTimeField(required=False, allow_null=True) answers = serializers.JSONField(required=False, allow_null=True) diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index 1eb02e4939..147810a437 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -607,6 +607,7 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer): order__status = serializers.SlugRelatedField(read_only=True, slug_field='status', source='order') order__valid_if_pending = serializers.SlugRelatedField(read_only=True, slug_field='valid_if_pending', source='order') order__require_approval = serializers.SlugRelatedField(read_only=True, slug_field='require_approval', source='order') + order__locale = serializers.SlugRelatedField(read_only=True, slug_field='locale', source='order') class Meta: model = OrderPosition @@ -615,7 +616,7 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer): 'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins', 'print_logs', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'require_attention', 'order__status', 'order__valid_if_pending', 'order__require_approval', - 'valid_from', 'valid_until', 'blocked') + 'order__locale', 'valid_from', 'valid_until', 'blocked') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/src/pretix/api/views/checkin.py b/src/pretix/api/views/checkin.py index 311a6bd602..521687caa9 100644 --- a/src/pretix/api/views/checkin.py +++ b/src/pretix/api/views/checkin.py @@ -420,7 +420,7 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force, checkin_type, ignore_unpaid, nonce, untrusted_input, user, auth, expand, pdf_data, request, questions_supported, canceled_supported, - source_type='barcode', legacy_url_support=False, simulate=False, gate=None): + source_type='barcode', legacy_url_support=False, simulate=False, gate=None, use_order_locale=False): if not checkinlists: raise ValidationError('No check-in list passed.') @@ -694,7 +694,11 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force, pass # 6. Pass to our actual check-in logic - with language(op.order.event.settings.locale): + if use_order_locale: + locale = op.order.locale + else: + locale = op.order.event.settings.locale + with language(locale): try: perform_checkin( op=op, @@ -909,6 +913,7 @@ class CheckinRPCRedeemView(views.APIView): expand=self.request.query_params.getlist('expand'), pdf_data=self.request.query_params.get('pdf_data', 'false') == 'true', questions_supported=s.validated_data['questions_supported'], + use_order_locale=s.validated_data['use_order_locale'], canceled_supported=True, request=self.request, # this is not clean, but we need it in the serializers for URL generation legacy_url_support=False, diff --git a/src/tests/api/test_checkin.py b/src/tests/api/test_checkin.py index 361e9d391e..52af8a8cf1 100644 --- a/src/tests/api/test_checkin.py +++ b/src/tests/api/test_checkin.py @@ -108,6 +108,7 @@ TEST_ORDERPOSITION1_RES = { "order__status": "p", "order__require_approval": False, "order__valid_if_pending": False, + "order__locale": "en", "order": "FOO", "positionid": 1, "item": 1, @@ -146,6 +147,7 @@ TEST_ORDERPOSITION2_RES = { "order__status": "p", "order__require_approval": False, "order__valid_if_pending": False, + "order__locale": "en", "order": "FOO", "positionid": 2, "item": 1, @@ -184,6 +186,7 @@ TEST_ORDERPOSITION3_RES = { "order__status": "p", "order__require_approval": False, "order__valid_if_pending": False, + "order__locale": "en", "order": "FOO", "positionid": 3, "item": 1, diff --git a/src/tests/api/test_checkinrpc.py b/src/tests/api/test_checkinrpc.py index 8a91bfcaf6..d2fdd397de 100644 --- a/src/tests/api/test_checkinrpc.py +++ b/src/tests/api/test_checkinrpc.py @@ -28,6 +28,7 @@ from django.core.files.base import ContentFile from django.utils.timezone import now from django_countries.fields import Country from django_scopes import scopes_disabled +from freezegun import freeze_time from i18nfield.strings import LazyI18nString from tests.const import SAMPLE_PNG @@ -148,6 +149,7 @@ TEST_ORDERPOSITION1_RES = { "order__status": "p", "order__require_approval": False, "order__valid_if_pending": False, + "order__locale": "en", "order": "FOO", "positionid": 1, "item": 1, @@ -200,7 +202,7 @@ def clist_event2(event2): return c -def _redeem(token_client, org, clist, p, body=None, query=''): +def _redeem(token_client, org, clist, p, body=None, query='', headers={}): body = body or {} if isinstance(clist, list): body['lists'] = [c.pk for c in clist] @@ -209,7 +211,7 @@ def _redeem(token_client, org, clist, p, body=None, query=''): body['secret'] = p return token_client.post('/api/v1/organizers/{}/checkinrpc/redeem/{}'.format( org.slug, query, - ), body, format='json') + ), body, format='json', headers={}) @pytest.mark.django_db @@ -1050,3 +1052,28 @@ def test_checkin_no_pdf_data(token_client, event, team, organizer, clist_all, or resp = token_client.get( '/api/v1/organizers/{}/checkinrpc/search/?list={}&search=dummy&pdf_data=true'.format(organizer.slug, clist_all.pk)) assert not resp.data['results'][0].get('pdf_data') + + +@pytest.mark.django_db +def test_reason_explanation_localization(token_client, organizer, clist, other_item, event, order): + event.settings.locales = ["de", "en"] + order.locale = "de" + order.save() + with scopes_disabled(): + p = order.positions.first() + p.valid_from = datetime.datetime(2020, 1, 1, 12, 0, 0, tzinfo=event.timezone) + p.save() + with freeze_time("2020-01-01 10:45:00"): + resp = _redeem(token_client, organizer, clist, 'z3fsn8jyufm5kpk768q69gkbyr5f4h6w', {}) + assert resp.status_code == 400 + assert resp.data["status"] == "error" + assert resp.data["reason"] == "invalid_time" + assert resp.data["reason_explanation"] == "This ticket is only valid after 2020-01-01 12:00." + + resp = _redeem(token_client, organizer, clist, 'z3fsn8jyufm5kpk768q69gkbyr5f4h6w', { + "use_order_locale": True + }) + assert resp.status_code == 400 + assert resp.data["status"] == "error" + assert resp.data["reason"] == "invalid_time" + assert resp.data["reason_explanation"] == "Erst ab 01.01.2020 12:00 gültig."