Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django forms: making a disabled field persist between validations

At some point I need to display a "disabled" (greyed out by disabled="disabled" attribute) input of type "select". As specified in the standard (xhtml and html4), inputs of type "select" can not have the "readonly" attribute. Note that this is for presentation purposes only, the actual value must end up in the POST. So here is what I do (quoting a part of the form declaration in django):

from django import forms

_choices = ['to be', 'not to be']
class SomeForm(forms.Form):
    field = forms.ChoiceField(choices=[(item, item) for item in _choices],
                   widget=forms.HiddenInput()) # the real field

    mock_field = forms.ChoiceField(required=False, # doesn't get submitted
                        choices=[(item, item) for item in _choices],
                        label="The question",
                        widget=forms.Select(attrs={'disabled':'disabled'}))

Then it is initialized like this:

initial_val = 'to be'
form = SomeForm(ititial={'field':initial_val,
                         'mock_field':initial_val})

And all is well. Well, until the form gets validated and one of the other fields fails the validation. When this happens, the form is reloaded and the values are preserved, but not the one of the "mock_field" - it never got submitted (it is disabled). So it is not preserved. While this doesn't affect the data integrity, it is still not so good presentation-wise.

Is there any way to preserve that field, with as little hackery as possible? The form is a part of a django.contrib.formtools.FormWizard and the initial values (and some fields) are generated dynamically. Basically, there is a lot of stuff going on already, it'd be great if it was possible not to overcomplicate things.

like image 923
shylent Avatar asked Oct 20 '09 17:10

shylent


2 Answers

Browsers don't POST disabled fields.

You can try to copy fields initial value to mock_field in your Form's __init__

def __init__(self, *args, **kwargs):
    super(SomeForm, self).__init__(*args, **kwargs)
    mock_initial = self.fields['field'].initial
    self.fields['mock_field'].initial = mock_initial

Code is not tested. Normally you would be concerned about form.data as well, but in this case it won't be different than initial

like image 174
muhuk Avatar answered Sep 19 '22 11:09

muhuk


Well, this will be the first time I answer my question, but I've found a solution and (while it cerainly is a hack) it works.

Instead of getting the initial value from the form instance, - self.fields['whatever'].initial seems to be None inside the constructor, I am getting the value from keyword argument "initial". And then I set it as the only choice for the "mock" field. Like this:

from django import forms

_choices = ['to be', 'not to be']
class SomeForm(forms.Form):
    field = forms.ChoiceField(choices=[(item, item) for item in _choices],
                   widget=forms.HiddenInput()) # the real field

    mock_field = forms.ChoiceField(required=False, # doesn't get submitted
                        choices=[(item, item) for item in _choices],
                        label="The question",
                        widget=forms.Select(attrs={'disabled':'disabled'}))

    def __init__(self, *args, **kwargs):
        super(SomeForm, self).__init__(*args, **kwargs)
        mock_initial = kwargs['initial']['field']
        self.fields['mock_field'].choices = [(mock_initial, mock_initial),]

This probably needs some error handling. Obviously, this will not work if the initial value is not provided for the actual field.

like image 32
shylent Avatar answered Sep 19 '22 11:09

shylent