Skip to content

Commit

Permalink
Merge pull request #29 from eldarion/fix/validate-multiple-choice-limit
Browse files Browse the repository at this point in the history
Validate maximum_choices on CHECKBOX_FIELD
  • Loading branch information
jacobwegner committed Sep 26, 2017
2 parents ef6901e + eadd95a commit 89e24a2
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 1 deletion.
25 changes: 25 additions & 0 deletions formly/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _


class LimitedMultipleChoiceField(forms.MultipleChoiceField):
def __init__(self, *args, **kwargs):
self.maximum_choices = kwargs.pop("maximum_choices")

self.default_error_messages.update({
'maximum_choices': _('You may select at most %(maximum)d choices (%(selected)d selected)')
})

super(LimitedMultipleChoiceField, self).__init__(*args, **kwargs)

def validate(self, value):
super(LimitedMultipleChoiceField, self).validate(value)

selected_count = len(value)
if self.maximum_choices and selected_count > self.maximum_choices:
raise ValidationError(
self.error_messages['maximum_choices'],
code='maximum_choices',
params={'maximum': self.maximum_choices, 'selected': selected_count},
)
5 changes: 4 additions & 1 deletion formly/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from jsonfield import JSONField

from .fields import LimitedMultipleChoiceField
from .forms import MultipleTextField, MultiTextWidget
from .forms.widgets import LikertSelect, RatingSelect

Expand Down Expand Up @@ -361,6 +362,8 @@ def _get_field_class(self, choices):

if self.field_type in [Field.CHECKBOX_FIELD, Field.SELECT_FIELD, Field.RADIO_CHOICES, Field.LIKERT_FIELD, Field.RATING_FIELD]:
kwargs.update({"choices": choices})
if self.field_type == Field.CHECKBOX_FIELD:
kwargs.update({"maximum_choices": self.maximum_choices})
elif self.field_type == Field.MULTIPLE_TEXT:
kwargs.update({
"fields_length": self.expected_answers,
Expand Down Expand Up @@ -405,7 +408,7 @@ def _get_field_class(self, choices):
)
),
Field.CHECKBOX_FIELD: dict(
field_class=forms.MultipleChoiceField,
field_class=LimitedMultipleChoiceField,
kwargs=dict(
widget=forms.CheckboxSelectMultiple()
)
Expand Down
27 changes: 27 additions & 0 deletions formly/tests/tests.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from django.core.exceptions import ValidationError
from django.contrib.auth import get_user_model
from django.test import TestCase

from ..models import (
Field,
Survey,
FieldChoice,
)

User = get_user_model()
Expand Down Expand Up @@ -31,3 +33,28 @@ def test_likert_field_missing_scale(self):
self.assertFalse(field.choices.all())
# Ensure no exception when field has no choices
self.assertTrue(field.form_field())

def test_multiplechoice_field_choice_limit(self):
"""
Enforce maximum_choices on multiple choice fields that allow
multiple answers.
"""
survey = Survey.objects.create(
name="multiple choice test",
creator=self.user,
)
field = Field.objects.create(
survey=survey,
label="multiple choice field",
field_type=Field.CHECKBOX_FIELD,
maximum_choices=1,
ordinal=0,
)
choice_pks = []
for label in ["a", "b"]:
choice = FieldChoice.objects.create(label=label, field=field)
choice_pks.append(choice.pk)

form_field = field.form_field()
with self.assertRaises(ValidationError):
form_field.clean(choice_pks)

0 comments on commit 89e24a2

Please sign in to comment.