Add min/max validation for date, datetime, and number questions (#1858)

This commit is contained in:
Raphael Michel
2020-11-27 11:02:07 +01:00
committed by GitHub
parent 921b28f8d4
commit 66af5973ec
13 changed files with 262 additions and 13 deletions

View File

@@ -13,10 +13,13 @@ from babel import localedata
from django import forms
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db.models import QuerySet
from django.forms import Select
from django.utils.formats import date_format
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.timezone import get_current_timezone
from django.utils.translation import (
get_language, gettext_lazy as _, pgettext_lazy,
)
@@ -234,6 +237,43 @@ class QuestionCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
option_template_name = 'pretixbase/forms/widgets/checkbox_option_with_links.html'
class MinDateValidator(MinValueValidator):
def __call__(self, value):
try:
return super().__call__(value)
except ValidationError as e:
e.params['limit_value'] = date_format(e.params['limit_value'], 'SHORT_DATE_FORMAT')
raise e
class MinDateTimeValidator(MinValueValidator):
def __call__(self, value):
try:
return super().__call__(value)
except ValidationError as e:
e.params['limit_value'] = date_format(e.params['limit_value'].astimezone(get_current_timezone()), 'SHORT_DATETIME_FORMAT')
raise e
class MaxDateValidator(MaxValueValidator):
def __call__(self, value):
try:
return super().__call__(value)
except ValidationError as e:
e.params['limit_value'] = date_format(e.params['limit_value'], 'SHORT_DATE_FORMAT')
raise e
class MaxDateTimeValidator(MaxValueValidator):
def __call__(self, value):
try:
return super().__call__(value)
except ValidationError as e:
e.params['limit_value'] = date_format(e.params['limit_value'].astimezone(get_current_timezone()), 'SHORT_DATETIME_FORMAT')
raise e
class BaseQuestionsForm(forms.Form):
"""
This form class is responsible for asking order-related questions. This includes
@@ -392,9 +432,10 @@ class BaseQuestionsForm(forms.Form):
elif q.type == Question.TYPE_NUMBER:
field = forms.DecimalField(
label=label, required=required,
min_value=q.valid_number_min or Decimal('0.00'),
max_value=q.valid_number_max,
help_text=q.help_text,
initial=initial.answer if initial else None,
min_value=Decimal('0.00'),
)
elif q.type == Question.TYPE_STRING:
field = forms.CharField(
@@ -453,12 +494,21 @@ class BaseQuestionsForm(forms.Form):
max_size=10 * 1024 * 1024,
)
elif q.type == Question.TYPE_DATE:
attrs = {}
if q.valid_date_min:
attrs['data-min'] = q.valid_date_min.isoformat()
if q.valid_date_max:
attrs['data-max'] = q.valid_date_max.isoformat()
field = forms.DateField(
label=label, required=required,
help_text=help_text,
initial=dateutil.parser.parse(initial.answer).date() if initial and initial.answer else None,
widget=DatePickerWidget(),
widget=DatePickerWidget(attrs),
)
if q.valid_date_min:
field.validators.append(MinDateValidator(q.valid_date_min))
if q.valid_date_max:
field.validators.append(MaxDateValidator(q.valid_date_max))
elif q.type == Question.TYPE_TIME:
field = forms.TimeField(
label=label, required=required,
@@ -471,8 +521,16 @@ class BaseQuestionsForm(forms.Form):
label=label, required=required,
help_text=help_text,
initial=dateutil.parser.parse(initial.answer).astimezone(tz) if initial and initial.answer else None,
widget=SplitDateTimePickerWidget(time_format=get_format_without_seconds('TIME_INPUT_FORMATS')),
widget=SplitDateTimePickerWidget(
time_format=get_format_without_seconds('TIME_INPUT_FORMATS'),
min_date=q.valid_datetime_min,
max_date=q.valid_datetime_max
),
)
if q.valid_datetime_min:
field.validators.append(MinDateTimeValidator(q.valid_datetime_min))
if q.valid_datetime_max:
field.validators.append(MaxDateTimeValidator(q.valid_datetime_max))
elif q.type == Question.TYPE_PHONENUMBER:
babel_locale = 'en'
# Babel, and therefore django-phonenumberfield, do not support our custom locales such das de_Informal

View File

@@ -1,9 +1,10 @@
import os
from datetime import date
from django import forms
from django.utils.formats import get_format
from django.utils.functional import lazy
from django.utils.timezone import now
from django.utils.timezone import get_current_timezone, now
from django.utils.translation import gettext_lazy as _
@@ -92,7 +93,7 @@ class UploadedFileWidget(forms.ClearableFileInput):
class SplitDateTimePickerWidget(forms.SplitDateTimeWidget):
template_name = 'pretixbase/forms/widgets/splitdatetime.html'
def __init__(self, attrs=None, date_format=None, time_format=None):
def __init__(self, attrs=None, date_format=None, time_format=None, min_date=None, max_date=None):
attrs = attrs or {}
if 'placeholder' in attrs:
del attrs['placeholder']
@@ -106,6 +107,14 @@ class SplitDateTimePickerWidget(forms.SplitDateTimeWidget):
time_attrs['class'] += ' timepickerfield'
date_attrs['autocomplete'] = 'date-picker-do-not-autofill'
time_attrs['autocomplete'] = 'time-picker-do-not-autofill'
if min_date:
date_attrs['data-min'] = (
min_date if isinstance(min_date, date) else min_date.astimezone(get_current_timezone()).date()
).isoformat()
if max_date:
date_attrs['data-max'] = (
max_date if isinstance(max_date, date) else max_date.astimezone(get_current_timezone()).date()
).isoformat()
def date_placeholder():
df = date_format or get_format('DATE_INPUT_FORMATS')[0]