From 2c88efceb128d6e032b22528d4ab150dbdc5d2f3 Mon Sep 17 00:00:00 2001 From: Mira Weller Date: Fri, 30 Oct 2026 12:39:16 +0100 Subject: [PATCH] QuestionnaireChild -> QuestionnaireRow --- src/pretix/api/serializers/item.py | 50 +++++++------- src/pretix/api/serializers/order.py | 12 ++-- src/pretix/api/views/checkin.py | 4 +- src/pretix/api/views/item.py | 20 +++--- src/pretix/base/forms/questions.py | 46 ++++++------- .../0312_questionnaire_questionnairechild.py | 68 +++++++++---------- src/pretix/base/models/__init__.py | 2 +- src/pretix/base/models/event.py | 4 +- src/pretix/base/models/items.py | 24 +++---- src/pretix/base/models/orders.py | 50 +++++++------- src/pretix/base/services/cart.py | 2 +- src/pretix/base/services/checkin.py | 4 +- src/pretix/base/views/mixins.py | 6 +- src/pretix/control/views/item.py | 4 +- src/pretix/presale/checkoutflow.py | 4 +- src/pretix/presale/views/__init__.py | 6 +- .../js/ui/questionnaires/Editor.vue | 6 +- .../questionnaires/QuestionnaireElement.vue | 16 ++--- .../js/ui/questionnaires/model.ts | 4 +- 19 files changed, 166 insertions(+), 166 deletions(-) diff --git a/src/pretix/api/serializers/item.py b/src/pretix/api/serializers/item.py index 714194d275..58b12fcbb2 100644 --- a/src/pretix/api/serializers/item.py +++ b/src/pretix/api/serializers/item.py @@ -61,7 +61,7 @@ from pretix.base.models import ( ItemVariation, ItemVariationMetaValue, Question, QuestionAnswer, QuestionOption, Quota, SalesChannel, ) -from pretix.base.models.items import Questionnaire, QuestionnaireChild +from pretix.base.models.items import Questionnaire, QuestionnaireRow from pretix.base.settings import ( PERSON_NAME_SALUTATIONS, PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS, ) @@ -353,7 +353,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer): self.fields['variations'].child.fields['limit_sales_channels'].child_relation.queryset = self.context['event'].organizer.sales_channels.all() def get_personalized(self, item): - return QuestionnaireChild.objects.filter(questionnaire__items=item, system_datafield__in=('@attendee_name_parts', '@attendee_email')).exists() + return QuestionnaireRow.objects.filter(questionnaire__items=item, system_datafield__in=('@attendee_name_parts', '@attendee_email')).exists() def put_personalized(self, item, new_value): if item and new_value != self.get_personalized(item): @@ -588,7 +588,7 @@ class DatafieldRelatedField(serializers.SlugRelatedField): if data is None: return {'user_datafield': None, 'system_datafield': None} elif type(data) == str and data.startswith('@'): - if data not in QuestionnaireChild.SystemDatafield.values: + if data not in QuestionnaireRow.SystemDatafield.values: self.fail("invalid_choice", input=data) return {'user_datafield': None, 'system_datafield': data} elif type(data) == str: @@ -631,7 +631,7 @@ class QuestionFormSerializer(I18nAwareModelSerializer): options = InlineQuestionOptionSerializer(source='user_datafield.options', many=True, required=False, default=[]) class Meta: - model = QuestionnaireChild + model = QuestionnaireRow fields = ('datafield', 'label', 'type', 'required', 'help_text', 'items', 'options', 'position', 'show_during_checkin', 'identifier', 'dependency_question', 'dependency_values', 'print_on_invoice', 'valid_number_min', @@ -646,7 +646,7 @@ class QuestionFormSerializer(I18nAwareModelSerializer): if isinstance(instance, QuestionAnswer): position = instance.cartposition if instance.cartposition_id else instance.orderposition print('searching ', repr(instance), repr(position), repr(instance.question), instance.pk, position.item_id, self.context) # TODO remove print - qcs = list(QuestionnaireChild.objects.filter(user_datafield_id=instance.question_id, questionnaire__items__in=[position.item_id])) + qcs = list(QuestionnaireRow.objects.filter(user_datafield_id=instance.question_id, questionnaire__items__in=[position.item_id])) return next((qc for qc in qcs if qc.questionnaire.type == self.context.get('preferred_questionnaire_type', Questionnaire.QuestionnaireType.ORDER_POSITION_SALE)), qcs[0]) return super().get_attribute(instance) @@ -681,7 +681,7 @@ class QuestionFormSerializer(I18nAwareModelSerializer): part["type"] = Question.FieldType.CHOICE part["options"] = [{"id": idx + 1, "identifier": k, "answer": i18n_all_from_gettext(v, settings.locales), "position": idx + 1} for idx, (k, v) in enumerate(PERSON_NAME_SALUTATIONS)] - part["id"] = QuestionnaireChild.cook_id_from_parts(row['questionnaire_id'], None, part["identifier"]) + part["id"] = QuestionnaireRow.cook_id_from_parts(row['questionnaire_id'], None, part["identifier"]) part["required"] = part["required"] and k in REQUIRED_NAME_PARTS mangled_results.append(part) pos_offset += 1 @@ -720,7 +720,7 @@ class POSCompatQuestionSerializer(QuestionFormSerializer): options = InlineQuestionOptionSerializer(source='user_datafield.options', many=True, required=False, default=[]) class Meta: - model = QuestionnaireChild + model = QuestionnaireRow fields = ('id', 'question', 'type', 'required', 'items', 'options', 'position', 'ask_during_checkin', 'show_during_checkin', 'identifier', 'dependency_question', 'dependency_values', 'hidden', 'dependency_value', 'print_on_invoice', 'help_text', 'valid_number_min', @@ -735,7 +735,7 @@ class POSCompatQuestionSerializer(QuestionFormSerializer): if isinstance(instance, QuestionAnswer): position = instance.cartposition if instance.cartposition_id else instance.orderposition print('searching ', repr(instance), repr(position), repr(instance.question), instance.pk, position.item_id, self.context) # TODO remove print - qcs = list(QuestionnaireChild.objects.filter(user_datafield_id=instance.question_id, questionnaire__items__in=[position.item_id])) + qcs = list(QuestionnaireRow.objects.filter(user_datafield_id=instance.question_id, questionnaire__items__in=[position.item_id])) return next((qc for qc in qcs if qc.questionnaire.type == self.context.get('preferred_questionnaire_type', Questionnaire.QuestionnaireType.ORDER_POSITION_SALE)), qcs[0]) return super().get_attribute(instance) @@ -775,7 +775,7 @@ class POSCompatQuestionSerializer(QuestionFormSerializer): part["type"] = Question.FieldType.CHOICE part["options"] = [{"id": idx + 1, "identifier": k, "answer": i18n_all_from_gettext(v, settings.locales), "position": idx + 1} for idx, (k, v) in enumerate(PERSON_NAME_SALUTATIONS)] - part["id"] = QuestionnaireChild.cook_id_from_parts(row['questionnaire_id'], None, part["identifier"]) + part["id"] = QuestionnaireRow.cook_id_from_parts(row['questionnaire_id'], None, part["identifier"]) part["required"] = part["required"] and k in REQUIRED_NAME_PARTS mangled_results.append(part) pos_offset += 1 @@ -890,14 +890,14 @@ class RenderedMarkdownField(serializers.CharField): return rich_text(value) -class InlineQuestionnaireChildSerializer(I18nAwareModelSerializer): +class InlineQuestionnaireRowSerializer(I18nAwareModelSerializer): datafield = DatafieldRelatedField(allow_null=True, source='*', queryset=Question.objects.none()) dependency_question = DatafieldRelatedField(allow_null=True, required=False, queryset=Question.objects.none()) rendered_help_text = RenderedMarkdownField(read_only=True, source='help_text') cooked_id = serializers.IntegerField(read_only=True, allow_null=True) class Meta: - model = QuestionnaireChild + model = QuestionnaireRow fields = ('datafield', 'required', 'label', 'help_text', 'dependency_question', 'dependency_values', 'rendered_help_text', 'cooked_id') def __init__(self, *args, **kwargs): @@ -925,10 +925,10 @@ class QuestionnaireSerializer(I18nAwareModelSerializer): class Meta: model = Questionnaire - fields = ('id', 'type', 'internal_name', 'items', 'position', 'all_sales_channels', 'limit_sales_channels', 'children') + fields = ('id', 'type', 'internal_name', 'items', 'position', 'all_sales_channels', 'limit_sales_channels', 'rows') def __init__(self, *args, **kwargs): - self.fields['children'] = InlineQuestionnaireChildSerializer(many=True, required=True, context=kwargs['context'], partial=False) + self.fields['rows'] = InlineQuestionnaireRowSerializer(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) @@ -955,14 +955,14 @@ class QuestionnaireSerializer(I18nAwareModelSerializer): #Question.clean_items(event, full_data.get('items') or []) if (full_data.get('type') == Questionnaire.QuestionnaireType.ORDER_POSITION_CHECKIN - and any(c['dependency_question'] for c in full_data['children'])): + and any(c['dependency_question'] for c in full_data['rows'])): raise ValidationError('Dependencies are not supported during check-in.') if (not full_data.get('type').startswith('position_') - and any(c['system_datafield'] for c in full_data['children'])): + and any(c['system_datafield'] for c in full_data['rows'])): raise ValidationError('System data fields are only supported on order positions.') - system_fields = set(c['system_datafield'] for c in full_data['children']) + system_fields = set(c['system_datafield'] for c in full_data['rows']) if ('zipcode' in system_fields or 'state' in system_fields or 'street' in system_fields or 'city' in system_fields) and 'country' not in system_fields: raise ValidationError('The system data fields "street", "zip code", "city", and "state" can only be used in combination with the "country" field.') @@ -970,9 +970,9 @@ class QuestionnaireSerializer(I18nAwareModelSerializer): @transaction.atomic def create(self, validated_data): - children_data = validated_data.pop('children') if 'children' in validated_data else [] + rows_data = validated_data.pop('rows') if 'rows' in validated_data else [] questionnaire = super().create(validated_data) - self.set_children(questionnaire, children_data) + self.set_rows(questionnaire, rows_data) try: Questionnaire.check_constraints(questionnaire.event) except ValidationError as e: @@ -981,20 +981,20 @@ class QuestionnaireSerializer(I18nAwareModelSerializer): @transaction.atomic def update(self, instance, validated_data): - children_data = validated_data.pop('children', None) + rows_data = validated_data.pop('rows', None) questionnaire = super().update(instance, validated_data) - if children_data is not None: - self.set_children(questionnaire, children_data) + if rows_data is not None: + self.set_rows(questionnaire, rows_data) try: Questionnaire.check_constraints(questionnaire.event) except ValidationError as e: raise DrfValidationError(as_serializer_error(e)) return questionnaire - def set_children(self, questionnaire, new_data): + def set_rows(self, questionnaire, new_data): result = {} - child_serializer = self.fields['children'].child - existing_children = questionnaire.children.all() + child_serializer = self.fields['rows'].child + existing_rows = questionnaire.rows.all() def q(qc): return qc['user_datafield'] or qc['system_datafield'] for i, d in enumerate(new_data): @@ -1004,7 +1004,7 @@ class QuestionnaireSerializer(I18nAwareModelSerializer): d.setdefault('help_text', None) d.setdefault('dependency_question', None) d.setdefault('dependency_values', None) - for existing, update_data in zip_longest(existing_children, new_data): + for existing, update_data in zip_longest(existing_rows, new_data): if update_data: if update_data.get('dependency_question'): try: diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index ea5736e19b..6100ff85f3 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -58,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, QuestionnaireChild, ReusableMedium, SalesChannel, Seat, + QuestionAnswer, QuestionnaireRow, ReusableMedium, SalesChannel, Seat, SubEvent, TaxRule, Voucher, ) from pretix.base.models.orders import ( @@ -242,7 +242,7 @@ class MixedAnswerListSerializer(ListSerializer): def to_representation(self, instance): answers = super().to_representation(instance.answers) answers_dict = {answer["question"]: answer for answer in answers} - for qc in QuestionnaireChild.objects.filter(questionnaire__items__id=instance.item_id): + for qc in QuestionnaireRow.objects.filter(questionnaire__items__id=instance.item_id): if qc.user_datafield_id: if answer := answers_dict.get(qc.user_datafield_id): answer = dict(answer) @@ -251,7 +251,7 @@ class MixedAnswerListSerializer(ListSerializer): answer["user_datafield_id"] = qc.user_datafield_id answers.append(answer) elif (sys_answer := instance.get_system_answer(qc.system_datafield)) is not None: - if qc.system_datafield == QuestionnaireChild.SystemDatafield.ATTENDEE_NAME_PARTS: + if qc.system_datafield == QuestionnaireRow.SystemDatafield.ATTENDEE_NAME_PARTS: name_parts = instance.attendee_name_parts settings = self.context['event'].settings for k, v, w, *__ in PERSON_NAME_SCHEMES.get(settings.name_scheme)['fields']: @@ -269,7 +269,7 @@ class MixedAnswerListSerializer(ListSerializer): except ValueError: pass answers.append({ - "question": QuestionnaireChild.cook_id_from_parts(qc.questionnaire_id, None, f'{qc.system_datafield}:{k}'), + "question": QuestionnaireRow.cook_id_from_parts(qc.questionnaire_id, None, f'{qc.system_datafield}:{k}'), "answer": name_parts[k], "question_identifier": f'{qc.system_datafield}:{k}', "options": options, @@ -293,9 +293,9 @@ class MixedAnswerListSerializer(ListSerializer): settings = self.context['event'].settings for answer in data["answers"]: try: - qid, dfid, sys_df = QuestionnaireChild.uncook_id(answer["question"]) + qid, dfid, sys_df = QuestionnaireRow.uncook_id(answer["question"]) except ValueError: - if type(answer["question"]) is str and answer["question"] in QuestionnaireChild.SYSTEM_DATAFIELD_NUMBERS: + if type(answer["question"]) is str and answer["question"] in QuestionnaireRow.SYSTEM_DATAFIELD_NUMBERS: qid, dfid, sys_df = None, None, answer["question"] else: raise ValidationError('Invalid question ID') diff --git a/src/pretix/api/views/checkin.py b/src/pretix/api/views/checkin.py index 2b6704fd1d..b1c3355e97 100644 --- a/src/pretix/api/views/checkin.py +++ b/src/pretix/api/views/checkin.py @@ -65,7 +65,7 @@ from pretix.api.views.order import OrderPositionFilter from pretix.base.i18n import language from pretix.base.models import ( CachedFile, Checkin, CheckinList, Device, Event, Order, OrderPosition, - Question, Questionnaire, QuestionnaireChild, ReusableMedium, + Question, Questionnaire, QuestionnaireRow, ReusableMedium, RevokedTicketSecret, TeamAPIToken, ) from pretix.base.models.orders import PrintLog @@ -799,7 +799,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force, # 5. Pre-validate all incoming answers, handle file upload given_answers = {} if answers_data: - for qc in QuestionnaireChild.objects.filter( + for qc in QuestionnaireRow.objects.filter( questionnaire__items__in=[op.item], questionnaire__type=Questionnaire.QuestionnaireType.ORDER_POSITION_CHECKIN ): if qc.user_datafield_id and (str(qc.user_datafield_id) in answers_data or str(qc.cooked_id) in answers_data): diff --git a/src/pretix/api/views/item.py b/src/pretix/api/views/item.py index 5043cb78c1..2c11e0a695 100644 --- a/src/pretix/api/views/item.py +++ b/src/pretix/api/views/item.py @@ -60,7 +60,7 @@ from pretix.base.models import ( CartPosition, Device, Item, ItemAddOn, ItemBundle, ItemCategory, ItemProgramTime, ItemVariation, Question, QuestionOption, Quota, ) -from pretix.base.models.items import Questionnaire, QuestionnaireChild +from pretix.base.models.items import Questionnaire, QuestionnaireRow from pretix.base.services.quotas import QuotaAvailability from pretix.helpers.dicts import merge_dicts from pretix.helpers.i18n import i18ncomp @@ -472,7 +472,7 @@ with scopes_disabled(): ask_during_checkin = django_filters.BooleanFilter(method='ask_during_checkin_qs') class Meta: - model = QuestionnaireChild + model = QuestionnaireRow fields = ['required'] def ask_during_checkin_qs(self, qs, name, value): @@ -494,7 +494,7 @@ class DeprecationWarningMixin: class POSCompatQuestionViewSet(DeprecationWarningMixin, viewsets.ReadOnlyModelViewSet): serializer_class = POSCompatQuestionSerializer - queryset = QuestionnaireChild.objects.none() + queryset = QuestionnaireRow.objects.none() permission = None sales_channel_ident = None @@ -511,7 +511,7 @@ class POSCompatQuestionViewSet(DeprecationWarningMixin, viewsets.ReadOnlyModelVi ) def get_queryset(self): - return QuestionnaireChild.objects.filter( + return QuestionnaireRow.objects.filter( # only matching sales channel Q(questionnaire__all_sales_channels=True) | Q(questionnaire__limit_sales_channels__identifier=self.sales_channel_ident), # only real questions, no text blocks @@ -533,7 +533,7 @@ class POSCompatQuestionViewSet(DeprecationWarningMixin, viewsets.ReadOnlyModelVi lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field - qid, dfid, sys_df = QuestionnaireChild.uncook_id(self.kwargs[lookup_url_kwarg]) + qid, dfid, sys_df = QuestionnaireRow.uncook_id(self.kwargs[lookup_url_kwarg]) print(self.kwargs[lookup_url_kwarg], qid, dfid, sys_df) if qid and sys_df: obj = get_object_or_404(queryset, questionnaire__pk=qid, system_datafield=sys_df) @@ -608,7 +608,7 @@ class BaseDatafieldViewSet(viewsets.ModelViewSet): lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field - qid, dfid, sys_df = QuestionnaireChild.uncook_id(self.kwargs[lookup_url_kwarg]) + qid, dfid, sys_df = QuestionnaireRow.uncook_id(self.kwargs[lookup_url_kwarg]) self.via_questionnaire_id = qid obj = get_object_or_404(queryset, pk=dfid) @@ -833,7 +833,7 @@ class QuestionnaireViewSet(ConditionalListView, viewsets.ModelViewSet): write_permission = 'event.items:write' def get_queryset(self): - return self.request.event.questionnaires.prefetch_related('children').all() + return self.request.event.questionnaires.prefetch_related('rows').all() def perform_create(self, serializer): serializer.save(event=self.request.event) @@ -871,7 +871,7 @@ class QuestionFormView(generics.GenericAPIView): # like POSCompatQuestionViewSet, but less compat serializer_class = QuestionFormSerializer - queryset = QuestionnaireChild.objects.none() + queryset = QuestionnaireRow.objects.none() permission = None sales_channel_ident = None @@ -888,7 +888,7 @@ class QuestionFormView(generics.GenericAPIView): ) def get_queryset(self): - return QuestionnaireChild.objects.filter( + return QuestionnaireRow.objects.filter( Q(questionnaire__all_sales_channels=True) | Q(questionnaire__limit_sales_channels__identifier=self.sales_channel_ident), questionnaire__event=self.request.event, # legacy compat only for position-level questions @@ -907,7 +907,7 @@ class QuestionFormView(generics.GenericAPIView): lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field - qid, dfid, sys_df = QuestionnaireChild.uncook_id(self.kwargs[lookup_url_kwarg]) + qid, dfid, sys_df = QuestionnaireRow.uncook_id(self.kwargs[lookup_url_kwarg]) print(self.kwargs[lookup_url_kwarg], qid, dfid, sys_df) if qid and sys_df: obj = get_object_or_404(queryset, questionnaire__pk=qid, system_datafield=sys_df) diff --git a/src/pretix/base/forms/questions.py b/src/pretix/base/forms/questions.py index 84fe2948af..04b62dd847 100644 --- a/src/pretix/base/forms/questions.py +++ b/src/pretix/base/forms/questions.py @@ -83,7 +83,7 @@ from pretix.base.invoicing.transmission import ( get_transmission_types, transmission_types, ) from pretix.base.models import ( - InvoiceAddress, Item, Question, Questionnaire, QuestionnaireChild, + InvoiceAddress, Item, Question, Questionnaire, QuestionnaireRow, QuestionOption, ) from pretix.base.models.tax import ask_for_vat_id @@ -963,7 +963,7 @@ class OrderLevelQuestionsForm(BaseQuestionsForm): Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=container.sales_channel.identifier), event=event, type=Questionnaire.QuestionnaireType.ORDER_SALE, ).order_by('position').prefetch_related( - Prefetch('children', QuestionnaireChild.objects.prefetch_related( + Prefetch('rows', QuestionnaireRow.objects.prefetch_related( Prefetch('user_datafield', Question.objects.prefetch_related( Prefetch('options', QuestionOption.objects.prefetch_related(Prefetch( # This prefetch statement is utter bullshit, but it actually prevents Django from doing @@ -975,18 +975,18 @@ class OrderLevelQuestionsForm(BaseQuestionsForm): ))) )) ), - to_attr='childlist') + to_attr='questionnairerowlist') ) answerlist = container.answers.prefetch_related('options') idx = 0 for questionnaire in questionnaires: - for child in getattr(questionnaire, 'childlist', questionnaire.children.all()): - if child.user_datafield: - df = child.user_datafield - self.fields[child.form_field_name] = self.build_user_question_field(request, event, answerlist, child, df) + for qrow in getattr(questionnaire, 'questionnairerowlist', questionnaire.rows.all()): + if qrow.user_datafield: + df = qrow.user_datafield + self.fields[qrow.form_field_name] = self.build_user_question_field(request, event, answerlist, qrow, df) else: - self.fields[child.form_field_name] = self.build_text_block(request, event, child) + self.fields[qrow.form_field_name] = self.build_text_block(request, event, qrow) idx += 1 def clean(self): @@ -1025,14 +1025,14 @@ class TicketLevelQuestionsForm(BaseQuestionsForm): self.fields['requested_valid_from'] = self.build_requested_valid_from_field(event, pos, item) for questionnaire in questionnaires: - for child in getattr(questionnaire, 'childlist', questionnaire.children.all()): - if child.user_datafield: - df = child.user_datafield - self.fields[child.form_field_name] = self.build_user_question_field(request, event, pos.answerlist, child, df) - elif child.system_datafield: - self.fields[child.form_field_name] = self.build_system_question_field(request, event, pos, child) + for qrow in getattr(questionnaire, 'questionnairerowlist', questionnaire.rows.all()): + if qrow.user_datafield: + df = qrow.user_datafield + self.fields[qrow.form_field_name] = self.build_user_question_field(request, event, pos.answerlist, qrow, df) + elif qrow.system_datafield: + self.fields[qrow.form_field_name] = self.build_system_question_field(request, event, pos, qrow) else: - self.fields[child.form_field_name] = self.build_text_block(request, event, child) + self.fields[qrow.form_field_name] = self.build_text_block(request, event, qrow) responses = question_form_fields.send(sender=event, position=pos) data = pos.meta_info_data @@ -1097,7 +1097,7 @@ class TicketLevelQuestionsForm(BaseQuestionsForm): ) def build_system_question_field(self, request, event, pos, qc): - if qc.system_datafield == QuestionnaireChild.SystemDatafield.ATTENDEE_NAME_PARTS: + if qc.system_datafield == QuestionnaireRow.SystemDatafield.ATTENDEE_NAME_PARTS: return NamePartsFormField( max_length=255, required=qc.required and not self.all_optional, @@ -1107,7 +1107,7 @@ class TicketLevelQuestionsForm(BaseQuestionsForm): help_text=rich_text(qc.help_text), initial=pos.attendee_name_parts, ) - if qc.system_datafield == QuestionnaireChild.SystemDatafield.ATTENDEE_EMAIL: + if qc.system_datafield == QuestionnaireRow.SystemDatafield.ATTENDEE_EMAIL: return forms.EmailField( required=qc.required and not self.all_optional, label=escape(qc.label), @@ -1119,7 +1119,7 @@ class TicketLevelQuestionsForm(BaseQuestionsForm): } ) ) - if qc.system_datafield == QuestionnaireChild.SystemDatafield.COMPANY: + if qc.system_datafield == QuestionnaireRow.SystemDatafield.COMPANY: return forms.CharField( required=qc.required and not self.all_optional, label=escape(qc.label), @@ -1128,7 +1128,7 @@ class TicketLevelQuestionsForm(BaseQuestionsForm): initial=pos.company, ) - if qc.system_datafield == QuestionnaireChild.SystemDatafield.STREET: + if qc.system_datafield == QuestionnaireRow.SystemDatafield.STREET: return forms.CharField( required=qc.required and not self.all_optional, label=escape(qc.label), @@ -1140,7 +1140,7 @@ class TicketLevelQuestionsForm(BaseQuestionsForm): }), initial=pos.street, ) - if qc.system_datafield == QuestionnaireChild.SystemDatafield.ZIPCODE: + if qc.system_datafield == QuestionnaireRow.SystemDatafield.ZIPCODE: return forms.CharField( required=False, max_length=30, @@ -1151,7 +1151,7 @@ class TicketLevelQuestionsForm(BaseQuestionsForm): 'autocomplete': 'postal-code', }), ) - if qc.system_datafield == QuestionnaireChild.SystemDatafield.CITY: + if qc.system_datafield == QuestionnaireRow.SystemDatafield.CITY: return forms.CharField( required=False, label=escape(qc.label), @@ -1162,7 +1162,7 @@ class TicketLevelQuestionsForm(BaseQuestionsForm): 'autocomplete': 'address-level2', }), ) - if qc.system_datafield == QuestionnaireChild.SystemDatafield.COUNTRY: + if qc.system_datafield == QuestionnaireRow.SystemDatafield.COUNTRY: country = pos.country or guess_country_from_request(request, event) return CountryField( countries=CachedCountries @@ -1176,7 +1176,7 @@ class TicketLevelQuestionsForm(BaseQuestionsForm): 'data-trigger-address-info': 'on', }), ) - if qc.system_datafield == QuestionnaireChild.SystemDatafield.STATE: + if qc.system_datafield == QuestionnaireRow.SystemDatafield.STATE: country = pos.country or guess_country_from_request(request, event) c = [('', '---')] fprefix = str(self.prefix) + '-' if self.prefix is not None and self.prefix != '-' else '' diff --git a/src/pretix/base/migrations/0312_questionnaire_questionnairechild.py b/src/pretix/base/migrations/0312_questionnaire_questionnairechild.py index b124d80558..9b09065894 100644 --- a/src/pretix/base/migrations/0312_questionnaire_questionnairechild.py +++ b/src/pretix/base/migrations/0312_questionnaire_questionnairechild.py @@ -52,7 +52,7 @@ def migrate_questions_forward(apps, schema_editor): Item = apps.get_model("pretixbase", "Item") Question = apps.get_model("pretixbase", "Question") Questionnaire = apps.get_model("pretixbase", "Questionnaire") - QuestionnaireChild = apps.get_model("pretixbase", "QuestionnaireChild") + QuestionnaireRow = apps.get_model("pretixbase", "QuestionnaireRow") EventSettingsStore = apps.get_model('pretixbase', 'Event_SettingsStore') qc_candidate = namedtuple('qc_candidate', ['item_info', 'question_id', 'question']) @@ -83,33 +83,33 @@ def migrate_questions_forward(apps, schema_editor): else: internal_name = _('personalized products') - # create questionnaires and children + # create questionnaires and rows questionnaire = Questionnaire.objects.create( event=event, type=questionnaire_type, position=0, all_sales_channels=True, internal_name=internal_name ) questionnaire.items.set([ii.id for ii in item_infos]) deps = {} - for position, child in enumerate(questions): - print(position, child) - if isinstance(child, FakeQuestion): - QuestionnaireChild.objects.create( + for position, question in enumerate(questions): + print(position, question) + if isinstance(question, FakeQuestion): + QuestionnaireRow.objects.create( questionnaire=questionnaire, position=position + 1, - system_datafield=child.id, - required=child.required, - label=child.question, + system_datafield=question.id, + required=question.required, + label=question.question, ) else: - deps[child.id] = QuestionnaireChild.objects.create( + deps[question.id] = QuestionnaireRow.objects.create( questionnaire=questionnaire, position=position + 1, - user_datafield=child, - required=child.required, - label=child.question, - help_text=child.help_text, - dependency_question=deps[child.dependency_question.id] if child.dependency_question else None, - dependency_values=child.dependency_values, + user_datafield=question, + required=question.required, + label=question.question, + help_text=question.help_text, + dependency_question=deps[question.dependency_question.id] if question.dependency_question else None, + dependency_values=question.dependency_values, ) for event in Event.objects.iterator(): @@ -127,7 +127,7 @@ def migrate_questions_forward(apps, schema_editor): personalized_items = event.items.filter(personalized=True).values_list('id', 'internal_name', 'name', 'personalized', named=True) # [position_checkout] presale - children = chain(( + rows = chain(( qc_candidate(item, 0, q) for q in get_fake_questions(settings) for item in personalized_items @@ -137,45 +137,45 @@ def migrate_questions_forward(apps, schema_editor): for item in q.items.values_list('id', 'internal_name', 'name', 'personalized', named=True) )) - create_grouped_item_questionnaires(event, children, '', Questionnaire.QuestionnaireType.ORDER_POSITION_SALE, personalized_items) + create_grouped_item_questionnaires(event, rows, '', Questionnaire.QuestionnaireType.ORDER_POSITION_SALE, personalized_items) # [position_checkin] checkin - children = chain(( + rows = chain(( qc_candidate(item, q.id, q) for q in questions.filter(ask_during_checkin=True) for item in q.items.values_list('id', 'internal_name', 'name', 'personalized', named=True) )) - create_grouped_item_questionnaires(event, children, _('Check-in questions for '), Questionnaire.QuestionnaireType.ORDER_POSITION_CHECKIN, personalized_items) + create_grouped_item_questionnaires(event, rows, _('Check-in questions for '), Questionnaire.QuestionnaireType.ORDER_POSITION_CHECKIN, personalized_items) # [position_hidden] hidden - children = chain(( + rows = chain(( qc_candidate(item, q.id, q) for q in questions.filter(hidden=True, ask_during_checkin=False) for item in q.items.values_list('id', 'internal_name', 'name', 'personalized', named=True) )) - create_grouped_item_questionnaires(event, children, _('Hidden questions for '), Questionnaire.QuestionnaireType.ORDER_POSITION_HIDDEN, personalized_items) + create_grouped_item_questionnaires(event, rows, _('Hidden questions for '), Questionnaire.QuestionnaireType.ORDER_POSITION_HIDDEN, personalized_items) # [O] get all order-level questions questions = list(event.questions.filter(container_type='O', hidden=False).order_by('position')) if questions: - # create questionnaires and children + # create questionnaires and rows questionnaire = Questionnaire.objects.create( event=event, type=Questionnaire.QuestionnaireType.ORDER_SALE, position=0, all_sales_channels=True, internal_name=_('Per-order questions'), ) deps = {} - for position, child in enumerate(questions): - deps[child.id] = QuestionnaireChild.objects.create( + for position, question in enumerate(questions): + deps[question.id] = QuestionnaireRow.objects.create( questionnaire=questionnaire, position=position + 1, - user_datafield=child, - required=child.required, - label=child.question, - help_text=child.help_text, - dependency_question=deps[child.dependency_question.id] if child.dependency_question else None, - dependency_values=child.dependency_values, + user_datafield=question, + required=question.required, + label=question.question, + help_text=question.help_text, + dependency_question=deps[question.dependency_question.id] if question.dependency_question else None, + dependency_values=question.dependency_values, ) @@ -208,7 +208,7 @@ class Migration(migrations.Migration): bases=(models.Model, pretix.base.models.base.LoggingMixin), ), migrations.CreateModel( - name='QuestionnaireChild', + name='QuestionnaireRow', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), ('position', models.PositiveIntegerField(default=0)), @@ -217,8 +217,8 @@ class Migration(migrations.Migration): ('label', i18nfield.fields.I18nTextField()), ('help_text', i18nfield.fields.I18nTextField(null=True)), ('dependency_values', pretix.base.models.fields.MultiStringField(default=[])), - ('dependency_question', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dependent_questions', to='pretixbase.questionnairechild')), - ('questionnaire', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='children', to='pretixbase.questionnaire')), + ('dependency_question', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dependent_questions', to='pretixbase.questionnairerow')), + ('questionnaire', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rows', to='pretixbase.questionnaire')), ('user_datafield', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='references', to='pretixbase.question')), ], options={ diff --git a/src/pretix/base/models/__init__.py b/src/pretix/base/models/__init__.py index 68172979f0..126a1c977b 100644 --- a/src/pretix/base/models/__init__.py +++ b/src/pretix/base/models/__init__.py @@ -37,7 +37,7 @@ from .invoices import Invoice, InvoiceLine, invoice_filename from .items import ( Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaProperty, ItemMetaValue, ItemProgramTime, ItemVariation, ItemVariationMetaValue, Question, - Questionnaire, QuestionnaireChild, QuestionOption, Quota, SubEventItem, + Questionnaire, QuestionnaireRow, QuestionOption, Quota, SubEventItem, SubEventItemVariation, itempicture_upload_to, ) from .log import LogEntry diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index babc95489a..805d728ee3 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -76,7 +76,7 @@ from pretix.helpers.daterange import daterange from pretix.helpers.hierarkey import clean_filename from pretix.helpers.json import CustomJSONEncoder, safe_string from pretix.helpers.thumb import get_thumbnail -from . import QuestionnaireChild +from . import QuestionnaireRow from ..settings import settings_hierarkey from .organizer import Organizer, Team @@ -740,7 +740,7 @@ class Event(EventMixin, LoggedModel): self.settings.mail_send_download_reminder_attendee = True from . import Questionnaire q = self.questionnaires.create(internal_name=str(_('Attendee data')), type=Questionnaire.QuestionnaireType.ORDER_POSITION_SALE, position=0) - q.children.create(label=str(_('Attendee name')), system_datafield=QuestionnaireChild.SystemDatafield.ATTENDEE_NAME_PARTS, position=0) + q.rows.create(label=str(_('Attendee name')), system_datafield=QuestionnaireRow.SystemDatafield.ATTENDEE_NAME_PARTS, position=0) @property def social_image(self): diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index d28ec7e224..e4167fe59e 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -1680,7 +1680,7 @@ class Question(LoggedModel): # TODO(questionnaires) - rename to Datafield ? choices=FieldType.choices, verbose_name=_("Question type") ) - tbd_required = models.BooleanField( # TODO(questionnaires) : to be removed, -> QuestionnaireChild + tbd_required = models.BooleanField( # TODO(questionnaires) : to be removed, -> QuestionnaireRow default=False, verbose_name=_("Required question"), db_column="required", @@ -1692,7 +1692,7 @@ class Question(LoggedModel): # TODO(questionnaires) - rename to Datafield ? # blank=True, # help_text=_('This question will be asked to buyers of the selected products') # ) - tbd_position = models.PositiveIntegerField( # TODO(questionnaires) : to be removed, -> Questionnaire + QuestionnaireChild + tbd_position = models.PositiveIntegerField( # TODO(questionnaires) : to be removed, -> Questionnaire + QuestionnaireRow default=0, verbose_name=_("Position"), db_column="position", @@ -1717,10 +1717,10 @@ class Question(LoggedModel): # TODO(questionnaires) - rename to Datafield ? verbose_name=_('Print answer on invoices'), default=False ) - dependency_question = models.ForeignKey( # TODO(questionnaires) : to be removed, -> QuestionnaireChild + dependency_question = models.ForeignKey( # TODO(questionnaires) : to be removed, -> QuestionnaireRow 'Question', null=True, blank=True, on_delete=models.SET_NULL, related_name='dependent_questions' ) - dependency_values = MultiStringField(default=[]) # TODO(questionnaires) : to be removed, -> QuestionnaireChild + dependency_values = MultiStringField(default=[]) # TODO(questionnaires) : to be removed, -> QuestionnaireRow valid_number_min = models.DecimalField(decimal_places=6, max_digits=30, null=True, blank=True, verbose_name=_('Minimum value'), help_text=_('Currently not supported in our apps and during check-in')) @@ -2066,14 +2066,14 @@ class Questionnaire(LoggedModel): @staticmethod def check_constraints(event): errors = set() - all_questionnaires = Questionnaire.objects.filter(event=event).prefetch_related('children', 'items') + all_questionnaires = Questionnaire.objects.filter(event=event).prefetch_related('rows', 'items') all_sales_channels = event.sales_channels.all() for (sales_channel, type, item_id, sdf, udf), fields in groupby(sorted( (sales_channel, q.type, item.id, c.system_datafield or '', c.user_datafield_id or 0, q, item) for q in all_questionnaires for sales_channel in (q.limit_sales_channels if not q.all_sales_channels else all_sales_channels).values_list('pk', flat=True) for item in q.items.all() - for c in q.children.all() + for c in q.rows.all() ), key=lambda d: (d[0], d[1], d[2], d[3], d[4])): fields = list(fields) if len(fields) > 1: @@ -2092,7 +2092,7 @@ class Questionnaire(LoggedModel): raise ValidationError(list(errors)) -class QuestionnaireChild(LoggedModel): +class QuestionnaireRow(LoggedModel): class SystemDatafield(models.TextChoices): ATTENDEE_NAME_PARTS = '@attendee_name_parts', _('Attendee name') ATTENDEE_EMAIL = '@attendee_email', _('Attendee email') @@ -2122,7 +2122,7 @@ class QuestionnaireChild(LoggedModel): questionnaire = models.ForeignKey( Questionnaire, - related_name="children", + related_name="rows", on_delete=models.CASCADE ) position = models.PositiveIntegerField( @@ -2153,7 +2153,7 @@ class QuestionnaireChild(LoggedModel): null=True, blank=True, ) dependency_question = models.ForeignKey( - 'QuestionnaireChild', null=True, blank=True, on_delete=models.SET_NULL, related_name='dependent_questions' + 'pretix.base.models.items.QuestionnaireRow', null=True, blank=True, on_delete=models.SET_NULL, related_name='dependent_questions' ) dependency_values = MultiStringField(default=[]) @@ -2172,13 +2172,13 @@ class QuestionnaireChild(LoggedModel): @property def cooked_id(self): if self.user_datafield_id or self.system_datafield: - return QuestionnaireChild.cook_id_from_parts(self.questionnaire_id, self.user_datafield_id, self.system_datafield) + return QuestionnaireRow.cook_id_from_parts(self.questionnaire_id, self.user_datafield_id, self.system_datafield) else: return None @staticmethod def cook_id_from_parts(qid, dfid, sys_df): - df_id = dfid + 100 if dfid else QuestionnaireChild.SYSTEM_DATAFIELD_NUMBERS.index(sys_df) + df_id = dfid + 100 if dfid else QuestionnaireRow.SYSTEM_DATAFIELD_NUMBERS.index(sys_df) assert qid < 1000000000 return qid * 1000000000 + df_id @@ -2190,7 +2190,7 @@ class QuestionnaireChild(LoggedModel): if dfid >= 100: return qid, dfid - 100, None else: - return qid, None, QuestionnaireChild.SYSTEM_DATAFIELD_NUMBERS[dfid] + return qid, None, QuestionnaireRow.SYSTEM_DATAFIELD_NUMBERS[dfid] else: return None, id, None diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index c2ffbb157c..49a1fdf502 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -96,7 +96,7 @@ from ._transactions import ( from .base import LockModel, LoggedModel from .event import Event, SubEvent from .items import ( - Item, ItemVariation, Question, QuestionnaireChild, QuestionOption, Quota, + Item, ItemVariation, Question, QuestionnaireRow, QuestionOption, Quota, ) logger = logging.getLogger(__name__) @@ -1608,37 +1608,37 @@ class AbstractPosition(RoundingCorrectionMixin, models.Model): # answers of other items in the same cart if the question objects have been # selected via prefetch_related if hasattr(self.item, 'relevant_questionnaires'): - children = list(copy.copy(qc) for qq in self.item.relevant_questionnaires for qc in qq.childlist) + qrows = list(copy.copy(qc) for qq in self.item.relevant_questionnaires for qc in qq.questionnairerowlist) else: - children = QuestionnaireChild.objects.filter(questionnaire__event_id=self.item.event_id, questionnaire__items__in=[self.item.id]) + qrows = QuestionnaireRow.objects.filter(questionnaire__event_id=self.item.event_id, questionnaire__items__in=[self.item.id]) if questionnaire_type is not None: - children = children.filter(questionnaire__type=questionnaire_type) + qrows = qrows.filter(questionnaire__type=questionnaire_type) if sales_channel is not None: - children = children.filter(Q(questionnaire__all_sales_channels=True) | Q(questionnaire__limit_sales_channels=sales_channel)) + qrows = qrows.filter(Q(questionnaire__all_sales_channels=True) | Q(questionnaire__limit_sales_channels=sales_channel)) - children = list(children) + qrows = list(qrows) - qc_cache = { - q.pk: q for q in children + row_cache = { + q.pk: q for q in qrows } - def qc_is_visible(parentid, qvals): - if parentid not in qc_cache: + def row_is_visible(parentid, qvals): + if parentid not in row_cache: return False - parentqc = qc_cache[parentid] - if parentqc.dependency_question_id and not qc_is_visible(parentqc.dependency_question_id, parentqc.dependency_values): + parentrow = row_cache[parentid] + if parentrow.dependency_question_id and not row_is_visible(parentrow.dependency_question_id, parentrow.dependency_values): return False - answer_values = self.get_dependency_answer_values(parentqc) + answer_values = self.get_dependency_answer_values(parentrow) return answer_values and any(qval in answer_values for qval in qvals) self.questions = [] - for qc in children: + for qrow in qrows: try: - qc.answer = self.answer_cache[qc.user_datafield_id or qc.system_datafield] + qrow.answer = self.answer_cache[qrow.user_datafield_id or qrow.system_datafield] except KeyError: continue - if not qc.dependency_question_id or qc_is_visible(qc.dependency_question_id, qc.dependency_values): - self.questions.append(qc) + if not qrow.dependency_question_id or row_is_visible(qrow.dependency_question_id, qrow.dependency_values): + self.questions.append(qrow) return self.questions @cached_property @@ -1667,21 +1667,21 @@ class AbstractPosition(RoundingCorrectionMixin, models.Model): raise ValueError('Questionnaire child without datafield has no answer') def get_system_answer(self, system_datafield_name): - if system_datafield_name == QuestionnaireChild.SystemDatafield.ATTENDEE_NAME_PARTS: + if system_datafield_name == QuestionnaireRow.SystemDatafield.ATTENDEE_NAME_PARTS: return self.attendee_name - elif system_datafield_name == QuestionnaireChild.SystemDatafield.ATTENDEE_EMAIL: + elif system_datafield_name == QuestionnaireRow.SystemDatafield.ATTENDEE_EMAIL: return self.attendee_email - elif system_datafield_name == QuestionnaireChild.SystemDatafield.COMPANY: + elif system_datafield_name == QuestionnaireRow.SystemDatafield.COMPANY: return self.company - elif system_datafield_name == QuestionnaireChild.SystemDatafield.STREET: + elif system_datafield_name == QuestionnaireRow.SystemDatafield.STREET: return self.street - elif system_datafield_name == QuestionnaireChild.SystemDatafield.ZIPCODE: + elif system_datafield_name == QuestionnaireRow.SystemDatafield.ZIPCODE: return self.zipcode - elif system_datafield_name == QuestionnaireChild.SystemDatafield.CITY: + elif system_datafield_name == QuestionnaireRow.SystemDatafield.CITY: return self.city - elif system_datafield_name == QuestionnaireChild.SystemDatafield.STATE: + elif system_datafield_name == QuestionnaireRow.SystemDatafield.STATE: return self.state - elif system_datafield_name == QuestionnaireChild.SystemDatafield.COUNTRY: + elif system_datafield_name == QuestionnaireRow.SystemDatafield.COUNTRY: return str(self.country) else: raise ValueError('Unknown system datafield name') diff --git a/src/pretix/base/services/cart.py b/src/pretix/base/services/cart.py index 2fd31ca06c..dcfbb87bac 100644 --- a/src/pretix/base/services/cart.py +++ b/src/pretix/base/services/cart.py @@ -1410,7 +1410,7 @@ class CartManager: for k, v in self._widget_data.items(): if not k.startswith('question-'): continue - # TODO(questionnaires) - switch to QuestionnaireChild + # TODO(questionnaires) - switch to QuestionnaireRow q = cp.item.questions.filter(ask_during_checkin=False, identifier__iexact=k[9:]).first() if q: try: diff --git a/src/pretix/base/services/checkin.py b/src/pretix/base/services/checkin.py index 45511d29d7..ab18edaebf 100644 --- a/src/pretix/base/services/checkin.py +++ b/src/pretix/base/services/checkin.py @@ -55,7 +55,7 @@ from django_scopes import scope, scopes_disabled from pretix.base.models import ( Checkin, CheckinList, Device, Event, Gate, Item, ItemVariation, Order, - OrderPosition, Questionnaire, QuestionnaireChild, QuestionOption, + OrderPosition, Questionnaire, QuestionnaireRow, QuestionOption, ) from pretix.base.signals import checkin_created, periodic_task from pretix.helpers import OF_SELF @@ -1033,7 +1033,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict, # Do this outside of transaction so it is saved even if the checkin fails for some other reason checkin_questions = list( - QuestionnaireChild.objects.filter( + QuestionnaireRow.objects.filter( questionnaire__event=clist.event, questionnaire__type=Questionnaire.QuestionnaireType.ORDER_POSITION_CHECKIN, questionnaire__items__in=[op.item_id], ).select_related('user_datafield', 'questionnaire') diff --git a/src/pretix/base/views/mixins.py b/src/pretix/base/views/mixins.py index d4ea11a97b..c6f9b394fb 100644 --- a/src/pretix/base/views/mixins.py +++ b/src/pretix/base/views/mixins.py @@ -37,7 +37,7 @@ from pretix.base.forms.questions import ( ) from pretix.base.models import ( CartPosition, InvoiceAddress, OrderPosition, Question, QuestionAnswer, - Questionnaire, QuestionnaireChild, QuestionOption, + Questionnaire, QuestionnaireRow, QuestionOption, ) from pretix.base.models.customers import AttendeeProfile from pretix.base.models.orders import CheckoutSession, Order @@ -364,7 +364,7 @@ class OrderQuestionsViewMixin(BaseQuestionsViewMixin): to_attr='answerlist'), Prefetch('item__questionnaires', qqs.prefetch_related( - Prefetch('children', QuestionnaireChild.objects.prefetch_related( + Prefetch('rows', QuestionnaireRow.objects.prefetch_related( Prefetch('user_datafield', Question.objects.prefetch_related( Prefetch('options', QuestionOption.objects.prefetch_related(Prefetch( # This prefetch statement is utter bullshit, but it actually prevents Django from doing @@ -376,7 +376,7 @@ class OrderQuestionsViewMixin(BaseQuestionsViewMixin): ))) )) ), - to_attr='childlist') + to_attr='questionnairerowlist') ), to_attr='relevant_questionnaires') )) diff --git a/src/pretix/control/views/item.py b/src/pretix/control/views/item.py index 70fc1b0e54..512be1b59d 100644 --- a/src/pretix/control/views/item.py +++ b/src/pretix/control/views/item.py @@ -67,7 +67,7 @@ from pretix.api.serializers.item import ( from pretix.base.forms import I18nFormSet from pretix.base.models import ( CartPosition, Item, ItemCategory, ItemProgramTime, ItemVariation, LogEntry, - OrderPosition, Question, QuestionAnswer, QuestionnaireChild, + OrderPosition, Question, QuestionAnswer, QuestionnaireRow, QuestionOption, Quota, SeatCategoryMapping, Voucher, ) from pretix.base.models.event import SubEvent @@ -720,7 +720,7 @@ class QuestionnairesEditor(EventPermissionRequiredMixin, TemplateView): def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx['questionnaire_type_choices'] = textchoices_to_json(Questionnaire.QuestionnaireType, self.request.event) - ctx['system_question_choices'] = textchoices_to_json(QuestionnaireChild.SystemDatafield, self.request.event) + ctx['system_question_choices'] = textchoices_to_json(QuestionnaireRow.SystemDatafield, self.request.event) ctx['question_type_choices'] = textchoices_to_json(Question.FieldType, self.request.event) return ctx diff --git a/src/pretix/presale/checkoutflow.py b/src/pretix/presale/checkoutflow.py index a160493f21..98c182c6f3 100644 --- a/src/pretix/presale/checkoutflow.py +++ b/src/pretix/presale/checkoutflow.py @@ -1093,7 +1093,7 @@ class QuestionsStep(CartQuestionsViewMixin, CartMixin, TemplateFlowStep): for cp in self._positions_for_questions: qc_cache = { - qc.pk: qc for qq in cp.item.relevant_questionnaires for qc in qq.childlist + qc.pk: qc for qq in cp.item.relevant_questionnaires for qc in qq.questionnairerowlist } def question_is_visible(parentid, qvals): @@ -1113,7 +1113,7 @@ class QuestionsStep(CartQuestionsViewMixin, CartMixin, TemplateFlowStep): if not self.all_optional: for qq in cp.item.relevant_questionnaires: - for qc in qq.childlist: + for qc in qq.questionnairerowlist: if qc.user_datafield_id and question_is_required(qc) and qc.user_datafield_id not in cp.answer_cache: if warn: messages.warning(request, _('Please fill in answers to all required questions.')) diff --git a/src/pretix/presale/views/__init__.py b/src/pretix/presale/views/__init__.py index b5bf60e8ef..b59a1566cb 100644 --- a/src/pretix/presale/views/__init__.py +++ b/src/pretix/presale/views/__init__.py @@ -54,7 +54,7 @@ from pretix.base.models import ( CartPosition, Customer, InvoiceAddress, ItemAddOn, OrderFee, Question, QuestionAnswer, Questionnaire, QuestionOption, TaxRule, ) -from pretix.base.models.items import QuestionnaireChild +from pretix.base.models.items import QuestionnaireRow from pretix.base.models.orders import CheckoutSession from pretix.base.services.cart import get_fees from pretix.base.services.pricing import apply_rounding @@ -435,7 +435,7 @@ def get_cart_positions(request): to_attr='answerlist'), Prefetch('item__questionnaires', qqs.prefetch_related( - Prefetch('children', QuestionnaireChild.objects.prefetch_related( + Prefetch('rows', QuestionnaireRow.objects.prefetch_related( Prefetch('user_datafield', Question.objects.prefetch_related( Prefetch('options', QuestionOption.objects.prefetch_related(Prefetch( # This prefetch statement is utter bullshit, but it actually prevents Django from doing @@ -447,7 +447,7 @@ def get_cart_positions(request): ))) )) ), - to_attr='childlist') + to_attr='questionnairerowlist') ), to_attr='relevant_questionnaires') ) diff --git a/src/pretix/static/pretixcontrol/js/ui/questionnaires/Editor.vue b/src/pretix/static/pretixcontrol/js/ui/questionnaires/Editor.vue index 5748da2011..3a1fd6a1be 100644 --- a/src/pretix/static/pretixcontrol/js/ui/questionnaires/Editor.vue +++ b/src/pretix/static/pretixcontrol/js/ui/questionnaires/Editor.vue @@ -42,7 +42,7 @@ function saveQuestionnaire(questionnaire) { } result = result.then(d => { questionnaire.id = d.id - questionnaire.children = d.children + questionnaire.rows = d.rows questionnaire._err_mes = null questionnaire._loading = false return d; @@ -55,14 +55,14 @@ function saveQuestionnaire(questionnaire) { } function addPositionQuestionnaire () { position_questionnaires.value.push({ - all_sales_channels: true, children: [], limit_sales_channels: [], position: position_questionnaires.value.length, + all_sales_channels: true, rows: [], limit_sales_channels: [], position: position_questionnaires.value.length, items: [], internal_name: "Unnamed questionnaire", type: "position_purchase", _new_id: Date.now(), }); } function addOrderQuestionnaire () { order_questionnaires.value.push({ - all_sales_channels: true, children: [], limit_sales_channels: [], position: order_questionnaires.value.length, + all_sales_channels: true, rows: [], limit_sales_channels: [], position: order_questionnaires.value.length, items: [], internal_name: "Unnamed questionnaire", type: "order_purchase", _new_id: Date.now(), }); diff --git a/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionnaireElement.vue b/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionnaireElement.vue index 81014e18b8..8cde8923a6 100644 --- a/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionnaireElement.vue +++ b/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionnaireElement.vue @@ -34,14 +34,14 @@ const emit = defineEmits(['update', 'invalidate:datafields', 'invalidate:questio const sales_channels: Ref = inject('pretix:env:organizer:sales_channels') let nextId = 1; -watch(() => props.questionnaire.children, () => { - for (let qc of props.questionnaire.children) { +watch(() => props.questionnaire.rows, () => { + for (let qc of props.questionnaire.rows) { if (!qc._cid) qc._cid = id + (++nextId); } }) function addExistingDatafield (field) { - props.questionnaire.children.push({ + props.questionnaire.rows.push({ _cid: useId(), datafield: field.identifier, required: false, @@ -61,7 +61,7 @@ function showAddTextblockDialog () { } function addTextblock () { - props.questionnaire.children.push({ + props.questionnaire.rows.push({ _cid: useId(), datafield: null, required: false, @@ -163,14 +163,14 @@ const isEditable = computed(() => props.selected_product && props.questionnaire.

{{ questionnaire._err_mes }}

- - + + diff --git a/src/pretix/static/pretixcontrol/js/ui/questionnaires/model.ts b/src/pretix/static/pretixcontrol/js/ui/questionnaires/model.ts index 7f14fdcc11..7d9105c72a 100644 --- a/src/pretix/static/pretixcontrol/js/ui/questionnaires/model.ts +++ b/src/pretix/static/pretixcontrol/js/ui/questionnaires/model.ts @@ -45,10 +45,10 @@ export type Questionnaire = { position: number, all_sales_channels: boolean, limit_sales_channels: string[], - children: QuestionnaireChild[], + rows: QuestionnaireRow[], } -export type QuestionnaireChild = { +export type QuestionnaireRow = { datafield: string | null, required: boolean, label: I18nString,