Django - dynamic field forms

The form will spit out an unknown number of questions that need to be answered. Each question contains a tooltip, a value field, and a unit field. The form is created at run time in the init method of the class class.

edit: each question receives a unique invitation to use as a label, as well as a unique list of units for the select element.

this seems to be ideal for iterative forms of fields that can be easily styled. but since field sets, such as django-form-utils , are defined as tuples, they are immutable ... and I cannot find a way to define them at runtime. is this possible, or perhaps another solution?

Edit:

formsets with initial_data is not the answer - initial_data just allows you to set default values ​​for the form fields in the form set. the list of elements cannot be sent to the constructor of the selection field via initial_data.

... if I'm not mistaken.

+5
source share
4 answers

Check out the formsets . You should be able to transfer data on each of the N questions as initial data . Something like that:

question_data = []
for question in your_question_list:
    question_data.append({'prompt': question.prompt, 
                          'value': question.value, 
                          'units': question.units})
QuestionFormSet = formset_factory(QuestionForm, extra=2)
formset = QuestionFormSet(initial=question_data)
+2
source

, . , , , , Malcom . http://djangosnippets.org/snippets/1955/

, .

forms.py

    from django.forms.formsets import Form, BaseFormSet, formset_factory, \
            ValidationError


    class QuestionForm(Form):
        """Form for a single question on a quiz"""
        def __init__(self, *args, **kwargs):
            # CODE TRICK #1
            # pass in a question from the formset
            # use the question to build the form
            # pop removes from dict, so we don't pass to the parent
            self.question = kwargs.pop('question')
            super(QuestionForm, self).__init__(*args, **kwargs)

            # CODE TRICK #2
            # add a non-declared field to fields
            # use an order_by clause if you care about order
            self.answers = self.question.answer_set.all(
                    ).order_by('id')
            self.fields['answers'] = forms.ModelChoiceField(
                    queryset=self.answers())


    class BaseQuizFormSet(BaseFormSet):
        def __init__(self, *args, **kwargs):
            # CODE TRICK #3 - same as #1:
            # pass in a valid quiz object from the view
            # pop removes arg, so we don't pass to the parent
            self.quiz = kwargs.pop('quiz')

            # CODE TRICK #4
            # set length of extras based on query
            # each question will fill one 'extra' slot
            # use an order_by clause if you care about order
            self.questions = self.quiz.question_set.all().order_by('id')
            self.extra = len(self.questions)
            if not self.extra:
                raise Http404('Badly configured quiz has no questions.')

            # call the parent constructor to finish __init__            
            super(BaseQuizFormSet, self).__init__(*args, **kwargs)

        def _construct_form(self, index, **kwargs):
            # CODE TRICK #5
            # know that _construct_form is where forms get added
            # we can take advantage of this fact to add our forms
            # add custom kwargs, using the index to retrieve a question
            # kwargs will be passed to our form class
            kwargs['question'] = self.questions[index]
            return super(BaseQuizFormSet, self)._construct_form(index, **kwargs)


    QuizFormSet = formset_factory(
        QuestionForm, formset=BaseQuizDynamicFormSet)

views.py

from django.http import Http404


    def quiz_form(request, quiz_id):
        try:
            quiz = Quiz.objects.get(pk=quiz_id)
        except Quiz.DoesNotExist:
            return Http404('Invalid quiz id.')
        if request.method == 'POST':
            formset = QuizFormSet(quiz=quiz, data=request.POST)
            answers = []
            if formset.is_valid():
                for form in formset.forms:
                    answers.append(str(int(form.is_correct())))
                return HttpResponseRedirect('%s?a=%s'
                        % (reverse('result-display',args=[quiz_id]), ''.join(answers)))
        else:
            formset = QuizFormSet(quiz=quiz)

        return render_to_response('quiz.html', locals())

{% for form in formset.forms %}
<fieldset>{{ form }}</fieldset>
{% endfor %}
+1

, . create_dynamic_formset() .

def create_dynamic_formset(name_filter):

    """
    -Need to create the classess dynamically since there is no other way to filter
    """
    class FormWithFilteredField(forms.ModelForm):
        type = forms.ModelChoiceField(queryset=SomeType.objects.filter(name__icontains=name_filter))

        class Meta:
            model=SomeModelClass

    return modelformset_factory(SomeModelClass, form=FormWithFilteredField)
0

( , ).

type() Form BetterBaseForm django-form-utils.

def makeFurnitureForm():
    """makeFurnitureForm() function will generate a form with
    QuantityFurnitureFields."""

    furnitures = Furniture.objects.all()
    fieldsets = {}
    fields = {}

    for obj in furnitures:
        # I used a custom Form Field, but you can use whatever you want.
        field = QuantityFurnitureField(name = obj.name)

        fields[obj.name] = field
        if not obj.room in fieldsets.keys():
            fieldsets[obj.room] = [field,]
        else:
            fieldsets[obj.room].append(field)

    # Here I use a double list comprehension to define my fieldsets
    # and the fields within.
    # First item of each tuple is the fieldset name.
    # Second item of each tuple is a dictionnary containing :
    #  -The names of the fields. (I used a list comprehension for this)
    #  -The legend of the fieldset.
    # You also can add other meta attributes, like "description" or "classes",
    # see the documentation for further informations.
    # I added an example of output to show what the dic variable
    # I create may look like.
    dic = [(name, {"fields": [field.name for field in fieldsets[name]], "legend" : name})
           for name in fieldsets.keys()]
    print(dic)
    # Here I return a class object that is my form class.
    # It inherits from both forms.BaseForm and forms_utils.forms.BetterBaseForm.
    return (type("FurnitureForm",
                 (forms.BaseForm, form_utils.forms.BetterBaseForm,),
                 {"_fieldsets" : dic, "base_fields" : fields,
                  "_fieldset_collection" : None, '_row_attrs' : {}}))

, dic:

[('fieldset name 1',
    {'legend': 'fieldset legend 2',
     'fields' ['field name 1-1']}),
('fieldset name 2',
    {'legend': 'fieldset legend 2',
     'fields' : ['field 1-1', 'field 1-2']})]

BetterBaseForm BetterForm BaseForm, Form.

This article is interesting, even if it's old, and explains how to make dynamic forms (with a variable set of fields). It also gives other ways to achieve dynamic forms.

This does not explain how to do this using fields, but it inspired me to find how to do it, and the principle remains the same.

Using it in a view is pretty simple:

return (render(request,'main/form-template.html', {"form" : (makeFurnitureForm())()}))

and in the template:

    <form method="POST" name="myform" action=".">
      {% csrf_token %}
      <div>
        {% for fieldset in form.fieldsets %}
        <fieldset>
          <legend>{{ fieldset.legend }}</legend>
          {% for field in fieldset %}
          <div>
            {% include "main/furniturefieldtemplate.html" with field=field %}
          </div>
          {% endfor %}
        </fieldset>
        {% endfor %}
      </div>
      <input type="submit" value="Submit"/>
    </form>
0
source

All Articles