2010-04-12 23 views
5

Un formulario arrojará un número desconocido de preguntas para responder. cada pregunta contiene una solicitud, un campo de valor y un campo de unidad. El formulario se genera en tiempo de ejecución en el método init de formclass.django - fieldsets de formulario dinámicos

editar: cada pregunta recibe una solicitud única para usar como etiqueta, así como una lista única de unidades para el elemento seleccionado.

esto parece un caso perfecto para conjuntos de campos de formas iterables, que se pueden diseñar fácilmente. pero como los conjuntos de campos, como los del django-form-utils, se definen como tuplas, son inmutables ... y no puedo encontrar una manera de definirlos en tiempo de ejecución. ¿Es esto posible, o tal vez otra solución?

Editar:

formsets con initial_data no es la respuesta - initial_data sólo podrá realizarse el ajuste de los valores predeterminados para los campos de formulario en un formset. una lista de elementos no se puede enviar al constructor del campo de elección a través de initial_data.

... a menos que esté equivocado.

+0

¿No sería mejor usar [Formsets] (http: //docs.djangoproject .com/es/dev/topics/forms/formsets /) en lugar de conjuntos de campo? Una clase de formulario personalizada para una pregunta (con un atributo 'prompt'), luego carga los datos de la pregunta usando el argumento de la palabra clave' 'initial'] (http://docs.djangoproject.com/en/dev/topics/forms/formsets/# using-initial-data-with-a-formset)? –

+0

Formsets no funcionan. al menos no el formset_factory dado. Tengo que poder proporcionar algunos parámetros a los constructores de campo reales para cada formulario en el formset - la etiqueta/solicitud para el campo de valor, y la lista de unidades para el campo choice. –

Respuesta

2

Echa un vistazo formsets. Debería poder pasar los datos para cada una de las N preguntas como initial data. Algo en esta línea:

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) 
+0

los datos iniciales son para dar valores predeterminados a un elemento de formulario, no para proporcionar datos para la construcción de formularios en un formset. –

0

Utilicé el truco a continuación para crear un formset dinámico. Llame a la función create_dynamic_formset() desde su vista.

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) 
1

Pregunta anterior, pero me encuentro con un problema similar. Lo más parecido que he encontrado hasta ahora es este fragmento basado en una publicación que hizo Malcom hace un par de años. http://djangosnippets.org/snippets/1955/

El fragmento original no abordó el lado de la plantilla y los dividió en conjuntos de campos, pero agregar cada formulario a su propio conjunto de campos debería lograr eso.

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()) 

plantilla

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

aquí es lo que solía para un caso similar (un conjunto variable de fieldsets, cada uno que contiene un conjunto variable de campos).

Usé la función type() para construir mi clase de formulario, y BetterBaseForm clase de 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' : {}})) 

Aquí es un ejemplo de cómo dic puede verse como:

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

he usado en lugar de BetterBaseFormBetterForm por la misma razón this article sugiere utilizar BaseForm en lugar de Form.

Este artículo es interesante aunque sea antiguo, y explica cómo hacer formas dinámicas (con un conjunto variable de campos). También le da otras formas de lograr formas dinámicas.

Sin embargo, no explica cómo hacerlo con fieldsets, pero me inspiró para encontrar cómo hacerlo, y el principio sigue siendo el mismo.

se utiliza en un punto de vista es bastante simple:

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

y en una plantilla:

<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>