From 49ed8419bdab444d99269db0d908514adebcfcae Mon Sep 17 00:00:00 2001 From: Mira Weller Date: Wed, 30 Sep 2026 01:24:28 +0200 Subject: [PATCH] MixedAnswerListSerializer - wip --- src/pretix/api/serializers/cart.py | 6 +-- src/pretix/api/serializers/order.py | 53 +++++++++++++++++++++-- src/pretix/api/serializers/orderchange.py | 6 +-- src/pretix/api/views/order.py | 6 ++- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/pretix/api/serializers/cart.py b/src/pretix/api/serializers/cart.py index 74dc5242b4..c9b42a7096 100644 --- a/src/pretix/api/serializers/cart.py +++ b/src/pretix/api/serializers/cart.py @@ -31,7 +31,7 @@ from rest_framework.exceptions import ValidationError from pretix.api.serializers.i18n import I18nAwareModelSerializer from pretix.api.serializers.order import ( - AnswerCreateSerializer, AnswerSerializer, InlineSeatSerializer, + AnswerCreateSerializer, AnswerSerializer, InlineSeatSerializer, MixedAnswerListSerializer, ) from pretix.base.models import SalesChannel, Seat, Voucher from pretix.base.models.orders import CartPosition @@ -43,7 +43,7 @@ class TaxIncludedField(serializers.Field): class CartPositionSerializer(I18nAwareModelSerializer): - answers = AnswerSerializer(many=True) + answers = MixedAnswerListSerializer(child=AnswerSerializer(), source='*') seat = InlineSeatSerializer() includes_tax = TaxIncludedField(source='*') @@ -55,7 +55,7 @@ class CartPositionSerializer(I18nAwareModelSerializer): class BaseCartPositionCreateSerializer(I18nAwareModelSerializer): - answers = AnswerCreateSerializer(many=True, required=False) + answers = MixedAnswerListSerializer(child=AnswerCreateSerializer(required=False), source='*', required=False) attendee_name = serializers.CharField(required=False, allow_null=True) includes_tax = serializers.BooleanField(required=False, allow_null=True) diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index d847a6e762..89b7af474b 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -40,6 +40,7 @@ from rest_framework import serializers from rest_framework.exceptions import ValidationError from rest_framework.relations import SlugRelatedField from rest_framework.reverse import reverse +from rest_framework.serializers import ListSerializer from pretix.api.auth.utils import get_session_key_for_api_request from pretix.api.serializers import CompatDecimalField, CompatibleJSONField @@ -47,7 +48,7 @@ from pretix.api.serializers.event import SubEventSerializer from pretix.api.serializers.forms import form_field_to_serializer_field from pretix.api.serializers.i18n import I18nAwareModelSerializer from pretix.api.serializers.item import ( - InlineItemVariationSerializer, ItemSerializer, DatafieldSerializer, + InlineItemVariationSerializer, ItemSerializer, DatafieldSerializer, cook_id, system_datafield_numbers, uncook_id, ) from pretix.api.signals import order_api_details, orderposition_api_details from pretix.base.decimal import round_decimal @@ -57,7 +58,7 @@ from pretix.base.media import MEDIA_TYPES from pretix.base.models import ( CachedFile, Checkin, Customer, Device, GiftCard, Invoice, InvoiceAddress, InvoiceLine, Item, ItemVariation, Order, OrderPosition, Question, - QuestionAnswer, ReusableMedium, SalesChannel, Seat, SubEvent, TaxRule, + QuestionAnswer, QuestionnaireChild, ReusableMedium, SalesChannel, Seat, SubEvent, TaxRule, Voucher, ) from pretix.base.models.orders import ( @@ -229,6 +230,50 @@ class InlineSeatSerializer(I18nAwareModelSerializer): fields = ('id', 'name', 'seat_guid', 'zone_name', 'row_name', 'row_label', 'seat_label', 'seat_number') +class MixedAnswerListSerializer(ListSerializer): + def __init__(self, instance=None, *args, **kwargs): + self.parent_instance = instance + super().__init__(instance.answers if instance else None, *args, **kwargs) + + def get_value(self, dictionary): + return dictionary + + def to_representation(self, instance): + answers = super().to_representation(instance.answers) + for qc in QuestionnaireChild.objects.filter(questionnaire__items__id=instance.item_id, system_datafield__isnull=False): + answers.append({ + "question": cook_id(qc), + "answer": instance.get_system_answer(qc.system_datafield), + "question_identifier": f'@{qc.system_datafield}', + "options": [], + "option_identifiers": [], + "questionnaire_id": qc.questionnaire_id, + "system_datafield": qc.system_datafield, + }) + return { + "answers": answers, + } + + def to_internal_value(self, data): + result = {} + answers = [] + for answer in data["answers"]: + try: + qid, dfid, sys_df = uncook_id(answer["question"]) + except ValueError: + if type(answer["question"]) is str and answer["question"].lstrip("@") in system_datafield_numbers: + qid, dfid, sys_df = None, None, answer["question"].lstrip("@") + else: + raise + if dfid: + answer["question"] = dfid + answers.append(answer) + else: + result[sys_df] = Country(answer["answer"]) if sys_df == "country" else answer["answer"] + result["answers"] = super().to_internal_value(answers) + return result + + class AnswerSerializer(I18nAwareModelSerializer): question_identifier = AnswerQuestionIdentifierField(source='*', read_only=True) option_identifiers = AnswerQuestionOptionsIdentifierField(source='*', read_only=True) @@ -591,7 +636,7 @@ class OrderPositionPluginDataField(serializers.Field): class OrderPositionSerializer(I18nAwareModelSerializer): checkins = InlineCheckinSerializer(many=True, read_only=True) print_logs = PrintLogSerializer(many=True, read_only=True) - answers = AnswerSerializer(many=True) + answers = MixedAnswerListSerializer(child=AnswerSerializer(), source='*') downloads = PositionDownloadsField(source='*', read_only=True) order = serializers.SlugRelatedField(slug_field='code', read_only=True) pdf_data = PdfDataSerializer(source='*', read_only=True) @@ -1041,7 +1086,7 @@ class OrderFeeCreateSerializer(I18nAwareModelSerializer): class OrderPositionCreateSerializer(I18nAwareModelSerializer): - answers = AnswerCreateSerializer(many=True, required=False) + answers = MixedAnswerListSerializer(child=AnswerCreateSerializer(required=False), source='*', required=False) addon_to = serializers.IntegerField(required=False, allow_null=True) secret = serializers.CharField(required=False) attendee_name = serializers.CharField(required=False, allow_null=True) diff --git a/src/pretix/api/serializers/orderchange.py b/src/pretix/api/serializers/orderchange.py index a92aa46b03..2894e18b12 100644 --- a/src/pretix/api/serializers/orderchange.py +++ b/src/pretix/api/serializers/orderchange.py @@ -30,7 +30,7 @@ from rest_framework.exceptions import ValidationError from pretix.api.serializers.order import ( AnswerCreateSerializer, AnswerSerializer, CompatibleCountryField, - OrderFeeCreateSerializer, OrderPositionCreateSerializer, + MixedAnswerListSerializer, OrderFeeCreateSerializer, OrderPositionCreateSerializer, ) from pretix.base.models import ItemVariation, Order, OrderFee, OrderPosition from pretix.base.services.orders import OrderChangeManager, OrderError @@ -41,7 +41,7 @@ logger = logging.getLogger(__name__) class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerializer): order = serializers.SlugRelatedField(slug_field='code', queryset=Order.objects.none(), required=True, allow_null=False) - answers = AnswerCreateSerializer(many=True, required=False) + answers = MixedAnswerListSerializer(child=AnswerCreateSerializer(required=False), source='*', required=False) addon_to = serializers.IntegerField(required=False, allow_null=True) secret = serializers.CharField(required=False) attendee_name = serializers.CharField(required=False, allow_null=True) @@ -154,7 +154,7 @@ class OrderFeeCreateForExistingOrderSerializer(OrderFeeCreateSerializer): class OrderPositionInfoPatchSerializer(serializers.ModelSerializer): - answers = AnswerSerializer(many=True) + answers = MixedAnswerListSerializer(child=AnswerSerializer(), source='*') country = CompatibleCountryField(source='*') attendee_name = serializers.CharField(required=False) diff --git a/src/pretix/api/views/order.py b/src/pretix/api/views/order.py index 8d9c3b2414..c221caaead 100644 --- a/src/pretix/api/views/order.py +++ b/src/pretix/api/views/order.py @@ -256,7 +256,7 @@ class OrderViewSetMixin: [request.event], Prefetch('meta_values', queryset=EventMetaValue.objects.select_related('property'), to_attr='meta_values_cached'), - 'questions', + 'questionnaires', 'item_meta_properties', ) return Prefetch( @@ -287,7 +287,9 @@ class OrderViewSetMixin: Prefetch('checkins', queryset=Checkin.objects.select_related('device')), Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')), 'item', 'variation', - Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options', 'question').order_by('question__position')), + Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options', 'question') + # TODO(questionnaires) .order_by('question__position') + ), 'seat', ) )