Ticket #12398: 12398-TypedMultipleChoiceField-r14586.diff

File 12398-TypedMultipleChoiceField-r14586.diff, 7.1 KB (added by anonymous, 4 years ago)
  • django/forms/fields.py

     
    4040    'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
    4141    'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
    4242    'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
    43     'TypedChoiceField'
     43    'TypedChoiceField', 'TypedMultipleChoiceField'
    4444)
    4545
    4646def en_format(name):
     
    691691
    692692    def to_python(self, value):
    693693        """
    694         Validate that the value is in self.choices and can be coerced to the
     694        Validates that the value is in self.choices and can be coerced to the
    695695        right type.
    696696        """
    697697        value = super(TypedChoiceField, self).to_python(value)
     
    733733            if not self.valid_value(val):
    734734                raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
    735735
     736class TypedMultipleChoiceField(MultipleChoiceField):
     737    def __init__(self, *args, **kwargs):
     738        self.coerce = kwargs.pop('coerce', lambda val: val)
     739        self.empty_value = kwargs.pop('empty_value', [])
     740        super(TypedMultipleChoiceField, self).__init__(*args, **kwargs)
     741
     742    def to_python(self, value):
     743        """
     744        Validates that the values are in self.choices and can be coerced to the
     745        right type.
     746        """
     747        value = super(TypedMultipleChoiceField, self).to_python(value)
     748        super(TypedMultipleChoiceField, self).validate(value)
     749        if value == self.empty_value or value in validators.EMPTY_VALUES:
     750            return self.empty_value
     751        new_value = []
     752        for choice in value:
     753            try:
     754                new_value.append(self.coerce(choice))
     755            except (ValueError, TypeError, ValidationError):
     756                raise ValidationError(self.error_messages['invalid_choice'] % {'value': choice})
     757        return new_value
     758
     759    def validate(self, value):
     760        pass
     761
    736762class ComboField(Field):
    737763    """
    738764    A Field whose clean() method calls multiple Field clean() methods.
  • tests/regressiontests/forms/tests/fields.py

     
    749749        self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 6 is not one of the available choices.']", f.clean, ['6'])
    750750        self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 6 is not one of the available choices.']", f.clean, ['1','6'])
    751751
    752     # ComboField ##################################################################
     752    # TypedMultipleChoiceField ############################################################
     753    # TypedMultipleChoiceField is just like MultipleChoiceField, except that coerced types
     754    # will be returned:
    753755
     756    def test_typedmultiplechoicefield_1(self):
     757        f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int)
     758        self.assertEqual([1], f.clean(['1']))
     759        self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 2 is not one of the available choices.']", f.clean, ['2'])
     760
     761    def test_typedmultiplechoicefield_2(self):
     762        # Different coercion, same validation.
     763        f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=float)
     764        self.assertEqual([1.0], f.clean(['1']))
     765
     766    def test_typedmultiplechoicefield_3(self):
     767        # This can also cause weirdness: be careful (bool(-1) == True, remember)
     768        f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=bool)
     769        self.assertEqual([True], f.clean(['-1']))
     770
     771    def test_typedmultiplechoicefield_4(self):
     772        # Even more weirdness: if you have a valid choice but your coercion function
     773        # can't coerce, you'll still get a validation error. Don't do this!
     774        f = TypedMultipleChoiceField(choices=[('A', 'A'), ('B', 'B')], coerce=int)
     775        self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. B is not one of the available choices.']", f.clean, ['B'])
     776        # Required fields require values
     777        self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, [])
     778
     779    def test_typedmultiplechoicefield_5(self):
     780        # Non-required fields aren't required
     781        f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False)
     782        self.assertEqual([], f.clean([]))
     783
     784    def test_typedmultiplechoicefield_6(self):
     785        # If you want cleaning an empty value to return a different type, tell the field
     786        f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False, empty_value=None)
     787        self.assertEqual(None, f.clean([]))
     788
     789   # ComboField ##################################################################
     790
    754791    def test_combofield_1(self):
    755792        f = ComboField(fields=[CharField(max_length=20), EmailField()])
    756793        self.assertEqual(u'test@example.com', f.clean('test@example.com'))
  • docs/ref/forms/fields.txt

     
    361361
    362362.. class:: TypedChoiceField(**kwargs)
    363363
    364 Just like a :class:`ChoiceField`, except :class:`TypedChoiceField` takes an
    365 extra ``coerce`` argument.
     364Just like a :class:`ChoiceField`, except :class:`TypedChoiceField` takes two
     365extra arguments, ``coerce`` and ``empty_value``.
    366366
    367367    * Default widget: ``Select``
    368368    * Empty value: Whatever you've given as ``empty_value``
    369     * Normalizes to: the value returned by the ``coerce`` argument.
    370     * Validates that the given value exists in the list of choices.
     369    * Normalizes to: The value returned by the ``coerce`` argument.
     370    * Validates that the given value exists in the list of choices and can be
     371      coerced.
    371372    * Error message keys: ``required``, ``invalid_choice``
    372373
    373374Takes extra arguments:
     
    635636      of choices.
    636637    * Error message keys: ``required``, ``invalid_choice``, ``invalid_list``
    637638
    638 Takes one extra argument, ``choices``, as for ``ChoiceField``.
     639Takes one extra required argument, ``choices``, as for ``ChoiceField``.
    639640
     641``TypedMultipleChoiceField``
     642~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     643
     644.. class:: TypedMultipleChoiceField(**kwargs)
     645
     646Just like a :class:`MultipleChoiceField`, except :class:`TypedMultipleChoiceField`
     647takes two extra arguments, ``coerce`` and ``empty_value``.
     648
     649    * Default widget: ``SelectMultiple``
     650    * Empty value: Whatever you've given as ``empty_value``
     651    * Normalizes to: A list of values, each returned by the ``coerce`` argument.
     652    * Validates that the given values exists in the list of choices and can be
     653      coerced.
     654    * Error message keys: ``required``, ``invalid_choice``
     655
     656Takes two extra arguments, ``coerce`` and ``empty_value``, as for ``TypedChoiceField``.
     657
    640658``NullBooleanField``
    641659~~~~~~~~~~~~~~~~~~~~
    642660
Back to Top