13

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:

enter image description here

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.

enter image description here

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 :)

Community
  • 1
  • 1
Houman
  • 64,245
  • 87
  • 278
  • 460
  • 1
    "The data is not completely lost. Rather the select is set wrongly to the initial dummy value." - that is b/c that field isnt getting posted. so the default value is used – Francis Yaconiello Jul 31 '12 at 21:30

3 Answers3

18

Its not a django thing, its an HTML thing. Disabled form elements are not sent by the form.

[The Element] cannot receive user input nor will its value be submitted with the form.

http://www.w3.org/TR/html401/interact/forms.html#h-17.12.1 & http://www.w3schools.com/tags/att_input_disabled.asp

you could use readonly if its on a text/textarea http://www.w3schools.com/tags/att_input_readonly.asp

something else you could do, is show the value plaintext, and submit it as a hidden field....

{{ form.field_name.label_tag }}
{{ form.field_name.value }}
<input type="hidden" name="field_name" value="{{form.field_name.value}}" />

its not very elegant, but it could get you there.

You could also take it a step further and write some JS that looks for disabled elements and adds an input with that element's name and value after.

some sample JQuery:

//Untested, but you get the gist
$(':disabled').each(
    function()
    {
        $(this).after('<input type="hidden" name="' + $(this).attr('name') + '" value="' + $(this).val() + '" />');
    }
);
Francis Yaconiello
  • 10,829
  • 2
  • 35
  • 54
  • Thanks Francis. Great solution. Please see my updated question with some details. Thank you – Houman Jul 31 '12 at 22:44
1

Well, you could set the element with hidden property in the template, using formsets in the view to build the form:

{{form.field.as_hidden}}

and inside the view, if the problem is the data loss, you could always set an initial value for the field that suits your model structure, since it's a foreign key. Of course, you will have to validate the form before commiting it, and if the form is not valid, you can render it with initial values on the fields that must be always filled.

  • I like the idea. I didn't understand the hidden part, because I am clearly loosing the default selection. Hence I need to send it somehow back to the view. I managed to do that with Francis's suggestions of an extra hidden input field. And from there I was thinking about initials on formsets actually. :) Please have a look at my updated question. Thanks :) – Houman Jul 31 '12 at 22:52
  • 1
    Excelent! I will keep this question in my favorites, because I could deal with some situation like that in the future! –  Aug 01 '12 at 14:49
0

I think this is a HTML issue rather than Django, disabled form fields don't post their values back so you're losing the value.

Would it be possible to rebind the value to the field if validation fails? You could try something like

if form.is_valid(): # All validation rules pass
    #save form, redirect, etc.
else:
    form.disabled_field = my_value
return render(request, 'contact.html', {'form': form,})

Obviously you'll need to replace the field name and value with the correct data from your model.

Roarster
  • 804
  • 1
  • 8
  • 13