views:

101

answers:

0

As title says, how can I override it - specifically, how to change its widget from default TextInput to HiddenInput? And why isn't it hidden to begin with :S

I tried passing custom callback function as formfield callback to modelformset_factory but it only changed the default fields originating from model. I also tried specifying another widget by defining ORDER field in the form itself (the form that I give to modelformset_factory because it contain one additional field which gets added as expected) and to specify widgets dict in forms Meta class, but still no go - ORDER field gets rendered as visible TextInput:

<th><label for="id_form-0-ORDER">Order:</label></th><td><input name="form-0-ORDER" value="1" id="id_form-0-ORDER" type="text">

The reason why I (think) I need that order field is to change it dynamically with JS when I'm adding and removing random forms to/from the formset. I keep form-TOTAL_FORMS updated using JS too. When I submit such tinkered with formset it sometimes might have only one form with prefix form-1 or form-2 but not form-0. As I understand it, Django sees that there should only be one form (because of form-TOTAL_FORMS) and generates it looking for form-0 values in request.POST, so the actual submited data doesn't get used.

Am I doing something wrong or is there a better way to accomplish this? I still didn't check if ordering forms (setting form-1 ORDER value to 1 after form-0 with ORDER=1 has been removed from html) will solve the problem.

EDIT: Didn't know if answering the question myself was an accepted thing to do, so I'm doing this edit.

The answer - it is briefly mentioned in Django docs http://docs.djangoproject.com/en/1.2/topics/forms/formsets/#adding-additional-fields-to-a-formset, but somehow I missed it. So to override the default ORDER field which gets added to forms when using formsets with 'can_order', you have to override add_fields() method in Base(Model)FormSet subclass and change ORDER field to whatever you want. Here is the code I used:

class BaseAuthorFormSet(BaseModelFormSet):
    # Overriden add_fields method which makes ORDER field hidden in forms.
    def add_fields(self, form, index):
        super(BaseAuthorFormSet, self).add_fields(form, index)
        form.fields['ORDER'] = forms.IntegerField(required=False,
            widget=forms.HiddenInput, initial=form.fields['ORDER'].initial)

However, this didn't solve my problem - that is to be able to remove random forms from HTML/DOM using JS and still have correctly working formset. So I ended up altering HTML even more - when the form which isn't the last one in the formset is removed, I change the numbers in the last form to those used in the removed one. By using this method, on submit all forms in the formset will be numbered from 0 to TOTAL_FORMS (the number of existing forms) and Django will create/save model instances correctly. JS function I used ('new_number' - the number of the removed form, 'elements' - DOM elements of all current forms in the formset):

function orderForms(new_number, elements)
{
    var re1, re2, last, old_number, inputs, backup = [];
    if (typeof elements != "undefined" && elements.length > 0) {
        last = elements[elements.length - 1];
        old_number = parseInt(last.id.match(/\d+$/ig));

        // If removed author form was not the last,
        // then switch the numbers used in the attributes. 
        if (old_number > new_number) {
            re1 = /("[a-zA-Z_]+)\d+(")/ig;
            re2 = /(form-)\d+/ig;

            inputs = last.getElementsByTagName("input");

            // Save current values of form's input fields.
            for (i = 0; i < inputs.length; i++) {
                if (inputs[i].type == "checkbox") {
                    backup.push(inputs[i].checked);
                } else {
                    backup.push(inputs[i].value);
                }
            }

            // Change the numbers in attributes by altering html code.
            last.id = last.id.replace(old_number, new_number);
            last.innerHTML = last.innerHTML.replace(re1, "$1" +
                new_number + "$2").replace(re2, "$1" + new_number);

            // Restore the values of form's input fields.
            for (i = inputs.length; i > 0; i--) {
                if (inputs[i - 1].type == "checkbox") {
                    inputs[i - 1].checked = backup.pop();
                } else {
                    inputs[i - 1].value = backup.pop();
                }                
            }
        }
    }
}