django - наборы полей динамической формы - PullRequest
5 голосов
/ 13 апреля 2010

Форма будет выплевывать неизвестное количество вопросов, на которые нужно ответить. каждый вопрос содержит подсказку, поле значения и единицу измерения. Форма создается во время выполнения в методе init класса.

edit: каждый вопрос получает уникальную подсказку для использования в качестве метки, а также уникальный список единиц для элемента select.

этот случай идеально подходит для повторяющихся наборов полей формы, которые можно легко стилизовать. но поскольку наборы полей - такие как в django-form-utils определены как кортежи, они неизменны ... и я не могу найти способ определить их во время выполнения. Возможно ли это, или, возможно, другое решение?

Edit:

formsets с initial_data не является ответом - initial_data просто позволяет установить значения по умолчанию для полей формы в formset. список элементов не может быть отправлен конструктору choicefield с помощью initial_data.

... если я не прав.

Ответы [ 4 ]

2 голосов
/ 13 апреля 2010

Проверить formsets .Вы должны быть в состоянии передать данные для каждого из N вопросов как исходные данные .Что-то вроде этого:

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 голос
/ 18 февраля 2012

Старый вопрос, но я сталкиваюсь с подобной проблемой. Самая близкая вещь, которую я нашел до сих пор, это фрагмент, основанный на посте, который Мальком сделал пару лет назад. 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 %}
0 голосов
/ 04 апреля 2016

Вот то, что я использовал для аналогичного случая (набор переменных, каждый из которых содержит набор переменных).

Я использовал функцию type() для создания своего класса форм, а 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.

Эта статья интересна, даже если она старая, и объясняет, как создавать динамические формы (с переменным набором полей). Это также дает другие способы достижения динамических форм.

Хотя это не объясняет, как это сделать с наборами полей, но это вдохновило меня найти способ сделать это, и принцип остался прежним.

Использовать его в виде довольно просто:

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

и в шаблоне:

    <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 голосов
/ 24 сентября 2010

Я использовал уловку ниже, чтобы создать динамический набор форм.Вызовите функцию 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)
...