From 14a3056bad9b97bbe538cbe4dd57e33b576b5dfe Mon Sep 17 00:00:00 2001 From: Mira Weller Date: Thu, 17 Sep 2026 18:36:09 +0200 Subject: [PATCH] add DjangoDialog / notify_parent - allow to load existing views in iframe ...and some more changes --- src/pretix/base/forms/questions.py | 2 +- src/pretix/control/forms/item.py | 54 +---------------- .../pretixcontrol/items/question_edit.html | 7 +-- .../pretixcontrol/notify_parent.html | 11 ++++ src/pretix/control/views/__init__.py | 24 +++++++- .../static/pretixcontrol/js/notify_parent.js | 8 +++ src/pretix/static/pretixcontrol/js/ui/main.js | 28 +++++++++ .../js/ui/questionnaires/DjangoDialog.vue | 59 +++++++++++++++++++ .../js/ui/questionnaires/Editor.vue | 56 +++++++++++++----- .../js/ui/questionnaires/NativeDialog.vue | 11 +++- .../js/ui/questionnaires/QuestionElement.vue | 11 +++- .../questionnaires/QuestionnaireElement.vue | 20 +++++-- .../pretixcontrol/js/ui/questionnaires/api.ts | 4 +- .../static/pretixcontrol/scss/main.scss | 3 + .../static/pretixpresale/js/ui/questions.js | 2 +- src/tests/control/test_items.py | 7 +-- 16 files changed, 212 insertions(+), 95 deletions(-) create mode 100644 src/pretix/control/templates/pretixcontrol/notify_parent.html create mode 100644 src/pretix/static/pretixcontrol/js/notify_parent.js create mode 100644 src/pretix/static/pretixcontrol/js/ui/questionnaires/DjangoDialog.vue diff --git a/src/pretix/base/forms/questions.py b/src/pretix/base/forms/questions.py index ce32091a72..ad7754f5c0 100644 --- a/src/pretix/base/forms/questions.py +++ b/src/pretix/base/forms/questions.py @@ -878,7 +878,7 @@ class BaseQuestionsForm(forms.Form): field.answer = answers[0] if qc.dependency_question_id: - field.widget.attrs['data-question-dependency'] = qc.dependency_question_id + field.widget.attrs['data-question-dependency'] = f"question_{qc.dependency_question.user_datafield_id}" if qc.dependency_question.user_datafield_id else qc.dependency_question.system_datafield field.widget.attrs['data-question-dependency-values'] = escapejson_attr(json.dumps(qc.dependency_values)) if datafield.type != 'M': field.widget.attrs['required'] = qc.required and not self.all_optional diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py index b90592ffbd..00c90988b5 100644 --- a/src/pretix/control/forms/item.py +++ b/src/pretix/control/forms/item.py @@ -153,54 +153,12 @@ class QuestionForm(I18nModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - if self.instance.container_type == Question.ContainerType.ORDERPOSITION: - self.fields['items'].queryset = self.instance.event.items.all() - self.fields['items'].required = True - else: - del self.fields['items'] - del self.fields['ask_during_checkin'] + if self.instance.container_type != Question.ContainerType.ORDERPOSITION: del self.fields['show_during_checkin'] del self.fields['print_on_invoice'] - self.fields['dependency_question'].widget.attrs['data-container-type'] = self.instance.container_type - self.fields['dependency_question'].queryset = self.instance.event.questions.filter( - type__in=(Question.TYPE_BOOLEAN, Question.TYPE_CHOICE, Question.TYPE_CHOICE_MULTIPLE), - ask_during_checkin=False, - container_type=self.instance.container_type, - ) - if self.instance.pk: - self.fields['dependency_question'].queryset = self.fields['dependency_question'].queryset.exclude( - pk=self.instance.pk - ) self.fields['identifier'].required = False - self.fields['dependency_values'].required = False self.fields['help_text'].widget.attrs['rows'] = 3 - def clean_dependency_values(self): - val = self.data.getlist('dependency_values') - return val - - def clean_dependency_question(self): - dep = val = self.cleaned_data.get('dependency_question') - if dep: - if dep.ask_during_checkin: - raise ValidationError(_('Question cannot depend on a question asked during check-in.')) - - seen_ids = {self.instance.pk} if self.instance else set() - while dep: - if dep.pk in seen_ids: - raise ValidationError(_('Circular dependency between questions detected.')) - seen_ids.add(dep.pk) - dep = dep.dependency_question - return val - - def clean_ask_during_checkin(self): - val = self.cleaned_data.get('ask_during_checkin') - - if val and self.cleaned_data.get('type') in Question.ASK_DURING_CHECKIN_UNSUPPORTED: - raise ValidationError(_('This type of question cannot be asked during check-in.')) - - return val - def clean_show_during_checkin(self): val = self.cleaned_data.get('show_during_checkin') @@ -236,13 +194,9 @@ class QuestionForm(I18nModelForm): 'help_text', 'type', 'required', - 'ask_during_checkin', 'show_during_checkin', 'hidden', 'identifier', - 'items', - 'dependency_question', - 'dependency_values', 'print_on_invoice', 'valid_number_min', 'valid_number_max', @@ -259,17 +213,11 @@ class QuestionForm(I18nModelForm): 'valid_datetime_max': SplitDateTimePickerWidget(without_seconds=True), 'valid_date_min': DatePickerWidget(), 'valid_date_max': DatePickerWidget(), - 'items': forms.CheckboxSelectMultiple( - attrs={'class': 'scrolling-multiple-choice'} - ), - 'dependency_values': forms.SelectMultiple, 'help_text': I18nMarkdownTextarea, } field_classes = { 'valid_datetime_min': SplitDateTimeField, 'valid_datetime_max': SplitDateTimeField, - 'items': ItemMultipleChoiceField, - 'dependency_question': SafeModelChoiceField, } diff --git a/src/pretix/control/templates/pretixcontrol/items/question_edit.html b/src/pretix/control/templates/pretixcontrol/items/question_edit.html index dfe51714a3..6954f2d2cf 100644 --- a/src/pretix/control/templates/pretixcontrol/items/question_edit.html +++ b/src/pretix/control/templates/pretixcontrol/items/question_edit.html @@ -7,14 +7,14 @@ {% if question %} {% blocktrans with name=question.question %}Data field: {{ name }}{% endblocktrans %} {% else %} - {% trans "Question" %} + {% trans "Create new data field" %} {% endif %} {% endblock %} {% block inside %} {% if question %}

{% blocktrans with name=question.question %}Data field: {{ name }}{% endblocktrans %}

{% else %} -

{% trans "Data field" %}

+

{% trans "Create new data field" %}

{% endif %}
{% csrf_token %} @@ -114,9 +114,6 @@ {% bootstrap_field form.identifier layout="control" %} - {% if form.ask_during_checkin %} - {% bootstrap_field form.ask_during_checkin layout="control" %} - {% endif %} {% if form.show_during_checkin %} {% bootstrap_field form.show_during_checkin layout="control" %} {% endif %} diff --git a/src/pretix/control/templates/pretixcontrol/notify_parent.html b/src/pretix/control/templates/pretixcontrol/notify_parent.html new file mode 100644 index 0000000000..6ce7a7acbb --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/notify_parent.html @@ -0,0 +1,11 @@ +{% extends "error.html" %} +{% load i18n %} +{% load eventurl %} +{% load urlreplace %} +{% load static %} + +{% block content %} +

{% trans "Please wait..." %}

+ {{ notify_info|json_script:"notify_info" }} + +{% endblock %} diff --git a/src/pretix/control/views/__init__.py b/src/pretix/control/views/__init__.py index cfff1c4519..f6b4dcd201 100644 --- a/src/pretix/control/views/__init__.py +++ b/src/pretix/control/views/__init__.py @@ -21,10 +21,15 @@ # import collections.abc import warnings +from errno import EMSGSIZE +from django.contrib import messages from django.core.paginator import ( EmptyPage, PageNotAnInteger, UnorderedObjectListWarning, ) +from django.http.response import HttpResponseRedirect +from django.shortcuts import render +from django.urls import reverse from django.utils.translation import gettext_lazy as _ from django.views.generic import edit @@ -38,7 +43,22 @@ class EventBasedFormMixin: return kwargs -class CreateView(EventBasedFormMixin, edit.CreateView): +class TellParentFormMixin: + + def form_valid(self, form): + result = super().form_valid(form) + if self.request.GET.get('notify_parent') and isinstance(result, HttpResponseRedirect): + message_store = messages.get_messages(self.request) + msgs = [{'level': msg.level_tag, 'message': msg.message} for msg in message_store] + #message_store._queued_messages = [] + return render(self.request, 'pretixcontrol/notify_parent.html', { + 'notify_info': {'object': self.object.pk, 'redirect_url': result.url, 'messages': msgs}, + }) + else: + return result + + +class CreateView(EventBasedFormMixin, TellParentFormMixin, edit.CreateView): """ Like Django's default CreateView, but passes the optional event argument to the form. This is necessary for I18nModelForms to work @@ -47,7 +67,7 @@ class CreateView(EventBasedFormMixin, edit.CreateView): pass -class UpdateView(EventBasedFormMixin, edit.UpdateView): +class UpdateView(EventBasedFormMixin, TellParentFormMixin, edit.UpdateView): """ Like Django's default UpdateView, but passes the optional event argument to the form. This is necessary for I18nModelForms to work diff --git a/src/pretix/static/pretixcontrol/js/notify_parent.js b/src/pretix/static/pretixcontrol/js/notify_parent.js new file mode 100644 index 0000000000..c372ec2769 --- /dev/null +++ b/src/pretix/static/pretixcontrol/js/notify_parent.js @@ -0,0 +1,8 @@ +try { + window.parent.postMessage({ + type: 'pretix:notify-parent', + data: JSON.parse(document.getElementById('notify_info').textContent), + }, location.origin) +} catch (e) { + console.error('Could not post message to parent.', e) +} diff --git a/src/pretix/static/pretixcontrol/js/ui/main.js b/src/pretix/static/pretixcontrol/js/ui/main.js index 3d15c6cb93..10dc9f958b 100644 --- a/src/pretix/static/pretixcontrol/js/ui/main.js +++ b/src/pretix/static/pretixcontrol/js/ui/main.js @@ -1121,3 +1121,31 @@ $(function () { return $(this).find('button:not([type=button]), input[type=submit]').length > 0 }).areYouSure({ message: gettext('You have unsaved changes!') }) }) + +function notify_parent_frame() { + window.addEventListener('message', function(e) { + if (e.source === window) return + if (e.origin === location.origin && e.data.type === 'pretix:dialog-handshake') { + if (!window.isInDialog) { + window.isInDialog = true + window.document.documentElement.classList.add('in-iframe') + } + } + if (e.origin === location.origin && e.data.type === 'pretix:dialog-loading') { + e.source.postMessage({ type: 'pretix:dialog-handshake' }) + } + }) + try { + window.parent.postMessage({ + type: 'pretix:dialog-loading', + title: document.title, + }, location.origin) + } catch {} + $(function () { + window.parent.postMessage({ + type: 'pretix:dialog-loaded', + contentHeight: $('body').height(), + }, location.origin) + }) +} +notify_parent_frame() diff --git a/src/pretix/static/pretixcontrol/js/ui/questionnaires/DjangoDialog.vue b/src/pretix/static/pretixcontrol/js/ui/questionnaires/DjangoDialog.vue new file mode 100644 index 0000000000..4de39cadc7 --- /dev/null +++ b/src/pretix/static/pretixcontrol/js/ui/questionnaires/DjangoDialog.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/src/pretix/static/pretixcontrol/js/ui/questionnaires/Editor.vue b/src/pretix/static/pretixcontrol/js/ui/questionnaires/Editor.vue index e2a05b0bdd..ac85dae180 100644 --- a/src/pretix/static/pretixcontrol/js/ui/questionnaires/Editor.vue +++ b/src/pretix/static/pretixcontrol/js/ui/questionnaires/Editor.vue @@ -1,10 +1,10 @@ @@ -174,7 +197,8 @@ const preview_mode = ref(false) :grouped_items="null" :selected_product="null" :preview_mode="false" - @update="saveQuestionnaire(questionnaire)" /> + @update="saveQuestionnaire(questionnaire)" + @invalidate:datafields="refreshDatafieldList()" />
@@ -220,7 +244,8 @@ const preview_mode = ref(false) :grouped_items="grouped_items" :selected_product="selected_product" :preview_mode="true" - @update="saveQuestionnaire(questionnaire)"/> + @update="saveQuestionnaire(questionnaire)" + @invalidate:datafields="refreshDatafieldList()"/>
@@ -233,7 +258,8 @@ const preview_mode = ref(false) :grouped_items="grouped_items" :selected_product="selected_product" :preview_mode="false" - @update="saveQuestionnaire(questionnaire)"/> + @update="saveQuestionnaire(questionnaire)" + @invalidate:datafields="refreshDatafieldList()"/>
diff --git a/src/pretix/static/pretixcontrol/js/ui/questionnaires/NativeDialog.vue b/src/pretix/static/pretixcontrol/js/ui/questionnaires/NativeDialog.vue index d589c41ad8..b413658e7c 100644 --- a/src/pretix/static/pretixcontrol/js/ui/questionnaires/NativeDialog.vue +++ b/src/pretix/static/pretixcontrol/js/ui/questionnaires/NativeDialog.vue @@ -9,6 +9,8 @@ const props = defineProps({ default: "", }, title: '', + noPadding: false, + noScroll: true, }); const visible = ref(false); @@ -28,7 +30,7 @@ const id = useId(); + + diff --git a/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionElement.vue b/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionElement.vue index 72e5862e6f..1a67f09933 100644 --- a/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionElement.vue +++ b/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionElement.vue @@ -4,15 +4,18 @@ import NativeDialog from './NativeDialog.vue'; import I18nTextField from './I18nTextField.vue'; import {useId, ref, computed} from 'vue' import { DragHandle } from 'vue-slicksort'; -import {getDatafieldEditUrl} from "./api"; +import {getDatafieldCreateUrl, getDatafieldEditUrl} from "./api"; import I18nTextArea from "./I18nTextArea.vue"; +import DjangoDialog from "./DjangoDialog.vue"; const id = useId(); const props = defineProps(['question', 'datafields', 'editable', 'possible_dependencies']) -const emit = defineEmits(['removeSelf', 'update']); +const emit = defineEmits(['removeSelf', 'update', 'invalidate:datafields']); const gettext = (window as any).gettext; const question = ref(props.question); +const dlgEditDatafield = ref() + const df = typeof question.value.question === 'number' ? props.datafields.find(el => el.id === question.value.question) : typeof question.value.question === 'string' ? @@ -106,7 +109,7 @@ const editor = ref();

diff --git a/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionnaireElement.vue b/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionnaireElement.vue index a01c94e262..b5f91377f1 100644 --- a/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionnaireElement.vue +++ b/src/pretix/static/pretixcontrol/js/ui/questionnaires/QuestionnaireElement.vue @@ -1,5 +1,5 @@