Improve validation

This commit is contained in:
Mira Weller
2026-09-30 21:03:27 +02:00
parent 703aeee776
commit 3a97ad20d8
3 changed files with 59 additions and 4 deletions
+10
View File
@@ -668,6 +668,7 @@ class InlineQuestionnaireChildSerializer(I18nAwareModelSerializer):
class QuestionnaireSerializer(I18nAwareModelSerializer):
items = serializers.PrimaryKeyRelatedField(many=True, queryset=Item.objects.none(), allow_null=True, required=False)
limit_sales_channels = serializers.SlugRelatedField(
slug_field="identifier",
queryset=SalesChannel.objects.none(),
@@ -682,6 +683,7 @@ class QuestionnaireSerializer(I18nAwareModelSerializer):
def __init__(self, *args, **kwargs):
self.fields['children'] = InlineQuestionnaireChildSerializer(many=True, required=True, context=kwargs['context'], partial=False)
self.fields['items'].child_relation.queryset = kwargs['context']['event'].items.all()
self.fields['limit_sales_channels'].child_relation.queryset = kwargs['context']['event'].organizer.sales_channels.all()
super().__init__(*args, **kwargs)
@@ -725,6 +727,10 @@ class QuestionnaireSerializer(I18nAwareModelSerializer):
children_data = validated_data.pop('children') if 'children' in validated_data else []
questionnaire = super().create(validated_data)
self.set_children(questionnaire, children_data)
try:
questionnaire.check_constraints()
except ValidationError as e:
raise DrfValidationError(as_serializer_error(e))
return questionnaire
@transaction.atomic
@@ -733,6 +739,10 @@ class QuestionnaireSerializer(I18nAwareModelSerializer):
questionnaire = super().update(instance, validated_data)
if children_data is not None:
self.set_children(questionnaire, children_data)
try:
questionnaire.check_constraints()
except ValidationError as e:
raise DrfValidationError(as_serializer_error(e))
return questionnaire
def set_children(self, questionnaire, new_data):
+37
View File
@@ -35,6 +35,8 @@
import calendar
import os
from itertools import groupby
import sys
import uuid
import warnings
@@ -2040,6 +2042,41 @@ class Questionnaire(LoggedModel):
class Meta:
ordering = ('position', 'id')
def __str__(self) -> str:
return self.internal_name
@property
def sortkey(self):
return self.position, self.id
def __lt__(self, other) -> bool:
return self.sortkey < other.sortkey
def check_constraints(self):
errors = set()
all_questionnaires = Questionnaire.objects.filter(event=self.event).prefetch_related('children', 'items')
for (type, item_id, sdf, udf), fields in groupby(sorted(
(q.type, item.id, c.system_datafield or '', c.user_datafield_id or 0, q, item)
for q in all_questionnaires
for item in q.items.all()
for c in q.children.all()
), key=lambda d: (d[0], d[1], d[2], d[3])):
fields = list(fields)
if len(fields) > 1:
type, item_id, sdf, udf, q, item = fields[0]
questionnaires = set(q for type,item,sdf,udf,q,item in fields)
df_label = _('System data field') + f' "{sdf}"' if sdf else _('User-defined data field') + f' "{str(udf)}"'
if len(questionnaires) == 1:
errors.add(_('{datafield} added twice to questionnaire "{questionnaire}"').format(
datafield=df_label, questionnaire=next(str(q) for q in questionnaires)
))
else:
errors.add(_('{datafield} added twice to the same item "{item}" via questionnaires "{questionnaires}"').format(
datafield=df_label, item=str(item), questionnaires='" and "'.join(str(q) for q in questionnaires)
))
if errors:
raise ValidationError(list(errors))
class QuestionnaireChild(LoggedModel):
class SystemQuestion(models.TextChoices):
@@ -42,7 +42,7 @@ function addExistingDatafield (field) {
_cid: useId(),
question: field.id,
required: false,
label: field.question,
label: field.question ?? {en: field.internal_name},
help_text: {},
dependency_question: null,
dependency_values: [],
@@ -112,7 +112,7 @@ const isEditable = computed(() => props.selected_product && props.questionnaire.
<summary class="questionnaire-panel-heading">
<div class=" editor-row">
<div class="editor-preview-area">
<input type="checkbox" @change="e => {setListState(props.questionnaire.items, selected_product, e.target.checked); emit('update')}" v-if="selected_product && !preview_mode" :checked="!isHidden">
<input type="checkbox" @change="e => {setListState(props.questionnaire.items, e.target.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>
@@ -125,7 +125,15 @@ const isEditable = computed(() => props.selected_product && props.questionnaire.
</div>
</summary>
<div class="questionnaire-panel-body" v-if="!isHidden">
<div class="alert alert-warning" v-if="questionnaire._err_mes">{{ questionnaire._err_mes }}</div>
<div class="alert alert-warning" v-if="questionnaire._err_mes">
<dl v-if="questionnaire._err_mes.api_error">
<template v-for="(messages, key) in questionnaire._err_mes.api_error">
<dt>{{ key }}</dt>
<dd><ul><li v-for="message in messages">{{ message }}</li></ul></dd>
</template>
</dl>
<p v-else>{{ questionnaire._err_mes }}</p>
</div>
<div class="form-horizontal" :id="`questionListParent${props.questionnaire.id}`">
<SlickList axis="y" v-model:list="props.questionnaire.children" useDragHandle :appendTo="`#questionListParent${props.questionnaire.id}`" @update:list="emit('update')">
<SlickItem v-for="(child, index) in props.questionnaire.children" :key="child._cid" :index="index">
@@ -167,7 +175,7 @@ const isEditable = computed(() => props.selected_product && props.questionnaire.
:title="gettext('Add existing data field')">
<div class="list-group">
<a href="javascript:" @click="addExistingDatafield(field)" v-for="field in datafields" class="list-group-item">{{ i18n_any(field.question) }}</a>
<a href="javascript:" @click="addExistingDatafield(field)" v-for="field in datafields" class="list-group-item">{{ field.internal_name ?? i18n_any(field.question) }}</a>
</div>
<button @click="dlgAddExisting.close()" class="btn btn-default pull-right">{{ gettext('Cancel') }}</button>