Well in some cases I want to be able to set dynamically the possible choices of a choice field. In 2.X it was enough to add something like this in the serializer init method:
self.fields['dynamic_field'].choices = new_list_of_choices
However this is no longer the case, as there are other attributes that depend on self.choices and are set upon fields initialisation e.g. choice_strings_to_values. As a quick fix I have come up with this - https://gist.github.com/IlianIliev/3941c1b031406bdf6657
I know it lacks tests etc. but I would like to hear what is the global vision with adding such a method as part of the core functionality.
Any reason you couldn't instead add a new instance of the field?
I thought about this option too but that means to repeat all attributes that I are previously set to the field which is against the DRY principle.
I just wanted to have more Pythonic way to do it.
I know this is old and closed, but I recently hit this as a bit of a stumbling block. I expected to be able to set .choices in the __init__ of my serializer, much in the same way you can set .queryset for related fields and .choices in a django forms.ChoiceField. To get this working this my code now ends up like this:
user_field = self.fields['user']
user_choices = list(map(lambda u: (str(u.id), u.email), users))
user_field.choices = dict(user_choices)
user_field.grouped_choices = user_field.choices
user_field.choice_strings_to_values = {
str(key): key for key in user_field.choices.keys()
}
Which breaks DRY and is reliant in internal implementation. I think it'd be pretty simple to modify ChoiceField to work as I expected without breaking the existing API. If there's interest I'll make a pull request.
I encountered this problem too.
I think, that ChoiceField.choices attribute works not as expected.
Behavior is nowhere described in the documentation and behavior is different from django's form ChoiceField
I had to watch the source code to understand what to do.
When using django's form ChoiceField you have two options:
1. Set choices dynamically in form's constructor
2. Set choices to callable
None of this is working with serializer's ChoiceField
In django choice is the property.
May be implement choice attribute in serializer as property?
Something like this
def _set_choices(self, choices):
self._choices = choices
self.grouped_choices = to_choices_dict(self._choices)
self.choice_strings_to_values = {
six.text_type(key): key for key in flatten_choices_dict(self.grouped_choices).keys()
}
def _get_choices(self, choices):
return self._choices
choices = property(_get_choices, _set_choices)
Also I think need to add feature when choices is callable
If you think this is important enough you can make it a 3rd party. I can't promise it'll be part of core but that would be a good starting point to showcase the solution.
OK, I'll try. I think, at least need to write in the documentation that simple assignment to choices attribute (self.fields['choice_field'].choices = dynamic_choices) is not working as expected and provide working solution.
Can you take a look at this commit? I don't insist to include it in the core, just take a look.
@sinitsynsv It's perfectly reasonable, but it does add extra indirection. Rather than allowing ChoiceField choices to be modified I'd suggest instantiating a new instance dynamically when this sort of case is required.
OK, I understand. But my point is that it works not as it works in django. That may confuse people, who use django, but didn't use django-rest-framework. For now I'm just think that this need to reflect in documentation.
@sinitsynsv a beautiful solution. Small modification
def _set_choices(self, choices):
self._choices = choices
self.grouped_choices = to_choices_dict(self._choices)
self.choice_strings_to_values = {
six.text_type(key): key for key in flatten_choices_dict(self.grouped_choices).keys()
}
def _get_choices(self):
return self._choices
choices = property(_get_choices, _set_choices)
_get_choices should only take self. In my project, I've extended the ChoiceField and MultipleChoiceField like this
class ChoiceField(rest_serializers.ChoiceField):
"""Make choices a property, so it can be modified reliably.
"""
_choices = dict()
def _set_choices(self, choices):
self.grouped_choices = to_choices_dict(self._choices)
self._choices = flatten_choices_dict(choices)
self.choice_strings_to_values = {
six.text_type(key): key for key in
self._choices.keys()
}
def _get_choices(self):
return self._choices
choices = property(_get_choices, _set_choices)
class MultipleChoiceField(rest_serializers.MultipleChoiceField):
"""Make choices a property, so it can be modified reliably.
"""
_choices = dict()
def _set_choices(self, choices):
self.grouped_choices = to_choices_dict(self._choices)
self._choices = flatten_choices_dict(choices)
self.choice_strings_to_values = {
six.text_type(key): key for key in
self._choices.keys()
}
def _get_choices(self):
return self._choices
choices = property(_get_choices, _set_choices)
and am using these for my serializers. Works like a charm
Most helpful comment
I encountered this problem too.
I think, that
ChoiceField.choicesattribute works not as expected.Behavior is nowhere described in the documentation and behavior is different from django's form
ChoiceFieldI had to watch the source code to understand what to do.
When using django's form ChoiceField you have two options:
1. Set
choicesdynamically in form's constructor2. Set
choicesto callableNone of this is working with serializer's
ChoiceFieldIn django
choiceis the property.May be implement
choiceattribute in serializer as property?Something like this
Also I think need to add feature when
choicesis callable