5

I would like to render each form of a formset in a different bootstrap3 tabs using django-crispyForms, but it seems to not be simple because crispyForms does not handle formset completely yet.

enter image description here

my form:

class BlogMessageForm(forms.ModelForm):
    class Meta:
        model = BlogMessage
        fields = ['field1', 'field2', ]

class BlogMessageFormsetHelper(FormHelper):
    def __init__(self, *args, **kwargs):
        super(BlogMessageFormsetHelper, self).__init__(*args, **kwargs)
        self.form_tag = False
        self.layout = Layout(
            TabHolder(
                Tab('Form%s' % (form.pk),
                    'field1', 'field2', 'DELETE',
                    )
             )

        )

my view:

def all_blogs_messages_in_bootstrap_tab(request):
  all_blog_messages = BlogMessage.objects.all()
  blogMessageForm = modelformset_factory(BlogMessage, form=BlogMessageForm, extra=1, can_delete=True)
  formset = blogMessageForm(queryset=all_blog_messages)
  helper = BlogMessageFormHelper()

  render_to_response("blogs/blogMessage_forms.html", {'formset': formset, 'helper': helper}, context_instance=RequestContext(request))

my crispyForms template:

{% load crispy_forms_tags %}
<form method="post" action="" encrypt="multipart/form-data">{% csrf_token %}
    {% crispy formset helper %}
</form>

There it is some info about that crispyForm formset contraint:

Community
  • 1
  • 1
Below the Radar
  • 7,321
  • 11
  • 63
  • 142
  • just loop through the forms in the formset and call crisy's form renderer instead. you just need to wrap/create the tabs code inside the forloop. – warath-coder Mar 08 '15 at 02:01
  • yes, that would be great to have a crispy template doing that. If someone can create such template, I will open a bounty for that tomorrow. – Below the Radar Mar 08 '15 at 15:50

1 Answers1

9

Here is how I would do it:

<form method="post" enctype="multipart/form-data">{% csrf_token %}
  {{ formset.management_form }}
  <div role="tabpanel">
    <ul class="nav nav-tabs" role="tablist">
      {% for form in formset %}
        <li role="presentation" class="{% if forloop.first %}active{% endif %}">
          <a href="#id_form-{{ forloop.counter0 }}" aria-controls="id_form-{{ forloop.counter0 }}" role="tab" data-toggle="tab">
            {% if forloop.counter0 < formset.initial_forms|length %}
              Form{{ forloop.counter }}
            {% else %}
              New Form
            {% endif %}
          </a>
        </li>
      {% endfor %}
    </ul>
    <div class="tab-content">
      {% for form in formset %}
        <div role="tabpanel" class="tab-pane{% if forloop.first %} active{% endif %}" id="id_form-{{ forloop.counter0 }}">
          {% crispy form %}
          {% if form.instance.pk %}
             <input id="id_form-{{ forloop.counter0 }}-id" name="form-{{ forloop.counter0 }}-id" type="hidden" value="{{ form.instance.id }}">
          {% endif %}
          {% if forloop.counter0 < formset.initial_forms|length %}
            {% if formset.can_delete %}
              <input id="id_form-{{ forloop.counter0 }}-DELETE" name="form-{{ forloop.counter0 }}-DELETE" type="hidden">
              <button type="submit" data-id="id_form-{{ forloop.counter0 }}-DELETE" class="btn btn-default btn-formset-delete">Delete</button>
            {% endif %}
          {% endif %}
        </div>
      {% endfor %}
    </div>
  </div>
  <button type="submit" class="btn btn-primary">Save</button>
</form>

Almost forgot, delete button won't work without some javascript. But that is optional of course, delete field can be made visible and button removed, otherwise:

$(document).ready(function(){
    $(".btn-formset-delete").click(function(){
        $("#" + $(this).data('id')).val("on");
        return true;
    });
});

Also add this to the form:

def __init__(self, *args, **kwargs):
    super(MyForm, self).__init__(*args, **kwargs)
    self.helper = FormHelper(self)
    self.helper.form_tag = False
    self.helper.disable_csrf = True
lehins
  • 9,642
  • 2
  • 35
  • 49
  • Great job Alexey! The only thing however is that the 'form-0-id' hidden input is not added in each forms of the formset, so I cant save. Do you have an idea why? – Below the Radar Mar 13 '15 at 14:22
  • I made a small adjustment that should fix the problem. Let me know if that field still doesn't show up. – lehins Mar 13 '15 at 17:01
  • ok I think you could just put the hidden form-id input in a {% if form.instance %}? However, I dont understand why this input is not added automatically with all other forms – Below the Radar Mar 13 '15 at 17:16
  • 1
    You are right, but it be better to check for `pk` rather than for instance: `{% if form.instance.pk %}`, just because each form actually has an instance associated with it: https://github.com/django/django/blob/master/django/forms/models.py#L322 Have no idea why that field is not rendered automatically. – lehins Mar 13 '15 at 17:36
  • Hi there @lehins, after this time this post still usefull, thanks a lot. I'm building a dynamic form tab. And this solution is almost perfect. I have one issue also in 'form-0-id', I need to have 'form_0_id' because I use custom widgets for some attributes and there are js dynamic names generated and I get the '2873 Uncaught SyntaxError: Unexpected token '-'', I cloned the formsets.py to change add_prefix(), to apply that logic, but is not working even without change anything. The error is, list indices must be integers or slices, not str in "def __getitem__(self, index):", any idea? – Ernesto Casanova Jun 10 '20 at 10:46
  • @ErnestoCasanova sorry mate, haven't touched django since I wrote this answer. Try asking a new question on SO, someone might help you out – lehins Jun 10 '20 at 11:35
  • Hi @lehins, thanks for the quick answer. Five years is really long time, I understand, I'm going to do that. Best regards. – Ernesto Casanova Jun 10 '20 at 12:15