Skip to content

Commit c0a4862

Browse files
Jeremy NautaCarlton Gibson
authored andcommitted
Allow ChoiceField.choices to be set dynamically (#5426)
## Description The `choices` field for the `ChoiceField` class should be able to be edited after `ChoiceField.__init__` is called. ``` field = ChoiceField(choices=[1,2]) field.choices = [1] # Should no longer allow `2` as a choice ``` Currently, you must update `choices`, `grouped_choices`, and `choice_strings_to_values` to achieve this. This P/R keeps `grouped_choices` and `choice_strings_to_values` in sync whenever the `choices` are edited.
1 parent 7b1582e commit c0a4862

File tree

2 files changed

+30
-9
lines changed

2 files changed

+30
-9
lines changed

rest_framework/fields.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,18 +1337,10 @@ class ChoiceField(Field):
13371337
html_cutoff_text = _('More than {count} items...')
13381338

13391339
def __init__(self, choices, **kwargs):
1340-
self.grouped_choices = to_choices_dict(choices)
1341-
self.choices = flatten_choices_dict(self.grouped_choices)
1340+
self.choices = choices
13421341
self.html_cutoff = kwargs.pop('html_cutoff', self.html_cutoff)
13431342
self.html_cutoff_text = kwargs.pop('html_cutoff_text', self.html_cutoff_text)
13441343

1345-
# Map the string representation of choices to the underlying value.
1346-
# Allows us to deal with eg. integer choices while supporting either
1347-
# integer or string input, but still get the correct datatype out.
1348-
self.choice_strings_to_values = {
1349-
six.text_type(key): key for key in self.choices.keys()
1350-
}
1351-
13521344
self.allow_blank = kwargs.pop('allow_blank', False)
13531345

13541346
super(ChoiceField, self).__init__(**kwargs)
@@ -1377,6 +1369,22 @@ def iter_options(self):
13771369
cutoff_text=self.html_cutoff_text
13781370
)
13791371

1372+
def _get_choices(self):
1373+
return self._choices
1374+
1375+
def _set_choices(self, choices):
1376+
self.grouped_choices = to_choices_dict(choices)
1377+
self._choices = flatten_choices_dict(self.grouped_choices)
1378+
1379+
# Map the string representation of choices to the underlying value.
1380+
# Allows us to deal with eg. integer choices while supporting either
1381+
# integer or string input, but still get the correct datatype out.
1382+
self.choice_strings_to_values = {
1383+
six.text_type(key): key for key in self.choices.keys()
1384+
}
1385+
1386+
choices = property(_get_choices, _set_choices)
1387+
13801388

13811389
class MultipleChoiceField(ChoiceField):
13821390
default_error_messages = {

tests/test_fields.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,6 +1425,19 @@ def test_iter_options(self):
14251425

14261426
assert items[9].value == 'boolean'
14271427

1428+
def test_edit_choices(self):
1429+
field = serializers.ChoiceField(
1430+
allow_null=True,
1431+
choices=[
1432+
1, 2,
1433+
]
1434+
)
1435+
field.choices = [1]
1436+
assert field.run_validation(1) is 1
1437+
with pytest.raises(serializers.ValidationError) as exc_info:
1438+
field.run_validation(2)
1439+
assert exc_info.value.detail == ['"2" is not a valid choice.']
1440+
14281441

14291442
class TestChoiceFieldWithType(FieldValues):
14301443
"""

0 commit comments

Comments
 (0)