Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django MultiValueField - How to pass choices to ChoiceField?

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.

like image 372
Terry J Avatar asked Oct 24 '09 09:10

Terry J


4 Answers

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.

like image 139
trubliphone Avatar answered Nov 06 '22 13:11

trubliphone


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
like image 27
Yeo Avatar answered Nov 06 '22 14:11

Yeo


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.

like image 31
stefanw Avatar answered Nov 06 '22 13:11

stefanw


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)
like image 33
Ashok Avatar answered Nov 06 '22 12:11

Ashok