Rebased --- Squashed commit of the following:

commit 2177bebf6a
Merge: ea613bf271 d08216d8c5
Author: Mira Weller <weller@rami.io>
Date:   Fri Aug 7 21:22:31 2026 +0200

    Merge branch 'master' into questions-vue3

commit ea613bf271
Author: Mira Weller <weller@rami.io>
Date:   Fri Aug 7 21:21:35 2026 +0200

    wip

commit c8bed5e3b9
Author: Mira Weller <weller@rami.io>
Date:   Fri Aug 7 10:15:00 2026 +0200

    rename get_cart to get_cart_positions (keep old name alive)

commit 1ec27fff05
Author: Mira Weller <weller@rami.io>
Date:   Fri Aug 7 10:01:38 2026 +0200

    filter questionnaires by sales channel

commit 0294f205d0
Author: Mira Weller <weller@rami.io>
Date:   Thu Aug 6 20:06:05 2026 +0200

    refactor: extract build_requested_valid_from_field method

commit 3424aacdc3
Author: Mira Weller <weller@rami.io>
Date:   Thu Aug 6 20:05:44 2026 +0200

    rename presale question forms and mixins

commit 4012889076
Author: Mira Weller <weller@rami.io>
Date:   Thu Aug 6 19:42:29 2026 +0200

    rebase migration

commit fe15d71cd2
Merge: 3ea7ea31b6 4d9dfa88fe
Author: Mira Weller <weller@rami.io>
Date:   Thu Aug 6 19:25:44 2026 +0200

    Merge branch 'master' into questions-vue3

    # Conflicts:
    #	src/pretix/base/forms/questions.py
    #	src/pretix/base/middleware.py
    #	src/pretix/base/templates/pretixbase/redirect.html

commit 3ea7ea31b6
Author: Mira Weller <weller@rami.io>
Date:   Fri Jul 24 17:37:46 2026 +0200

    wip

commit 5b34a25fb5
Author: Mira Weller <weller@rami.io>
Date:   Wed Jun 17 16:21:11 2026 +0200

    use questionnaire data for rendering checkout questions step

commit 9467f1d08d
Author: Mira Weller <weller@rami.io>
Date:   Wed Jun 17 15:54:14 2026 +0200

    refactor: extract build_system_question_field method

commit 85344a7690
Author: Mira Weller <weller@rami.io>
Date:   Wed Jun 17 15:50:41 2026 +0200

    refactor: extract build_user_question_field method

commit 3b47f4aaa7
Author: Mira Weller <weller@rami.io>
Date:   Wed Jun 17 15:49:34 2026 +0200

    refactor: cartpos / orderpos

commit 0309b05121
Author: Mira Weller <weller@rami.io>
Date:   Thu Mar 19 13:52:18 2026 +0100

    vite config: allow cors access from pretix.work subdomains

commit ad092ae0d1
Author: Mira Weller <weller@rami.io>
Date:   Wed Jun 17 12:35:33 2026 +0200

    re-add vue-slicksort dep

commit 9f42d679cc
Author: Mira Weller <weller@rami.io>
Date:   Wed Jun 17 12:28:53 2026 +0200

    rebase migration

commit 9eae9c6de7
Merge: 84c51e031e 694b915d89
Author: Mira Weller <weller@rami.io>
Date:   Wed Jun 17 12:24:49 2026 +0200

    Merge branch 'master' into questions-vue3

    # Conflicts:
    #	.github/workflows/tests.yml
    #	.gitignore
    #	Dockerfile
    #	package-lock.json
    #	package.json
    #	src/pretix/base/management/commands/runserver.py
    #	src/pretix/base/middleware.py
    #	src/pretix/base/templatetags/vite.py
    #	src/pretix/control/forms/global_settings.py
    #	src/pretix/control/templates/pretixcontrol/checkin/list_edit.html
    #	src/pretix/control/views/item.py
    #	src/pretix/presale/views/widget.py
    #	src/pretix/settings.py
    #	src/pretix/static/pretixcontrol/js/ui/checkinrules/App.vue
    #	src/pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
    #	src/pretix/static/pretixcontrol/js/ui/checkinrules/django-interop.ts
    #	src/pretix/static/pretixcontrol/js/ui/checkinrules/lookup-select2.vue
    #	src/pretix/static/pretixcontrol/js/ui/checkinrules/timefield.vue
    #	src/pretix/static/pretixcontrol/js/ui/checkinrules/viz-node.vue
    #	src/pretix/static/pretixpresale/widget/index.html
    #	src/pretix/static/pretixpresale/widget/src/api.ts
    #	src/pretix/static/pretixpresale/widget/src/button.ts
    #	src/pretix/static/pretixpresale/widget/src/components/PriceBox.vue
    #	src/pretix/static/pretixpresale/widget/src/main.ts
    #	src/pretix/static/pretixpresale/widget/src/sharedStore.ts
    #	src/pretix/static/pretixpresale/widget/src/utils.ts
    #	src/pretix/static/pretixpresale/widget/src/widget.ts
    #	src/tests/e2e/conftest.py
    #	vite.config.ts

commit 84c51e031e
Merge: 902ee9242e 222b453b43
Author: Mira Weller <weller@rami.io>
Date:   Wed Jun 17 12:20:05 2026 +0200

    Merge branch 'master' into questions-vue3

commit 902ee9242e
Author: Mira Weller <weller@rami.io>
Date:   Wed Jun 17 12:19:26 2026 +0200

    css comment

commit b758efd4a9
Author: Mira Weller <weller@rami.io>
Date:   Wed Jun 17 12:19:19 2026 +0200

    question dependencies

commit c68fe00502
Author: Mira Weller <weller@rami.io>
Date:   Wed Jun 17 12:18:00 2026 +0200

    add data field edit link

commit b7da9bb6c1
Author: Mira Weller <weller@rami.io>
Date:   Wed Jun 17 12:16:20 2026 +0200

    handle hidden questions

commit 897120d3da
Author: Mira Weller <weller@rami.io>
Date:   Tue May 12 13:04:08 2026 +0200

    add ts model definitions

commit 09abc5725e
Author: Mira Weller <weller@rami.io>
Date:   Tue May 12 11:30:12 2026 +0200

    reorder and save

commit 9a7d5abb1f
Author: Mira Weller <weller@rami.io>
Date:   Tue May 12 11:29:37 2026 +0200

    fix unhandled exception in taxrules API

commit 3cee5f524b
Author: Mira Weller <weller@rami.io>
Date:   Tue May 5 18:12:21 2026 +0200

    use slicksort for reordering questionnaires and questions

commit 3a1a6b988e
Author: Mira Weller <weller@rami.io>
Date:   Tue Apr 28 19:23:35 2026 +0200

    improve i18n, implement "add text block" dialog

commit bdc720f5a2
Author: Mira Weller <weller@rami.io>
Date:   Fri Mar 27 18:57:22 2026 +0100

    wip

commit 583184af38
Author: Mira Weller <weller@rami.io>
Date:   Thu Mar 19 21:45:32 2026 +0100

    change existing views

commit 63d9f7cea8
Author: Mira Weller <weller@rami.io>
Date:   Thu Mar 19 21:45:32 2026 +0100

    adapt vue app to new data model (wip)

commit c3d6fb1bd6
Author: Mira Weller <weller@rami.io>
Date:   Thu Mar 19 21:44:47 2026 +0100

    add Questionnaire and QuestionnaireChild models and their API; migrate existing data

commit bb43acd257
Author: Mira Weller <weller@rami.io>
Date:   Thu Mar 19 13:52:28 2026 +0100

    vite config: add questionnaires app

commit e0744951e8
Author: Mira Weller <weller@rami.io>
Date:   Thu Mar 19 13:52:18 2026 +0100

    vite config: allow cors access from pretix.work subdomains

commit fef1e356f7
Author: Mira Weller <weller@rami.io>
Date:   Thu Mar 19 13:33:24 2026 +0100

    fix out-of-the-box experience

commit 81775f5d2d
Author: Mira Weller <weller@rami.io>
Date:   Thu Mar 19 13:33:24 2026 +0100

    setup questionnaires vue app, add code from question editor proof of concept

commit 4bbcc398cc
Merge: a924d0a266 58840a5fd6
Author: Mira Weller <weller@rami.io>
Date:   Thu Mar 19 13:13:32 2026 +0100

    Merge remote-tracking branch 'refs/remotes/origin/master' into questions-vue3
This commit is contained in:
Mira Weller
2026-08-13 16:17:21 +02:00
parent e55de75679
commit 50431047f1
37 changed files with 1759 additions and 599 deletions
+65 -32
View File
@@ -1477,6 +1477,12 @@ class QuestionAnswer(models.Model):
else:
return self.answer
def to_dependency_values(self):
if self.question.type in (Question.TYPE_CHOICE, Question.TYPE_CHOICE_MULTIPLE):
return [o.identifier for o in self.options.all()]
elif self.question.type in (Question.TYPE_BOOLEAN, Question.TYPE_COUNTRYCODE):
return self.answer
def save(self, *args, **kwargs):
if self.orderposition and self.cartposition:
raise ValueError('QuestionAnswer cannot be linked to an order and a cart position at the same time.')
@@ -1621,53 +1627,80 @@ class AbstractPosition(RoundingCorrectionMixin, models.Model):
def cache_answers(self, all=True):
"""
Creates two properties on the object.
(1) answ: a dictionary of question.id → answer string
(2) questions: a list of Question objects, extended by an 'answer' property
Creates a new property on the object:
questions: a list of Question objects, extended by an 'answer' property
"""
self.answ = {}
for a in getattr(self, 'answerlist', self.answers.all()): # use prefetch_related cache from get_cart
self.answ[a.question_id] = a
# We need to clone our question objects, otherwise we will override the cached
# answers of other items in the same cart if the question objects have been
# selected via prefetch_related
if not all:
if hasattr(self.item, 'questions_to_ask'):
questions = list(copy.copy(q) for q in self.item.questions_to_ask)
if hasattr(self.item, 'relevant_questionnaires'):
children = list(copy.copy(qc) for qq in self.item.relevant_questionnaires for qc in qq.childlist)
else:
questions = list(copy.copy(q) for q in self.item.questions.filter(ask_during_checkin=False,
hidden=False))
children = list(copy.copy(qc) for qq in self.item.questionnaires.filter(type='PS') for qc in qq.children.all())
else:
questions = list(copy.copy(q) for q in self.item.questions.all())
children = list(copy.copy(qc) for qq in self.item.questionnaires.filter(type__startswith='P') for qc in qq.children.all())
question_cache = {
q.pk: q for q in questions
qc_cache = {
q.pk: q for q in children
}
def question_is_visible(parentid, qvals):
if parentid not in question_cache:
def qc_is_visible(parentid, qvals):
if parentid not in qc_cache:
return False
parentq = question_cache[parentid]
if parentq.dependency_question_id and not question_is_visible(parentq.dependency_question_id, parentq.dependency_values):
parentqc = qc_cache[parentid]
if parentqc.dependency_question_id and not qc_is_visible(parentqc.dependency_question_id, parentqc.dependency_values):
return False
if parentid not in self.answ:
return False
return (
('True' in qvals and self.answ[parentid].answer == 'True')
or ('False' in qvals and self.answ[parentid].answer == 'False')
or (any(qval in [o.identifier for o in self.answ[parentid].options.all()] for qval in qvals))
)
answer_values = self.get_dependency_answer_values(parentqc)
return any(qval in answer_values for qval in qvals)
self.questions = []
for q in questions:
if q.id in self.answ:
q.answer = self.answ[q.id]
q.answer.question = q # cache object
for qc in children:
if qc.user_question_id and qc.user_question_id in self.answer_cache:
qc.answer = self.answer_cache[qc.user_question_id]
#qc.answer.question = qc # cache object
elif qc.system_question:
qc.answer = self.get_system_answer(qc.system_question)
#qc.answer.question = qc # cache object
else:
q.answer = ""
if not q.dependency_question_id or question_is_visible(q.dependency_question_id, q.dependency_values):
self.questions.append(q)
qc.answer = ""
if not qc.dependency_question_id or qc_is_visible(qc.dependency_question_id, qc.dependency_values):
self.questions.append(qc)
@cached_property
def answer_cache(self):
return {
aw.question_id: aw for aw in getattr(self, 'answerlist', self.answers.all())
}
def get_dependency_answer_values(self, qc):
if qc.user_question_id:
if qc.user_question_id not in self.answer_cache:
return None
answer = self.answer_cache[qc.user_question_id]
return answer.to_dependency_values()
elif qc.system_question:
return [self.get_system_answer(qc.system_question)]
else:
raise ValueError('Questionnaire child without question has no answer')
def get_system_answer(self, system_question_name):
if system_question_name == 'attendee_name_parts':
return self.attendee_name_parts
elif system_question_name == 'attendee_email':
return self.attendee_email
elif system_question_name == 'street':
return self.street
elif system_question_name == 'zipcode':
return self.zipcode
elif system_question_name == 'city':
return self.city
elif system_question_name == 'state':
return self.state
elif system_question_name == 'country':
return self.country
else:
raise ValueError('Unknown system question name')
@property
def net_price(self):