C. Reed
C. Reed

Reputation: 2452

Django form field grouping

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.

Upvotes: 20

Views: 22548

Answers (3)

Henhuy
Henhuy

Reputation: 1164

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>

Upvotes: 7

C. Reed
C. Reed

Reputation: 2452

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

Upvotes: 5

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

Upvotes: 23

Related Questions