Search code examples
pythonvariablesdynamicdjango-formsdjango-widget

Python/Django Forms: Dynamic variable creation and access for Form fields


I have a django form class only with one widget. I would like to give a name to this widget like <select name="custom_name"></select> but Django takes the name of the variable and gives it as a name to the widget. For example:

class MultiSelectForm(forms.Form):
    here_is_the_name_of_the_widget = forms.MultipleChoiceField()

So the above code will create a widget with a name:

<select name="here_is_the_name_of_the_widget">...</select>

Because i need to prototype the form i need to create this name at initialization time. Until this moment this form works fine but with a standard pre-given name:

class MultiSelectForm(forms.Form):
    multiple_select = forms.MultipleChoiceField()

    def __init__(self, attrs=None, choices=(), *args, **kwargs):            
        self.base_fields['multiple_select'].choices = choices
        self.base_fields['multiple_select'].empty_permitted = False
        self.base_fields['multiple_select'].required = kwargs.get('required', False)
        self.base_fields['multiple_select'].widget = MultiSelect(attrs=attrs, choices=choices)

        forms.Form.__init__(self, *args, **kwargs)

So, i thought a way (a hack) to achieve what i want but isn't working as i expected. If the name i want to give to the widget is 'countries' it produces the following error:

KeyError at <project_name>
'countries'

And the code who produces that error is:

class MultiSelectForm(forms.Form):
    multiple_select = forms.MultipleChoiceField()

    def __init__(self, attrs=None, choices=(), *args, **kwargs):
        default_name = 'multiple_select'

        name = attrs.get('name', default_name)
        if name != default_name:
            setattr(self, name, forms.MultipleChoiceField())
            del attrs['name']

        '''
        The error is produced in the following line...
        '''
        self.base_fields[str(name)].choices = choices
        self.base_fields[str(name)].empty_permitted = False
        self.base_fields[str(name)].required = kwargs.get('required', False)
        self.base_fields[str(name)].widget = MultiSelect(attrs=attrs, choices=choices)

        forms.Form.__init__(self, *args, **kwargs)

Is there a way to achieve that, or something better?

Thanks!

Here is the solution...

thanks to @Daniel Roseman

class MultiSelectForm(forms.Form):
    def __init__(self, attrs=None, choices=(), *args, **kwargs):
        #empty_permitted is a Form's attribute
        kwargs['empty_permitted'] = False
        forms.Form.__init__(self, *args, **kwargs)

        name = attrs.get('name', 'multiple_select')
        self.fields[name] = forms.MultipleChoiceField(
            choices=choices,
            required=kwargs.get('required', False),
            widget=MultiSelect(attrs=attrs, choices=choices)
            )

Solution

  • I'm not sure why you need to do this at all, but you are definitely overcomplicating things. You can just add the field dynamically to the form when you instantiate it:

    class MultiSelectForm(forms.Form):
        def __init__(self, *args, **kwargs):
            dynamic_field = kwargs.pop('dynamic_field')
            super(MultiSelectForm, self).__init__(*args, **kwargs)
            self.fields[dynamic_field['name']] = forms.MultipleChoiceField(
                choices=dynamic_field['choices'], 
                empty_permitted=False
                required=dynamic_field['required'],
                widget=MultiSelect(attrs=dynamic_field['attrs']))
    

    and then pass a dictionary on instantiation:

    my_form = MultiSelectForm(
        dynamic_field={'name': 'countries', 'choices': (mychoices), 'required': True})