mirror of
https://github.com/pretix/pretix.git
synced 2026-10-01 19:14:43 +00:00
Split datafields in datafields and orderdatafields; rename QC datafield ref from 'question' to 'datafield'
This commit is contained in:
@@ -100,6 +100,8 @@ class PretixScanSecurityProfile(AllowListSecurityProfile):
|
||||
('GET', 'api-v1:itemcategory-list'),
|
||||
('GET', 'api-v1:item-list'),
|
||||
('GET', 'api-v1:question-list'),
|
||||
('GET', 'api-v1:compatquestion-list'),
|
||||
('GET', 'api-v1:datafield-list'),
|
||||
('GET', 'api-v1:questionnaire-list'),
|
||||
('GET', 'api-v1:questionnairechild-list'),
|
||||
('GET', 'api-v1:badgelayout-list'),
|
||||
@@ -143,6 +145,8 @@ class PretixScanNoSyncNoSearchSecurityProfile(AllowListSecurityProfile):
|
||||
('GET', 'api-v1:itemcategory-list'),
|
||||
('GET', 'api-v1:item-list'),
|
||||
('GET', 'api-v1:question-list'),
|
||||
('GET', 'api-v1:compatquestion-list'),
|
||||
('GET', 'api-v1:datafield-list'),
|
||||
('GET', 'api-v1:questionnaire-list'),
|
||||
('GET', 'api-v1:questionnairechild-list'),
|
||||
('GET', 'api-v1:badgelayout-list'),
|
||||
@@ -181,6 +185,8 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
|
||||
('GET', 'api-v1:itemcategory-list'),
|
||||
('GET', 'api-v1:item-list'),
|
||||
('GET', 'api-v1:question-list'),
|
||||
('GET', 'api-v1:compatquestion-list'),
|
||||
('GET', 'api-v1:datafield-list'),
|
||||
('GET', 'api-v1:questionnaire-list'),
|
||||
('GET', 'api-v1:questionnairechild-list'),
|
||||
('GET', 'api-v1:badgelayout-list'),
|
||||
|
||||
@@ -712,7 +712,7 @@ class DatafieldSerializer(I18nAwareModelSerializer):
|
||||
options_data = validated_data.pop('options') if 'options' in validated_data else []
|
||||
items = validated_data.pop('items', [])
|
||||
|
||||
question = Question.objects.create(**validated_data, container_type=Question.ContainerType.ORDERPOSITION)
|
||||
question = Question.objects.create(**validated_data, container_type=self.context['container_type'])
|
||||
question.items.set(items)
|
||||
for opt_data in options_data:
|
||||
QuestionOption.objects.create(question=question, **opt_data)
|
||||
@@ -755,18 +755,18 @@ class RenderedMarkdownField(serializers.CharField):
|
||||
|
||||
|
||||
class InlineQuestionnaireChildSerializer(I18nAwareModelSerializer):
|
||||
question = QuestionRefField(allow_null=True, source='*', queryset=Question.objects.none())
|
||||
dependency_question = QuestionRefField(allow_null=True, required=False, queryset=Question.objects.none())
|
||||
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)
|
||||
|
||||
class Meta:
|
||||
model = QuestionnaireChild
|
||||
fields = ('question', 'required', 'label', 'help_text', 'dependency_question', 'dependency_values', 'rendered_help_text', 'cooked_id')
|
||||
fields = ('datafield', 'required', 'label', 'help_text', 'dependency_question', 'dependency_values', 'rendered_help_text', 'cooked_id')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["question"].queryset = self.context["event"].questions.all()
|
||||
self.fields["datafield"].queryset = self.context["event"].questions.all()
|
||||
self.fields["dependency_question"].queryset = self.context["event"].questions.all()
|
||||
|
||||
def validate_dependency_question(self, value):
|
||||
|
||||
@@ -80,7 +80,8 @@ event_router.register(r'subevents', event.SubEventViewSet)
|
||||
event_router.register(r'clone', event.CloneEventViewSet)
|
||||
event_router.register(r'items', item.ItemViewSet)
|
||||
event_router.register(r'categories', item.ItemCategoryViewSet)
|
||||
event_router.register(r'datafields', item.DatafieldViewSet)
|
||||
event_router.register(r'datafields', item.DatafieldViewSet, basename='datafield')
|
||||
event_router.register(r'orderdatafields', item.OrderDatafieldViewSet, basename='orderdatafield')
|
||||
event_router.register(r'questionnaires', item.QuestionnaireViewSet)
|
||||
event_router.register(r'questions', item.CompatQuestionViewSet)
|
||||
event_router.register(r'discounts', discount.DiscountViewSet)
|
||||
|
||||
@@ -579,7 +579,7 @@ with scopes_disabled():
|
||||
fields = ['identifier']
|
||||
|
||||
|
||||
class DatafieldViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
class BaseDatafieldViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = DatafieldSerializer
|
||||
queryset = Question.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
@@ -588,12 +588,24 @@ class DatafieldViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
ordering = ('id')
|
||||
permission = None
|
||||
write_permission = 'event.items:write'
|
||||
container_type = Question.ContainerType.ORDERPOSITION
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.questions.filter(
|
||||
# the container_type parameter is undocumented, this API is going to change in a later release
|
||||
container_type=self.request.GET.get('container_type', Question.ContainerType.ORDERPOSITION),
|
||||
).prefetch_related('options').all()
|
||||
return self.request.event.questions.filter(container_type=self.container_type).prefetch_related('options')
|
||||
|
||||
def get_object(self):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
|
||||
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
||||
|
||||
qid, dfid, sys_df = QuestionnaireChild.uncook_id(self.kwargs[lookup_url_kwarg])
|
||||
self.via_questionnaire_id = qid
|
||||
obj = get_object_or_404(queryset, pk=dfid)
|
||||
|
||||
# May raise a permission denied
|
||||
self.check_object_permissions(self.request, obj)
|
||||
|
||||
return obj
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
@@ -608,6 +620,7 @@ class DatafieldViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['event'] = self.request.event
|
||||
ctx['container_type'] = self.container_type
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
@@ -630,6 +643,13 @@ class DatafieldViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
super().perform_destroy(instance)
|
||||
|
||||
|
||||
class DatafieldViewSet(ConditionalListView, BaseDatafieldViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class OrderDatafieldViewSet(ConditionalListView, BaseDatafieldViewSet):
|
||||
container_type = Question.ContainerType.ORDER
|
||||
|
||||
class QuestionOptionViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = QuestionOptionSerializer
|
||||
queryset = QuestionOption.objects.none()
|
||||
|
||||
@@ -16,10 +16,10 @@ const question = ref(props.question);
|
||||
|
||||
const dlgEditDatafield = ref()
|
||||
|
||||
const df = computed(() => typeof question.value.question === 'number' ?
|
||||
props.datafields.find(el => el.id === question.value.question) :
|
||||
typeof question.value.question === 'string' ?
|
||||
SYSTEM_DATAFIELDS[question.value.question] :
|
||||
const df = computed(() => typeof question.value.datafield === 'number' ?
|
||||
props.datafields.find(el => el.id === question.value.datafield) :
|
||||
typeof question.value.datafield === 'string' ?
|
||||
SYSTEM_DATAFIELDS[question.value.datafield] :
|
||||
null);
|
||||
|
||||
const dependency_values_options = computed(() => props.datafields.find(el => el.id === question.value.dependency_question)?.options);
|
||||
@@ -107,7 +107,7 @@ const editor = ref();
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<p class="form-control-static">
|
||||
<template v-if="typeof question.question === 'number'">
|
||||
<template v-if="typeof question.datafield === 'number'">
|
||||
{{ df.internal_name }}
|
||||
<div>
|
||||
<a class="btn btn-sm btn-default" href="javascript:" @click="dlgEditDatafield.open(getDatafieldEditUrl(df.id))"><span class="fa fa-wrench"></span> Manage data field details</a>
|
||||
@@ -115,7 +115,7 @@ const editor = ref();
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ question.question }}
|
||||
{{ question.datafield }}
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
@@ -149,7 +149,7 @@ const editor = ref();
|
||||
<div class="col-md-9">
|
||||
<select v-model="question.dependency_question" class="form-control">
|
||||
<option :value="null">{{ gettext('(none)') }}</option>
|
||||
<option v-for="(qc, index) in possible_dependencies" :value="qc.question">{{ i18n_any(qc.label) }}</option>
|
||||
<option v-for="(qc, index) in possible_dependencies" :value="qc.datafield">{{ i18n_any(qc.label) }}</option>
|
||||
</select>
|
||||
<select v-model="question.dependency_values" class="form-control" multiple>
|
||||
<option v-for="(qc, index) in dependency_values_options" :value="qc.identifier">{{ i18n_any(qc.answer) }}</option>
|
||||
|
||||
@@ -50,7 +50,7 @@ const props = defineProps<{
|
||||
<div class="checkbox" v-for="channel in sales_channels">
|
||||
<label>
|
||||
<input type="checkbox" :checked="questionnaire.all_sales_channels || questionnaire.limit_sales_channels.indexOf(channel.identifier) !== -1"
|
||||
@change="e => setListState(questionnaire.limit_sales_channels, e.target.checked, channel.identifier)"
|
||||
@change="e => setListState(questionnaire.limit_sales_channels, (e.target as HTMLInputElement).checked, channel.identifier)"
|
||||
:disabled="questionnaire.all_sales_channels">
|
||||
<SalesChannelIcon :sales-channel="channel"/>
|
||||
{{ i18n_any(channel.label) }}
|
||||
@@ -67,7 +67,7 @@ const props = defineProps<{
|
||||
<div class="category-header">{{ category.internal_name || i18n_any(category.name) }}</div>
|
||||
<div class="checkbox" v-for="item in items">
|
||||
<label :for="id + '_' + item.id">
|
||||
<input :id="id + '_' + item.id" type="checkbox" :checked="questionnaire.items.indexOf(item.id) !== -1" @change="e => setListState(questionnaire.items, e.target.checked, item.id)"> {{ item.internal_name || i18n_any(item.name) }}
|
||||
<input :id="id + '_' + item.id" type="checkbox" :checked="questionnaire.items.indexOf(item.id) !== -1" @change="e => setListState(questionnaire.items, (e.target as HTMLInputElement).checked, item.id)"> {{ item.internal_name || i18n_any(item.name) }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@ watch(() => props.questionnaire.children, () => {
|
||||
function addExistingDatafield (field) {
|
||||
props.questionnaire.children.push({
|
||||
_cid: useId(),
|
||||
question: field.id,
|
||||
datafield: field.id,
|
||||
required: false,
|
||||
label: field.question ?? {en: field.internal_name},
|
||||
help_text: {},
|
||||
@@ -63,7 +63,7 @@ function showAddTextblockDialog () {
|
||||
function addTextblock () {
|
||||
props.questionnaire.children.push({
|
||||
_cid: useId(),
|
||||
question: null,
|
||||
datafield: null,
|
||||
required: false,
|
||||
label: newTextblockTitle.value,
|
||||
help_text: newTextblockText.value,
|
||||
@@ -118,7 +118,7 @@ const isEditable = computed(() => props.selected_product && props.questionnaire.
|
||||
<summary class="questionnaire-panel-heading">
|
||||
<div class=" editor-row">
|
||||
<div class="editor-preview-area">
|
||||
<input type="checkbox" @change="e => {setListState(props.questionnaire.items, e.target.checked, selected_product); emit('update')}" v-if="selected_product && !preview_mode" :checked="!isHidden">
|
||||
<input type="checkbox" @change="e => {setListState(props.questionnaire.items, (e.target as HTMLInputElement).checked, selected_product); emit('update')}" v-if="selected_product && !preview_mode" :checked="!isHidden">
|
||||
{{ props.questionnaire.internal_name }}
|
||||
<span class="fa fa-warning" v-if="questionnaire._err_mes"></span>
|
||||
<span class="fa fa-cog fa-spin" v-if="questionnaire._loading"></span>
|
||||
@@ -139,7 +139,7 @@ const isEditable = computed(() => props.selected_product && props.questionnaire.
|
||||
|
||||
<aside class="editor-action-area"><div class="btn-group">
|
||||
<DragHandle tag="button" class="btn btn-default" v-if="!preview_mode"><i class="fa fa-arrows"></i></DragHandle>
|
||||
<button class="btn btn-default" @click="dlgEditor.show()"><i class="fa fa-wrench"></i></button>
|
||||
<button class="btn btn-default" @click="dlgEditor.show()" :title="`#${questionnaire.id}`"><i class="fa fa-wrench"></i></button>
|
||||
</div></aside>
|
||||
</div>
|
||||
</summary>
|
||||
|
||||
@@ -47,7 +47,7 @@ async function api_json_request(resource, method, json_body) {
|
||||
|
||||
export async function getDatafields(container_type) {
|
||||
using pb = ProgressBar.show('loading data fields')
|
||||
return await api_get_all<Datafield>(`organizers/${organizer_slug}/events/${event_slug}/datafields/?container_type=${container_type}&`);
|
||||
return await api_get_all<Datafield>(`organizers/${organizer_slug}/events/${event_slug}/${container_type == 'O' ? 'orderdatafields' : 'datafields'}/`);
|
||||
}
|
||||
|
||||
export async function getQuestionnaires() {
|
||||
|
||||
@@ -49,7 +49,7 @@ export type Questionnaire = {
|
||||
}
|
||||
|
||||
export type QuestionnaireChild = {
|
||||
question: string | number,
|
||||
datafield: string | number,
|
||||
required: boolean,
|
||||
label: I18nString,
|
||||
help_text: I18nString,
|
||||
|
||||
Reference in New Issue
Block a user