1

I would like to be able to add dynamically django forms in my formset with Add button.

I tried to write different things but I don't overcome to get the expected result up to now.

I have a formset defined in my forms.py file :

AnimalFormSet = inlineformset_factory(Elevage, Animal, form=AnimalForm, extra=1)

Then, I created in my views.py file this function :

class ElevageCreateView(CreateView):

    model = Elevage
    template_name = 'elevage_form.html'

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

    def form_valid(self, form):
        context = self.get_context_data()
        animal = context['AnimalFormSet']
        if animal.is_valid():
            self.object = form.save()
            animal.instance = self.object
            animal.save()
        return super(ElevageCreateView, self).form_valid(form)

    def get_success_url(self):
        return reverse('animal-list')

Finally, I'm trying to write my template file :

<fieldset>
    <legend class="title"><span class="name">{% trans 'Animal form' %}</span></legend>
    {{ AnimalFormSet.management_form }}
      {% for form in AnimalFormSet.forms %}
        <div class='table'>
           <table class='no_error'>
             {{ form.as_table }}
           </table>
        </div>
      {% endfor %}
    <input type="button" class="btn btn-default" value="Add More" id="add_more">
    <script>
      $('#add_more').click(function () {
        cloneMore('div.table:last', 'service');
      });
    </script>
  </fieldset>

And I have javascript function :

 <script>
  function cloneMore(selector, type) {
    var newElement = $(selector).clone(true);
    var total = $('#id_' + type + '-TOTAL_FORMS').val();
    newElement.find(':input').each(function() {
        var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
        var id = 'id_' + name;
        $(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
    });
    newElement.find('label').each(function() {
        var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
        $(this).attr('for', newFor);
    });
    total++;
    $('#id_' + type + '-TOTAL_FORMS').val(total);
    $(selector).after(newElement);
}
  </script>

I spent lot of time in order to get the expected result but I don't overcome to get it. SO I need your help if you could help me.

I don't want to use another library, but I accept JS, AJAX, JQuery or django answers !

Thank you by advance

EDIT :

The issue is :

It displays the first form, but when I want to add a second one by clicking on Add button, it doesn't create the second one.

So I assume my Django part is good, but not the JS part ?

ChocoBomb
  • 301
  • 4
  • 15
  • You haven't really identified what the specific problem is with the code shown. How does it differ from expected result? – charlietfl Nov 08 '18 at 10:18
  • Ok I will edit my code. But if I can answer here too, when I click on `Add button` nothing appears. – ChocoBomb Nov 08 '18 at 10:19
  • The issue is with the 'selector' part in the function call `cloneMore('div.table:last', 'service');`. There's no `div` element with `class="table" ` in your markup, to match the call. You may refer the answer at https://stackoverflow.com/a/669982/1472458 – art Nov 08 '18 at 11:37
  • I edited my answer because it doesn't seems to work with the good selector. – ChocoBomb Nov 08 '18 at 13:33
  • Nobody ? It makes more than 1 week I'm blocked on this issue and the possibility to add extra forms to formsets :/ – ChocoBomb Nov 08 '18 at 15:45
  • @charlietfl I identified the issue, but I don't overcome to solve this process. Is it possible to get your help ? – ChocoBomb Nov 12 '18 at 09:21

1 Answers1

0

I was tackling the same problem and found this helpful post.

Basically, we can use the empty_form attribute of a formset and clone it each time we want to add the form. While each form in formset has form-<index> prefix, the empty_form attribute has form-__prefix__ which we should replace by the next index of form when we clone it.

And don't forget to increment the form-TOTAL_FORMS hidden input's value so Django knows how many forms are in the submitted formset.

The formset_wrapper and emptyform_wrapper is for easier jQuery element injection.

<fieldset>
  <legend class="title"><span class="name">{% trans 'Animal form' %}</span></legend>
  {{ AnimalFormSet.management_form }}
  <div id="formset_wrapper">
    {% for form in AnimalFormSet.forms %}
    <div class='table'>
      <table class='no_error'>
        {{ form.as_table }}
      </table>
    </div>
    {% endfor %}
  </div>
  <div id="emptyform_wrapper" style="display: none">
    <div class='table'>
      <table class='no_error'>
        {{ AnimalFormSet.empty_form.as_table }}
      </table>
    </div>
  </div>

  <input type="button" class="btn btn-default" value="Add More" id="add_more">
</fieldset>
<script>
  $('#add_more').click(function () {
    let total_form = $('#id_form-TOTAL_FORMS');
    let form_idx = total_form.val();

    $('#formset_wrapper').append($('#emptyform_wrapper').html().replace(/__prefix__/g, form_idx));
    total_form.val(parseInt(form_idx)+1);
  });
</script>

Try this snippet for a rendered example of the Django template above

hashlash
  • 897
  • 8
  • 19