Questions: add min-length to string/text type questions

This commit is contained in:
Richard Schreiber
2026-08-19 13:49:54 +02:00
parent 4969c12c0a
commit 7f54217990
9 changed files with 47 additions and 4 deletions
+5
View File
@@ -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": [
{
+1 -1
View File
@@ -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)
+2
View File
@@ -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,
@@ -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),
),
]
+13 -1
View File
@@ -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):
+1
View File
@@ -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 = {
@@ -47,6 +47,7 @@
{% bootstrap_field form.valid_datetime_max layout="control" %}
</div>
<div id="valid-string">
{% bootstrap_field form.valid_string_length_min layout="control" %}
{% bootstrap_field form.valid_string_length_max layout="control" %}
</div>
<div id="valid-file">
+1
View File
@@ -2479,6 +2479,7 @@ TEST_QUESTION_RES = {
"valid_datetime_min": None,
"valid_datetime_max": None,
"valid_file_portrait": False,
"valid_string_length_min": None,
"valid_string_length_max": None,
"help_text": {"en": "This is an example question"},
"options": [
+4 -2
View File
@@ -2970,8 +2970,9 @@ class SeatingTestCase(TestCase):
@pytest.mark.django_db
@pytest.mark.parametrize("qtype,answer,expected", [
(Question.TYPE_STRING, "a", "a"),
(Question.TYPE_TEXT, "v", "v"),
(Question.TYPE_STRING, "aaa", "aaa"),
(Question.TYPE_STRING, "a", "a", ValidationError),
(Question.TYPE_TEXT, "vvv", "vvv"),
(Question.TYPE_TEXT, "waaaaay tooooo long", ValidationError),
(Question.TYPE_NUMBER, "0.9", ValidationError),
(Question.TYPE_NUMBER, "1", Decimal("1")),
@@ -3025,6 +3026,7 @@ def test_question_answer_validation(qtype, answer, expected):
valid_datetime_max=datetime.datetime(2018, 1, 16, 16, 0, 0, tzinfo=tzoffset(None, 3600)),
valid_number_min=Decimal('1'),
valid_number_max=Decimal('100'),
valid_string_length_min=3,
valid_string_length_max=8,
)
if isinstance(expected, type) and issubclass(expected, Exception):