0
votes

I have a form that utilizes choices argument from which I've created a formset. When the page containing the formset is rendered, the fields that uses choices argument display drop-down select widgets. The forms that are filled by the user have no errors. However, the forms that are NOT filled by the user have 'This field is required' errors for all other fields but the fields that used the select widget.

It appears that the select field's initial values is causing the form to be treated as half-filled form and thus the form validation process throws errors for the required fields that are not filled.

# Form:
class OwnerForm(forms.Form):
    name = forms.CharField(label = 'Name', max_length = 20)
    owner_entity = forms.ChoiceField(label = 'Owner Entity', choices = OWNER_ENTITIES)
    num_of_shares = forms.DecimalField(label = 'Number of Shares' , min_value = 0.0, max_digits = 5, decimal_places = 2)
    share_class = forms.ChoiceField(label = 'Share Class', choices = SHARE_CLASSES)
    joined_date = forms.DateField(label = 'Joined Date', help_text = 'mm/dd/yyyy')

# View:
#    In Get method:
OwnersFormSet = formset_factory(OwnerForm, extra = 5)
...

#    In Post method:
the_owners_forms = OwnerFormSet(request.POST)

if not the_owners_forms.is_valid():
    the_owners_forms_errors = the_owners_forms.errors

So, the question is how do I deal with this behavior so that the non-filled forms are not taken to be as hal-filled forms because of the initial value of the select method?

3
I did try that prior to posting the question, but it did not work. I added a (None, '------------') to the OWNER_ENTITIES, but it skips this and picks the next element in the tuple. What I am thinking is to manually delete those initial values when I receive the forms and run is_valid after the manual modification. - EarlyCoder
If you want to work with javascript I have a more elegant solution for your problem - trantu
Your solution is more than welcome! I would also like to know how to tackle this using Django. - EarlyCoder

3 Answers

0
votes

How about this in your forms.py?

By default, your Select fields would have '------' as a value, and it would be considered as invalid when the form is processed.

EMPTY_CHOICE = ((None, '-------------),)

class OwnerForm(forms.Form):
    ...
    owner_entity = forms.ChoiceField(label = 'Owner Entity', choices = EMPTY_CHOICE + OWNER_ENTITIES)
    ...
    share_class = forms.ChoiceField(label = 'Share Class', choices = EMPTY_CHOICE + SHARE_CLASSES)
0
votes

So, this solution is in combination with javascript. So, you should not use extra=5 to have more forms. You can use a "Add more" button if you like to add one more form:

On template.html:

{{ the_owners_forms.management_form }}
{% for form in the_owners_forms.forms %}
    <div class='table'>
    <table class='no_error'>
        {{ form.as_table }}
    </table>
    </div>
{% endfor %}
<input type="button" value="Add More" id="add_more">
<script>
    $('#add_more').click(function() {
        cloneMore('div.table:last', 'form');
    });
function cloneMore(selector, type) {
    var newElement = $(selector).clone(true);
    var total = $('#id_' + type + '-TOTAL_FORMS').val();
    newElement.find(':input').each(function() {
        var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
        var id = 'id_' + name;
        $(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
    });
    newElement.find('label').each(function() {
        var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
        $(this).attr('for', newFor);
    });
    total++;
    $('#id_' + type + '-TOTAL_FORMS').val(total);
    $(selector).after(newElement);
}
</script>

Let try...

0
votes

The way I solved this is a bit of a work-around. But, it works.

Here is the problem: It appears that the select field's initial values is causing the form to be treated as half-filled form and thus the form validation process throws errors for the required fields that are not filled.

Here is the solution: Just duplicate all the data you receive from the forms that are really filled and not the forms with fields that have choices, and not really filled by the user. Then, reconstruct the ManagementForm:

formset_data = {'form-TOTAL_FORMS': total_forms,
                'form-INITIAL_FORMS': initial_forms ,
                'form-MAX_NUM_FORMS': max_num_forms,
               }

# Selectively get the data from the formset of the get request and build 
# correct data into the formset_data by updating it:
for form in formset:
    # formset_data.update(<the correct data>)

OwnersFormsetCopy = formset_factory(form = OwnerForm, extra = extra_forms )        
owners_formset_copy = OwnersFormsetCopy(formset_data)

# Then, run is_valid() on the new formset to take advantage of 
# Django's form validation utility