2010-04-12 4 views
5

Un modulo sputerà un numero sconosciuto di domande a cui rispondere. ogni domanda contiene un prompt, un campo valore e un campo unità. Il modulo è creato in fase di runtime nel metodo init di formclass.django - campi modulo dinamici

modifica: ogni domanda riceve un prompt univoco da utilizzare come etichetta, nonché un elenco univoco di unità per l'elemento selezionato.

questo sembra un caso perfetto per i campi di modulo iterable, che potrebbero essere facilmente in stile. ma poiché i set di campi, come quelli di django-form-utils, sono definiti come tuple, sono immutabili ... e non riesco a trovare un modo per definirli in fase di runtime. è possibile, o forse un'altra soluzione?

Edit:

formsets con initial_data non è la risposta - initial_data solo consente l'impostazione di valori predefiniti per i campi del modulo in un formset. un elenco di elementi non può essere inviato al costruttore del campo di scelta a partire da initial_data.

... a meno che non mi sbagli.

+0

Non sarebbe meglio usare [formsets] (http: //docs.djangoproject .com/it/dev/topics/forms/formsets /) al posto dei set di campi? Una classe Form personalizzata per una domanda (con un attributo 'prompt'), quindi si caricano i dati della domanda usando [argomento parola chiave' 'initial'] (http://docs.djangoproject.com/en/dev/topics/forms/formsets/# utilizzando-iniziale-dati-con-un-formset)? Le forme –

+0

non fanno il trucco. almeno non il dato formset_factory. Devo essere in grado di fornire alcuni parametri ai costruttori di campo effettivi per ogni modulo nel formset - l'etichetta/prompt per il campo del valore e l'elenco di unità per il campo di scelta. –

risposta

2

Check out formsets. Dovresti essere in grado di trasmettere i dati per ciascuna delle N domande come initial data. Qualcosa in questo senso:

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

I dati iniziali servono per assegnare valori predefiniti a un elemento del modulo, non per fornire dati per la costruzione del modulo in un formset. –

0

Ho usato il seguente trucco per creare un formset dinamico. Chiama la funzione create_dynamic_formset() dalla tua 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

Vecchia domanda ma sto riscontrando un problema simile. La cosa più vicina che ho trovato finora è questo frammento basato su un post che Malcom ha fatto un paio di anni fa adesso. http://djangosnippets.org/snippets/1955/

Il frammento originale non ha affrontato il lato modello e dividendoli in fieldsets, ma l'aggiunta di ogni modulo al proprio fieldset dovrebbe realizzare questo.

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

modello

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

Ecco quello che ho usato per un caso simile (un insieme di variabili fieldsets, ognuno contenente un insieme variabile di campi).

ho usato la funzione type() per costruire la mia classe di form, e BetterBaseForm classe da 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' : {}})) 

Ecco un esempio di come dic possono apparire come:

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

ho usato BetterBaseForm piuttosto che BetterForm per lo stesso motivo this article suggerisce di utilizzare BaseForm piuttosto che Form.

Questo articolo è interessante anche se è vecchio e spiega come eseguire i moduli dinamici (con set di campi variabile). Fornisce anche altri modi per ottenere forme dinamiche.

Non spiega come farlo con i fieldset, ma mi ha ispirato a scoprire come farlo, e il principio rimane lo stesso.

Usandolo in una vista è piuttosto semplice:

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

e in un modello:

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