diff --git a/doc/api/resources/questions.rst b/doc/api/resources/questions.rst index 5cc5fa32f3..4f10d49841 100644 --- a/doc/api/resources/questions.rst +++ b/doc/api/resources/questions.rst @@ -65,6 +65,7 @@ valid_date_max date Maximum value f valid_datetime_min datetime Minimum value for date and time questions (optional) valid_datetime_max datetime Maximum value for date and time questions (optional) valid_file_portrait boolean Turn on file validation for portrait photos +valid_string_length_min integer Minimum length for string questions (optional) valid_string_length_max integer Maximum length for string questions (optional) dependency_question integer Internal ID of a different question. The current question will only be shown if the question given in @@ -130,6 +131,7 @@ Endpoints "valid_date_max": null, "valid_datetime_min": null, "valid_datetime_max": null, + "valid_string_length_min": null, "valid_string_length_max": null, "valid_file_portrait": false, "dependency_question": null, @@ -211,6 +213,7 @@ Endpoints "valid_datetime_min": null, "valid_datetime_max": null, "valid_file_portrait": false, + "valid_string_length_min": null, "valid_string_length_max": null, "dependency_question": null, "dependency_value": null, @@ -315,6 +318,7 @@ Endpoints "valid_datetime_min": null, "valid_datetime_max": null, "valid_file_portrait": false, + "valid_string_length_min": null, "valid_string_length_max": null, "options": [ { @@ -399,6 +403,7 @@ Endpoints "valid_datetime_min": null, "valid_datetime_max": null, "valid_file_portrait": false, + "valid_string_length_min": null, "valid_string_length_max": null, "options": [ { diff --git a/src/pretix/api/serializers/item.py b/src/pretix/api/serializers/item.py index b34858c49b..08a83f8cf1 100644 --- a/src/pretix/api/serializers/item.py +++ b/src/pretix/api/serializers/item.py @@ -550,7 +550,7 @@ class QuestionSerializer(I18nAwareModelSerializer): 'ask_during_checkin', 'show_during_checkin', 'identifier', 'dependency_question', 'dependency_values', 'hidden', 'dependency_value', 'print_on_invoice', 'help_text', 'valid_number_min', 'valid_number_max', 'valid_date_min', 'valid_date_max', 'valid_datetime_min', 'valid_datetime_max', - 'valid_string_length_max', 'valid_file_portrait') + 'valid_string_length_max', 'valid_string_length_min', 'valid_file_portrait') def validate_identifier(self, value): Question._clean_identifier(self.context['event'], value, self.instance) diff --git a/src/pretix/base/forms/questions.py b/src/pretix/base/forms/questions.py index 98c3727e28..73cdb2998b 100644 --- a/src/pretix/base/forms/questions.py +++ b/src/pretix/base/forms/questions.py @@ -708,6 +708,7 @@ class BaseQuestionsForm(forms.Form): elif q.type == Question.TYPE_STRING: field = forms.CharField( label=escape(q.question), required=required, + min_length=q.valid_string_length_min, max_length=q.valid_string_length_max, help_text=rich_text(q.help_text), initial=initial.answer if initial else None, @@ -715,6 +716,7 @@ class BaseQuestionsForm(forms.Form): elif q.type == Question.TYPE_TEXT: field = forms.CharField( label=escape(q.question), required=required, + min_length=q.valid_string_length_min, max_length=q.valid_string_length_max, help_text=rich_text(q.help_text), widget=forms.Textarea, diff --git a/src/pretix/base/migrations/0310_question_valid_string_length_min.py b/src/pretix/base/migrations/0310_question_valid_string_length_min.py new file mode 100644 index 0000000000..618f974f7d --- /dev/null +++ b/src/pretix/base/migrations/0310_question_valid_string_length_min.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.12 on 2026-08-19 11:31 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("pretixbase", "0309_alter_questionanswer_unique_together_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="question", + name="valid_string_length_min", + field=models.PositiveIntegerField(null=True), + ), + ] diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 088c84789c..6d4a5291ad 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -50,7 +50,7 @@ from dateutil.tz import datetime_exists from django.conf import settings from django.core.exceptions import ValidationError from django.core.validators import ( - MaxLengthValidator, MinValueValidator, RegexValidator, + MaxLengthValidator, MinLengthValidator, MinValueValidator, RegexValidator, ) from django.db import models from django.db.models import Q @@ -1731,6 +1731,11 @@ class Question(LoggedModel): valid_datetime_max = models.DateTimeField(null=True, blank=True, verbose_name=_('Maximum value'), help_text=_('Currently not supported in our apps and during check-in')) + valid_string_length_min = models.PositiveIntegerField(null=True, blank=True, + verbose_name=_('Minimum length'), + help_text=_( + 'Currently not supported in our apps and during check-in' + )) valid_string_length_max = models.PositiveIntegerField(null=True, blank=True, verbose_name=_('Maximum length'), help_text=_( @@ -1885,6 +1890,11 @@ class Question(LoggedModel): else: raise ValidationError(_('Unknown country code.')) elif self.type in (Question.TYPE_STRING, Question.TYPE_TEXT): + if self.valid_string_length_min is not None and len(answer) < self.valid_string_length_min: + raise ValidationError(MinLengthValidator.message % { + 'limit_value': self.valid_string_length_min, + 'show_value': len(answer) + }) if self.valid_string_length_max is not None and len(answer) > self.valid_string_length_max: raise ValidationError(MaxLengthValidator.message % { 'limit_value': self.valid_string_length_max, @@ -1906,6 +1916,8 @@ class Question(LoggedModel): raise ValidationError(_("The maximum date must not be before the minimum value.")) if self.valid_number_max and self.valid_number_min and self.valid_number_min > self.valid_number_max: raise ValidationError(_("The maximum value must not be lower than the minimum value.")) + if self.valid_string_length_max and self.valid_string_length_min and self.valid_string_length_min > self.valid_string_length_max: + raise ValidationError(_("The maximum length must not be shorter than the minimum length.")) super().clean() def clean_type_change(self, old_type, new_type): diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py index 1f2fceca1c..b90592ffbd 100644 --- a/src/pretix/control/forms/item.py +++ b/src/pretix/control/forms/item.py @@ -251,6 +251,7 @@ class QuestionForm(I18nModelForm): 'valid_date_min', 'valid_date_max', 'valid_file_portrait', + 'valid_string_length_min', 'valid_string_length_max', ] widgets = { diff --git a/src/pretix/control/templates/pretixcontrol/items/question_edit.html b/src/pretix/control/templates/pretixcontrol/items/question_edit.html index d241be5651..864b421687 100644 --- a/src/pretix/control/templates/pretixcontrol/items/question_edit.html +++ b/src/pretix/control/templates/pretixcontrol/items/question_edit.html @@ -47,6 +47,7 @@ {% bootstrap_field form.valid_datetime_max layout="control" %}