I have a multivaluefield with a charfield and choicefield. I need to pass choices to the choicefield constructor, however when I try to pass it into my custom multivaluefield I get an error __init__()
got an unexpected keyword argument 'choices'.
I know the rest of the code works because when I remove the choices keyword argument from __init__
and super, the multivaluefield displays correctly but without any choices.
This is how I setup my custom multivaluefield:
class InputAndChoice(object):
def __init__(self, text_val='', choice_val=''):
self.text_val=text_val
self.choice_val=choice_val
class InputAndChoiceWidget(widgets.MultiWidget):
def __init__(self, attrs=None):
widget = (widgets.TextInput(),
widgets.Select()
)
super(InputAndChoiceWidget, self).__init__(widget, attrs=attrs)
def decompress(self,value):
if value:
return [value.text_val, value.choice_val]
return [None, None]
class InputAndChoiceField(forms.MultiValueField):
widget = InputAndChoiceWidget
def __init__(self, required=True, widget=None, label=None, initial=None,
help_text=None, choices=None):
field = (
fields.CharField(),
fields.ChoiceField(choices=choices),
)
super(InputAndChoiceField, self).__init__(fields=field, widget=widget,
label=label, initial=initial, help_text=help_text, choices=choices)
And I call it like so:
input_and_choice = InputAndChoiceField(choices=[(1,'first'),(2,'second')])
So how do I pass the choices to my ChoiceField field?
Edit:
I've tried stefanw's suggestion but still no luck. I've used logging.debug to print out the contents of InputAndChoiceField at the end of the init and self.fields[1].choices contains the correct values as per above however it doesnt display any choices in the browser.
I ran into this exact same problem and solved it like this:
class InputAndChoiceWidget(widgets.MultiWidget):
def __init__(self,*args,**kwargs):
myChoices = kwargs.pop("choices")
widgets = (
widgets.TextInput(),
widgets.Select(choices=myChoices)
)
super(InputAndChoiceWidget, self).__init__(widgets,*args,**kwargs)
class InputAndChoiceField(forms.MultiValueField):
widget = InputAndChoiceWidget
def __init__(self,*args,**kwargs):
# you could also use some fn to return the choices;
# the point is, they get set dynamically
myChoices = kwargs.pop("choices",[("default","default choice")])
fields = (
fields.CharField(),
fields.ChoiceField(choices=myChoices),
)
super(InputAndChoiceField,self).__init__(fields,*args,**kwargs)
# here's where the choices get set:
self.widget = InputAndChoiceWidget(choices=myChoices)
Add a "choices" kwarg to the widget's constructor. Then explicitly call the constructor after the field is created.
Have a look at the source of __init__
of forms.MultiValueField
:
def __init__(self, fields=(), *args, **kwargs):
super(MultiValueField, self).__init__(*args, **kwargs)
# Set 'required' to False on the individual fields, because the
# required validation will be handled by MultiValueField, not by those
# individual fields.
for f in fields:
f.required = False
self.fields = fields
So I would overwrite the __init__
probably like this:
def __init__(self, *args, **kwargs):
choices = kwargs.pop("choices",[])
super(InputAndChoiceField, self).__init__(*args, **kwargs)
self.fields = (
fields.CharField(),
fields.ChoiceField(choices=choices),
)
You might even want to do super(MultiValueField, self).__init__(*args, **kwargs)
instead of super(InputAndChoiceField, self).__init__(*args, **kwargs)
because you are setting the fields yourself instead of getting them via parameters.
passing the choices in the widget solved this for me
class InputAndChoiceWidget(widgets.MultiWidget):
def __init__(self, attrs=None):
choices = [('a', 1), ('b', 2)]
widget = (widgets.TextInput(),
widgets.Select(choices=choices)
)
super(InputAndChoiceWidget, self).__init__(widget, attrs=attrs)
ModelChoiceField
is technically a ChoiceField
, but it doesn't actually use any of the ChoiceField
's implementations. So, here's how I use it.
class ChoiceInputMultiWidget(MultiWidget):
"""Kindly provide the choices dynamically"""
def __init__(self, attrs=None):
_widget = (
Select(attrs=attrs),
TextInput(attrs=attrs)
)
super().__init__(_widget, attrs)
class ModelChoiceInputField(MultiValueField):
widget = ChoiceInputMultiWidget
def __init__(self, *args, **kwargs):
_fields = (
ModelChoiceField(queryset=Type.objects.all()),
CharField()
)
super().__init__(_fields, *args, **kwargs)
# Use the auto-generated widget.choices by the ModelChoiceField
self.widget.widgets[0].choices = self.fields[0].widget.choices
class HTML5DateInput(DateInput):
input_type = 'date'
class CustomSelectRangeWidget(forms.MultiWidget):
def __init__(self, attrs=None, choices = ()):
widgets = (Select(attrs=attrs, choices=choices), HTML5DateInput(attrs=attrs), HTML5DateInput(attrs=attrs))
super(CustomSelectRangeWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return [value.field, value.start, value.stop]
return [None, None, None]
def format_output(self, rendered_widgets):
return '-'.join(rendered_widgets)
class CustomSelectRangeField(forms.MultiValueField):
widget = CustomSelectRangeWidget
def __init__(self, *args, **kwargs):
if kwargs.has_key('choices') :
choices = kwargs.pop('choices')
else:
choices = ()
fields = (
forms.ChoiceField(choices=choices), #field with choices,
# so that clean can be passed
forms.DateField(),
forms.DateField(),
)
super(CustomSelectRangeField, self).__init__(fields=fields, *args, **kwargs)
#initialize widget with choices.
self.widget = CustomSelectRangeWidget(choices=choices)
def compress(self, data_list):
if data_list:
#check if datalist has 3 not null values
if len([v for v in data_list if v not in [None, '']]) == 3:
out_dict = {'field':data_list[0], 'start':data_list[1], 'stop':data_list[2]}
return out_dict
return None
来源:https://stackoverflow.com/questions/1617454/django-multivaluefield-how-to-pass-choices-to-choicefield