3
votes

I spent several days in order to add dynamically forms to formset and I find a way to do that. But I have a little issue with forms saving because it just saves the first form and not all forms from formset.

This is my formset :

DocumentFormSets = inlineformset_factory(Publication, Document, form=DocumentForm, extra=1, max_num=4)

This is my template :

<fieldset>
    <legend class="title"><span class="name">{% trans 'Document form' %}</span></legend>
    {{ DocumentFormSet.management_form }}
    <div id="form_set">
      {% for form in DocumentFormSet.forms %}
        <div class='formset-document'>
          <table class='no_error'>
            <div class="row">
              <div class="col-xs-3">
                {{ form.title|as_crispy_field }}
              </div>
              <div class="col-xs-3">
                {{ form.language|as_crispy_field }}
              </div>
              <div class="col-xs-3">
                {{ form.format|as_crispy_field }}
              </div>
              <div class="col-xs-3">
                {{ form.upload|as_crispy_field }}
              </div>
            </div>
          </table>
        </div>
      {% endfor %}
    </div>
    <br>
    <input type="button" class="btn btn-default" value="Add More" id="add_more">
    <div id="empty_form" style="display:none">
      <table class='no_error'>
        <div class="row">
          <div class="col-xs-3">
            {{ DocumentFormSet.empty_form.title|as_crispy_field }}
          </div>
          <div class="col-xs-3">
            {{ DocumentFormSet.empty_form.language|as_crispy_field }}
          </div>
          <div class="col-xs-3">
            {{ DocumentFormSet.empty_form.format|as_crispy_field }}
          </div>
          <div class="col-xs-3">
            {{ DocumentFormSet.empty_form.upload|as_crispy_field }}
          </div>
        </div>
      </table>
    </div>
</fieldset>

This is my JS part :

$('#add_more').click(function () {
  var form_idx = $('#id_form-TOTAL_FORMS').val();
  $('#form_set').append($('#empty_form').html().replace(/__prefix__/g, form_idx));
  $('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1);
});

And this is my views.py file :

class PublicationCreateView(EdqmCreateView):
    """ Create publication with document form through formset """
    model = Publication
    template_name = 'freepub/publication/publication_form.html'

    def get_context_data(self, **kwargs):
        context = super(PublicationCreateView, self).get_context_data(**kwargs)
        context['DocumentFormSets'] = DocumentFormSets(self.request.POST or None, self.request.FILES or None)
        return context

    def form_valid(self, form):
        context = self.get_context_data()
        formsets = context['DocumentFormSets']
        if form.is_valid() and formsets.is_valid():
            self.object = form.save()
            formsets.instance = self.object
            formsets.save()
        return super(PublicationCreateView, self).form_valid(form)

But with this function, it saves only the first document form in my formset :

I looked over id document form field and I get this :

enter image description here

The first title field get id_documents-0-title but when I add more forms, title field get always id_documents-undefined-title

How I can save my document forms from formsets ?

Thank you !

EDIT :

This is an edit from @HaiLang question :

This is my self.request.POST :

<QueryDict: {'csrfmiddlewaretoken': ['0LHaaEG1dkyZmOnP3X7037tZI45QrQOIRrQQAMhiOoTyeohDwQdcdTt08yuqH86Z'], 'category': ['23'], 'pub_id': ['PUBSDDD-55'], 'title': ['TESTPUBLIE'], 'description': [''], 'doc-TOTAL_FORMS': ['1'], 'doc-INITIAL_FORMS': ['0'], 'doc-MIN_NUM_FORMS': ['0'], 'doc-MAX_NUM_FORMS': ['4'], 'doc-0-title': ['doc1'], 'doc-0-language': ['FR'], 'doc-0-format': ['pdf'], 'doc-undefined-title': ['doc2'], 'doc-undefined-language': ['FR'], 'doc-undefined-format': ['pdf'], 'doc-__prefix__-title': [''], 'doc-__prefix__-language': [''], 'doc-__prefix__-format': [''], 'doc-__prefix__-upload': ['']}>

And this is my console.log :

Element kt.fn.init {}
1
to make things easier to read, you should use a plural for your formset. But why are you assigning something to document and saving document inside the for loop if document is your formset? - dirkgroten
Also on which line exactly do you get the error? And show us how you construct the formset (your get_context_data) - dirkgroten
@dirkgroten It's the first time I use formsets, so some things are a bit unclear yet. I added get_context_data in my question. The issue is on line : self.object = form.save() - Essex
is DocumentFormset created using an inlineformset_factory? - dirkgroten
Yes exactly ! I edited my code in order to show you missing elements. - Essex

1 Answers

2
votes

Luckily I just worked with FormSet recently so I can see the issue with the code in question.

(Edited by Ducky. Thank you!)

You have to set the same prefix between your views.py file and your app.js file. Because in your view file you are using prefix='doc' and in your javascript function you used form prefix.

So in your JQuery file you have to write :

(function ($) {
$('#add_more').click(function () {
    var form_idx = $('#id_doc-TOTAL_FORMS').val();
    // For debugging the issue
    // console.log('Element', $('#id_doc-TOTAL_FORMS'), 'Num of forms', form_idx);
    $('#form_set').append($('#empty_form').html().replace(/__prefix__/g, form_idx));
    $('#id_doc-TOTAL_FORMS').val(parseInt(form_idx) + 1);
    console.log('Element', $('#id_doc-TOTAL_FORMS'));
});
})(jQuery)

You can overwrite your view like this :

class PublicationCreateView(CreateView):
    """ Create publication with document form through formset """
    model = Publication
    template_name = 'publication_form.html'

    def get_context_data(self, **kwargs):
        context = super(PublicationCreateView, self).get_context_data(**kwargs)
        document_queryset = Document.objects.all()
        context['DocumentFormSets'] = DocumentFormSet(self.request.POST or None, self.request.FILES or None, prefix='doc', queryset=document_queryset)
        return context

    def form_valid(self, form):
        context = self.get_context_data()
        formsets = context['DocumentFormSets']
        // For debugging
        // print(self.request.POST)
        if form.is_valid() and formsets.is_valid():
            self.object = form.save()
            formsets.instance = self.object
            formsets.save()
        return super(PublicationCreateView, self).form_valid(form)