Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making a Django form class with a dynamic number of fields

Jacob Kaplan-Moss has an extensive writeup on dynamic form fields: http://jacobian.org/writing/dynamic-form-generation/

Essentially, you add more items to the form's self.fields dictionary during instantiation.


Here's another option: how about a formset? Since your fields are all the same, that's precisely what formsets are used for.

The django admin uses FormSets + a bit of javascript to add arbitrary length inlines.

class ColorForm(forms.Form):
    color = forms.ChoiceField(choices=(('blue', 'Blue'), ('red', 'Red')))

ColorFormSet = formset_factory(ColorForm, extra=0) 
# we'll dynamically create the elements, no need for any forms

def myview(request):
    if request.method == "POST":
        formset = ColorFormSet(request.POST)
        for form in formset.forms:
            print "You've picked {0}".format(form.cleaned_data['color'])
    else:
        formset = ColorFormSet()
    return render(request, 'template', {'formset': formset}))

JavaScript

    <script>
        $(function() {
            // this is on click event just to demo.
            // You would probably run this at page load or quantity change.
            $("#generate_forms").click(function() {
                // update total form count
                quantity = $("[name=quantity]").val();
                $("[name=form-TOTAL_FORMS]").val(quantity);  

                // copy the template and replace prefixes with the correct index
                for (i=0;i<quantity;i++) {
                    // Note: Must use global replace here
                    html = $("#form_template").clone().html().replace(/__prefix_/g', i);
                    $("#forms").append(html);
                };
            })
        })
    </script>

Template

    <form method="post">
        {{ formset.management_form }}
        <div style="display:none;" id="form_template">
            {{ formset.empty_form.as_p }}
        </div><!-- stores empty form for javascript -->
        <div id="forms"></div><!-- where the generated forms go -->
    </form>
    <input type="text" name="quantity" value="6" />
    <input type="submit" id="generate_forms" value="Generate Forms" />

you can do it like

def __init__(self, n,  *args, **kwargs):
  super(your_form, self).__init__(*args, **kwargs)
  for i in range(0, n):
    self.fields["field_name %d" % i] = forms.CharField()

and when you create form instance, you just do

forms = your_form(n)

it's just the basic idea, you can change the code to whatever your want. :D


The way I would do it is the following:

  1. Create an "empty" class that inherits from froms.Form, like this:

    class ItemsForm(forms.Form):
        pass
    
  2. Construct a dictionary of forms objects being the actual forms, whose composition would be dependent on the context (e.g. you can import them from an external module). For example:

    new_fields = {
        'milk'  : forms.IntegerField(),
        'butter': forms.IntegerField(),
        'honey' : forms.IntegerField(),
        'eggs'  : forms.IntegerField()}
    
  3. In views, you can use python native "type" function to dynamically generate a Form class with variable number of fields.

    DynamicItemsForm = type('DynamicItemsForm', (ItemsForm,), new_fields)
    
  4. Pass the content to the form and render it in the template:

    Form = DynamicItemsForm(content)
    context['my_form'] = Form
    return render(request, "demo/dynamic.html", context)
    

The "content" is a dictionary of field values (e.g. even request.POST would do). You can see my whole example explained here.