Add strict_choices to ChoiceField & MultipleChoiceField

This commit is contained in:
qu3vipon 2021-06-08 22:57:45 +09:00
parent 61e7a993bd
commit 1a2142e1f5
3 changed files with 55 additions and 0 deletions

View File

@ -404,6 +404,7 @@ Used by `ModelSerializer` to automatically generate fields if the corresponding
- `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`. - `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`.
- `html_cutoff` - If set this will be the maximum number of choices that will be displayed by a HTML select drop down. Can be used to ensure that automatically generated ChoiceFields with very large possible selections do not prevent a template from rendering. Defaults to `None`. - `html_cutoff` - If set this will be the maximum number of choices that will be displayed by a HTML select drop down. Can be used to ensure that automatically generated ChoiceFields with very large possible selections do not prevent a template from rendering. Defaults to `None`.
- `html_cutoff_text` - If set this will display a textual indicator if the maximum number of items have been cutoff in an HTML select drop down. Defaults to `"More than {count} items…"` - `html_cutoff_text` - If set this will display a textual indicator if the maximum number of items have been cutoff in an HTML select drop down. Defaults to `"More than {count} items…"`
- `strict_choices` - If set to `True` then `to_representation()` checks if output is one of choices. Defaults to `False`.
Both the `allow_blank` and `allow_null` are valid options on `ChoiceField`, although it is highly recommended that you only use one and not both. `allow_blank` should be preferred for textual choices, and `allow_null` should be preferred for numeric or other non-textual choices. Both the `allow_blank` and `allow_null` are valid options on `ChoiceField`, although it is highly recommended that you only use one and not both. `allow_blank` should be preferred for textual choices, and `allow_null` should be preferred for numeric or other non-textual choices.
@ -417,6 +418,7 @@ A field that can accept a set of zero, one or many values, chosen from a limited
- `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`. - `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`.
- `html_cutoff` - If set this will be the maximum number of choices that will be displayed by a HTML select drop down. Can be used to ensure that automatically generated ChoiceFields with very large possible selections do not prevent a template from rendering. Defaults to `None`. - `html_cutoff` - If set this will be the maximum number of choices that will be displayed by a HTML select drop down. Can be used to ensure that automatically generated ChoiceFields with very large possible selections do not prevent a template from rendering. Defaults to `None`.
- `html_cutoff_text` - If set this will display a textual indicator if the maximum number of items have been cutoff in an HTML select drop down. Defaults to `"More than {count} items…"` - `html_cutoff_text` - If set this will display a textual indicator if the maximum number of items have been cutoff in an HTML select drop down. Defaults to `"More than {count} items…"`
- `strict_choices` - If set to `True` then `to_representation()` checks if output is a set from choices. Defaults to `False`.
As with `ChoiceField`, both the `allow_blank` and `allow_null` options are valid, although it is highly recommended that you only use one and not both. `allow_blank` should be preferred for textual choices, and `allow_null` should be preferred for numeric or other non-textual choices. As with `ChoiceField`, both the `allow_blank` and `allow_null` options are valid, although it is highly recommended that you only use one and not both. `allow_blank` should be preferred for textual choices, and `allow_null` should be preferred for numeric or other non-textual choices.

View File

@ -1416,6 +1416,7 @@ class ChoiceField(Field):
self.html_cutoff_text = kwargs.pop('html_cutoff_text', self.html_cutoff_text) self.html_cutoff_text = kwargs.pop('html_cutoff_text', self.html_cutoff_text)
self.allow_blank = kwargs.pop('allow_blank', False) self.allow_blank = kwargs.pop('allow_blank', False)
self.strict_choices = kwargs.pop('strict_choices', False)
super().__init__(**kwargs) super().__init__(**kwargs)
@ -1429,6 +1430,12 @@ class ChoiceField(Field):
self.fail('invalid_choice', input=data) self.fail('invalid_choice', input=data)
def to_representation(self, value): def to_representation(self, value):
if self.strict_choices:
try:
return self.choice_strings_to_values[str(value)]
except KeyError:
self.fail('invalid_choice', input=value)
if value in ('', None): if value in ('', None):
return value return value
return self.choice_strings_to_values.get(str(value), value) return self.choice_strings_to_values.get(str(value), value)
@ -1494,6 +1501,17 @@ class MultipleChoiceField(ChoiceField):
} }
def to_representation(self, value): def to_representation(self, value):
if self.strict_choices:
if isinstance(value, str) or not hasattr(value, '__iter__'):
self.fail('not_a_list', input_type=type(value).__name__)
if not self.allow_empty and len(value) == 0:
self.fail('empty')
return {
super(MultipleChoiceField, self).to_representation(item)
for item in value
}
return { return {
self.choice_strings_to_values.get(str(item), item) for item in value self.choice_strings_to_values.get(str(item), item) for item in value
} }

View File

@ -1693,6 +1693,23 @@ class TestChoiceField(FieldValues):
field.run_validation(2) field.run_validation(2)
assert exc_info.value.detail == ['"2" is not a valid choice.'] assert exc_info.value.detail == ['"2" is not a valid choice.']
def test_strict_choices(self):
"""
If `strict_choices` then to_representation() should return one of given choices.
"""
field = serializers.ChoiceField(
strict_choices=True,
choices=[
('poor', 'Poor quality'),
('medium', 'Medium quality'),
('good', 'Good quality'),
]
)
assert field.to_representation('poor') == 'poor'
with pytest.raises(serializers.ValidationError) as exc_info:
field.to_representation('amazing')
assert exc_info.value.detail == ['"amazing" is not a valid choice.']
class TestChoiceFieldWithType(FieldValues): class TestChoiceFieldWithType(FieldValues):
""" """
@ -1830,6 +1847,24 @@ class TestMultipleChoiceField(FieldValues):
field.partial = True field.partial = True
assert field.get_value(QueryDict({})) == rest_framework.fields.empty assert field.get_value(QueryDict({})) == rest_framework.fields.empty
def test_multiple_strict_choices(self):
"""
If `strict_choices` then to_representation() should return a set from given choices.
"""
field = serializers.MultipleChoiceField(
strict_choices=True,
choices=[
('aircon', 'AirCon'),
('manual', 'Manual drive'),
('diesel', 'Diesel'),
]
)
assert field.to_representation(['aircon', 'manual']) == {'aircon', 'manual'}
with pytest.raises(serializers.ValidationError) as exc_info:
field.to_representation(['aircon', 'incorrect'])
assert exc_info.value.detail == ['"incorrect" is not a valid choice.']
class TestEmptyMultipleChoiceField(FieldValues): class TestEmptyMultipleChoiceField(FieldValues):
""" """