18

Say I have a form with 20 fields, and I want to put 10 of these fields (group1) in a particular div environment and the other 10 fields (group2) in a different div environment. Something like:

<div class="class1">
{% for field in form.group1 %}
            {{ field.label}}: {{ field }}
{% endfor %}
</div>

<div class="class2">
{% for field in form.group2 %}
            {{ field.label}}: {{ field }}
{% endfor %}
</div>

Any ideas how I could accomplish this by iterating over the fields? More generally, I would like to be able to do this with many different div environments and sets of form fields.

C. Reed
  • 2,382
  • 7
  • 30
  • 35

3 Answers3

23

Any logical way to group fields would work... say you have a method on your form that returns form fields that you explicitly group?

To save typing, perhaps a certain field prefix naming scheme?

class MyForm(forms.Form):
    group1_field = forms.CharField()
    group1_field = forms.CharField()
    group2_field = forms.CharField()
    group2_field = forms.CharField()

   def group1(self):
        return [self[name] for name in filter(lambda x: x.startswith('group1_'), self.fields.values()]

Perhaps set an attribute on the field you can filter by?

class MyForm(forms.Form):
    field = forms.CharField()
    field.group = 1

    field2 = forms.CharField()
    field2.group = 2

    def group1(self):
        return filter(lambda x: x.group == 1, self.fields.values())

    def group2(self):
        return filter(lambda x: x.group == 2, self.fields.values())

You could also use the regroup tag if you set these attributes.

{% regroup form.fields by group as field_group %}
{% for group in field_group %}
<div class="group_{{ group.grouper }}">
  {% for field in group.list %}
    {{ field }}
  {% endfor %}
</div>
{% endfor %}
Yuji 'Tomita' Tomita
  • 115,817
  • 29
  • 282
  • 245
  • I like the use of the regroup tag --- I didn't know this existed. Thanks! – C. Reed Apr 28 '12 at 21:45
  • Note: your first solution doesn't quite work to loop over the fields in the template --- use the `yield` keyword to create a generator to obtain the expected fields (see my post below) and emulate django's typical field iteration – C. Reed Apr 28 '12 at 22:49
  • @C.Reed, you're right I forgot to return BoundFields. Updated. Thanks for noticing! – Yuji 'Tomita' Tomita Apr 28 '12 at 23:30
  • 2
    I did try the regroup approach, but was not successful. the problem is that the new group attribute doesn't exist for the field tag - but for the "field.field" tag - which refers to the underlying python class. Is there a way I can regroup by those underlying classes' attribute? – batusek Jan 10 '16 at 21:57
  • 2
    @Yuji'Tomita'Tomita What would be your approach in case you had a `ModelForm` instead of `Form`? In that case we usually don't redefine the fields inside the form class. Well, I suppose the easiest workaround would be to drop the `ModelForm` in favor of `Form` after all... right? – stelios Feb 24 '18 at 19:08
  • 1
    I reallly like your suggestion using _regroup_ template tag - but I cannot get it to work. Grouping `form.fields` or even `form.fields.values` gives only keys or `Field` instances respectively, instead of `BoundFields`. On the other hand, grouping `forms` directly returns `BoundFields` correctly, but the _group_ attribute is not transfered. Thus, resulting into one single group `None`. Any suggestions? – Henhuy Apr 05 '18 at 11:55
  • @Henhuy late, but same issue here – martin Sep 28 '20 at 18:36
  • I'd also be interested to know how to adapt this approach to a ModelForm. – w5m Jul 07 '21 at 07:11
6

I finally was able to bring @Yuji'Tomita'Tomitas regroup-template-tag-solution to work (see comments in @Yuji'Tomita'Tomitas answer to understand difficulties). I think this is really a nice and easy way to perfom grouping of fields!

The solution was to regroup via group attribute of field accessing the field attribute of returned BoundFields. Minimal example:

In forms.py :

class TestForm(Form):
    a = IntegerField()
    a.group = 1
    b = IntegerField()
    b.group = 1
    c = IntegerField()
    c.group = 2
    d = IntegerField()
    d.group = 2

In template:

<form>
  {% csrf_token %}
  {% regroup form by field.group as field_groups %}
  {% for field_group in field_groups %}
    {{field_group.grouper}}
    {% for field in field_group.list %}
      {{field}}
    {% endfor %}
  {% endfor %}
</form>
Henhuy
  • 1,034
  • 1
  • 13
  • 23
5

Here's a relevant SO question: Django and Fieldsets on Modelform, though this seems a bit overkill for what I'm looking to accomplish. Also, here's one possible hack, although I'm curious to hear how Django experts solve this problem.

(0) Define a python fieldset object that is iterable so we can iterate over it in a django template:

from django.forms.forms import BoundField

class FieldSet(object):
    def __init__(self,form,fields,legend='',cls=None):
        self.form = form
        self.legend = legend
        self.fields = fields
        self.cls = cls

    def __iter__(self):
        for name in self.fields:
            field = self.form.fields[name]
            yield BoundField(self.form, field, name)

(1) In the view use:

fieldsets = (FieldSet(form_object, ('field_name1','field_name2'),
                        legend='Div env 1',
                        cls="class1"),
             FieldSet(form_object, ('field_name3','field_name4'), 
                        legend="Div env 2",
                        cls="class2"))

return render_to_response('my_form.html',
                          {'form': form_object,'fieldsets':fieldsets},
                          context_instance=RequestContext(request))

(2) Now in the template do:

{% for set in fieldsets %}
    <fieldset{% if set.cls %} class="{{ set.cls }}"{% endif %}>
      <legend>{{ set.legend }}</legend>
      {% for field in set %}
          {{ field.label}} : {{ field }}
      {% endfor %}
    </fieldset>
{% endfor %}

Note that it is possible to replace the fieldset tag with a div tag to address my specific question.

+++ Much of this answer extracted from this blog post by Michael Kowalchik. +++

Community
  • 1
  • 1
C. Reed
  • 2,382
  • 7
  • 30
  • 35
  • `yield self.form[name]` is sufficient. (I had problems importing BoundField but with that line it's not even necessary.) – Risadinha Oct 13 '15 at 15:19