From 3a97ad20d8c4e94c6ed217630df508457f68bf57 Mon Sep 17 00:00:00 2001 From: Mira Weller Date: Wed, 30 Sep 2026 00:14:59 +0200 Subject: [PATCH] Improve validation --- src/pretix/api/serializers/item.py | 10 +++++ src/pretix/base/models/items.py | 37 +++++++++++++++++++ .../questionnaires/QuestionnaireElement.vue | 16 ++++++-- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/pretix/api/serializers/item.py b/src/pretix/api/serializers/item.py index c27a734498..3a77ffa3af 100644 --- a/src/pretix/api/serializers/item.py +++ b/src/pretix/api/serializers/item.py @@ -668,6 +668,7 @@ class InlineQuestionnaireChildSerializer(I18nAwareModelSerializer): class QuestionnaireSerializer(I18nAwareModelSerializer): + items = serializers.PrimaryKeyRelatedField(many=True, queryset=Item.objects.none(), allow_null=True, required=False) limit_sales_channels = serializers.SlugRelatedField( slug_field="identifier", queryset=SalesChannel.objects.none(), @@ -682,6 +683,7 @@ class QuestionnaireSerializer(I18nAwareModelSerializer): def __init__(self, *args, **kwargs): self.fields['children'] = InlineQuestionnaireChildSerializer(many=True, required=True, context=kwargs['context'], partial=False) + self.fields['items'].child_relation.queryset = kwargs['context']['event'].items.all() self.fields['limit_sales_channels'].child_relation.queryset = kwargs['context']['event'].organizer.sales_channels.all() super().__init__(*args, **kwargs) @@ -725,6 +727,10 @@ class QuestionnaireSerializer(I18nAwareModelSerializer): children_data = validated_data.pop('children') if 'children' in validated_data else [] questionnaire = super().create(validated_data) self.set_children(questionnaire, children_data) + try: + questionnaire.check_constraints() + except ValidationError as e: + raise DrfValidationError(as_serializer_error(e)) return questionnaire @transaction.atomic @@ -733,6 +739,10 @@ class QuestionnaireSerializer(I18nAwareModelSerializer): questionnaire = super().update(instance, validated_data) if children_data is not None: self.set_children(questionnaire, children_data) + try: + questionnaire.check_constraints() + except ValidationError as e: + raise DrfValidationError(as_serializer_error(e)) return questionnaire def set_children(self, questionnaire, new_data): diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 9dbf9037f6..26b5ba1fa9 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -35,6 +35,8 @@ import calendar import os +from itertools import groupby + import sys import uuid import warnings @@ -2040,6 +2042,41 @@ class Questionnaire(LoggedModel): class Meta: ordering = ('position', 'id') + def __str__(self) -> str: + return self.internal_name + + @property + def sortkey(self): + return self.position, self.id + + def __lt__(self, other) -> bool: + return self.sortkey < other.sortkey + + def check_constraints(self): + errors = set() + all_questionnaires = Questionnaire.objects.filter(event=self.event).prefetch_related('children', 'items') + for (type, item_id, sdf, udf), fields in groupby(sorted( + (q.type, item.id, c.system_datafield or '', c.user_datafield_id or 0, q, item) + for q in all_questionnaires + for item in q.items.all() + for c in q.children.all() + ), key=lambda d: (d[0], d[1], d[2], d[3])): + fields = list(fields) + if len(fields) > 1: + type, item_id, sdf, udf, q, item = fields[0] + questionnaires = set(q for type,item,sdf,udf,q,item in fields) + df_label = _('System data field') + f' "{sdf}"' if sdf else _('User-defined data field') + f' "{str(udf)}"' + if len(questionnaires) == 1: + errors.add(_('{datafield} added twice to questionnaire "{questionnaire}"').format( + datafield=df_label, questionnaire=next(str(q) for q in questionnaires) + )) + else: + errors.add(_('{datafield} added twice to the same item "{item}" via questionnaires "{questionnaires}"').format( + datafield=df_label, item=str(item), questionnaires='" and "'.join(str(q) for q in questionnaires) + )) + if errors: + raise ValidationError(list(errors)) + class QuestionnaireChild(LoggedModel): class SystemQuestion(models.TextChoices): diff --git a/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionnaireElement.vue b/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionnaireElement.vue index 06772319ad..7c550edf41 100644 --- a/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionnaireElement.vue +++ b/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionnaireElement.vue @@ -42,7 +42,7 @@ function addExistingDatafield (field) { _cid: useId(), question: field.id, required: false, - label: field.question, + label: field.question ?? {en: field.internal_name}, help_text: {}, dependency_question: null, dependency_values: [], @@ -112,7 +112,7 @@ const isEditable = computed(() => props.selected_product && props.questionnaire.
- + {{ props.questionnaire.internal_name }} @@ -125,7 +125,15 @@ const isEditable = computed(() => props.selected_product && props.questionnaire.
-
{{ questionnaire._err_mes }}
+
+
+ +
+

{{ questionnaire._err_mes }}

+
@@ -167,7 +175,7 @@ const isEditable = computed(() => props.selected_product && props.questionnaire. :title="gettext('Add existing data field')">