django - 动态表格字段集

时间:2010-04-12 21:14:38

标签: django django-templates django-forms django-models

表格将吐出未知数量的问题以供回答。每个问题都包含一个提示,一个值字段和一个单位字段。表单是在运行时在formclass的init方法中构建的。

编辑:每个问题都会收到一个独特的提示,用作标签,以及选择元素的唯一单位列表。

这似乎是一个适用于可迭代表单字段集的案例,可以轻松设置样式。但由于字段集 - 例如django-form-utils中的字段集被定义为元组,它们是不可变的......我找不到在运行时定义它们的方法。这是可能的,还是另一种解决方案?

编辑:

具有initial_data的

formset不是答案 - initial_data仅允许为formset中的表单字段设置默认值。无法通过initial_data将项目列表发送到choicefield构造函数。

......除非我错了。

4 个答案:

答案 0 :(得分:2)

结帐formsets。您应该能够将N个问题中的每个问题的数据传递为initial data。这些方面的东西:

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)

答案 1 :(得分:1)

老问题,但我遇到了类似的问题。到目前为止,我发现的最接近的事情是这个片段基于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 %}

答案 2 :(得分:0)

我使用下面的技巧来创建动态formset。从视图中调用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)

答案 3 :(得分:0)

Here is what I used for a similar case (a variable set of fieldsets, each one containing a variable set of fields).

I used the type() function to build my Form Class, and BetterBaseForm class from 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' : {}}))

Here is an example of how dic may look like :

[('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']})]

I used BetterBaseForm rather than BetterForm for the same reason this article suggests to use BaseForm rather than Form.

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

It doesn't explain how to do it with fieldsets though, 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 a 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>