I have a dropdown in a modelform and the user should not be able to change the selected value.
I found that a disabled
does exactly do what I need. However there is an oddness to this:
The first time when the form opens (GET) the value is selected and the user can't change the value. which is great:
But as soon as there is a validation error with an unrelated field
and the POST sends the user back to the same form, the previous information is lost. The disabled foreignkey-dropdown no longer contains any value and is very irritating.
I did some research and found something on stackoverflow and seems when a foreignkey-dropdown widget is disabled, no data is sent back at all. While the validation can be overriden to not throw any errors for the dropdown field as the third answer here explains. However if ANY OTHER unrelated field throws an error then the data is lost, because the disabled dropdown had never sent any data to POST in first place.
It is a tricky situation.
Is there a way to pass in the data within the view to the request.POST ? or what do you suggest? I could use a readonly
instead ofdisabled
and that would work, however the dropdown can be changed by the user, which is also irritating.
Any ideas? Many Thanks
edit:
Small correction: The data is not completely lost. Rather the select is set wrongly to the initial dummy value.
<select id="id_form-0-deal_type" name="form-0-deal_type" disabled="disabled">
<option selected="selected" value="">---------</option>
<option value="1">deal 1</option>
<option value="2">deal 2</option>
</select>
UPDATE:
The solution from Francis looks very promising. So I have tried his second suggestion and added a hidden inputfield in the html and pass in the correct value into the POST.
The problem is now how to proceed. I have tried to add the missing entry in the formset's form's querydict like this (in order to set the correct dropdown value)
formset.forms[0].data['form-0-deal_type'] = formset.forms[0].data['form-0-hiddenfield']
But it says This QueryDict instance is immutable
The only other way to do it is setting it through Initials with regular formsets. Unfortunally I am using modelformsets, which doesn't support initials for existing forms.
If there is no other solution, I start refactoring my modelformset into a regular formset. Still open for ideas...
Final Update + Solution:
There is no need to refactor modelformset into regular fomsets. In fact I highly discourage doing that, since it brings other problems with itself. modelformsets handle everything for you and fill the missing parts.
The actual problem is the fact that QueryDict are immutable, but this can be easily solved by copying them:
formset = deal_formset(request.POST, queryset=formset_query)
if formset.is_valid():
pass
else:
new_post = request.POST.copy()
deal_types = dict()
for k,v in new_post.items():
if k.startswith('hidden'):
deal_types[k[7:]]= v
for k,v in deal_types.iteritems():
new_post[k] = v
formset = deal_formset(new_post, queryset=formset_query)
This plus the solution of Francis:
{{ formset.management_form }}
{% for fs in formset %}
{{ fs.id }}
<input type="hidden" name="hidden-{{ fs.prefix }}-deal_type" value="{{fs.deal_type.value}}" />
{{fs.deal_type}}
{% endfor %}
{% endif %}
just works wonders... enjoy :)