41

I know you can specify fieldsets in django for Admin helpers. However, I cannot find anything useful for ModelForms. Just some patches which I cannot use. Am I missing something? Is there a way I could achieve something like fieldsets without manually writing out each field on my template in the appropriate tag.

I would ideally like to iterate through a set of BoundFields. However, doing something like this at the end of my ModelForm:

    fieldsets = []
    fieldsets.append(('Personal Information',
                      [username,password,password2,first_name,last_name,email]),) # add a 2 element tuple of string and list of fields
    fieldsets.append(('Terms & Conditions',
                      [acceptterms,acceptprivacy]),) # add a 2 element tuple of string and list of fields

fails as the items contained in my data structure are the raw fields, not the BoundFields. t looks like BoundFields are generated on the fly... this makes me sad. Could I create my own subclass of forms.Form which contains a concept of fieldsets (even a rough one that is not backward compatible... this is just for my own project) and if so, can you give any pointer? I do not want to mess with the django code.

6 Answers 6

52

I think this snippet does exactly what you want. It gives you a Form subclass that allows you to declaratively subdivide your form into fieldsets and iterate through them in your template.

Update: that snippet has since become part of django-form-utils

2
  • Excellent, it's going in my current project!
    – Van Gale
    Commented Feb 9, 2009 at 2:41
  • I'm using form-utils as well, and I've also written an extension for form-utils which allows the fields to be arranged in rows within the fieldset.. just like admin. I am planning to contact the author of form-utils to see if he'll accept it as a patch. Meanwhile, if you would like it, just ask... Commented Aug 21, 2015 at 3:11
36

Fieldsets in modelforms are still in "design" stage. There's a ticket in Django trac with low activity.

It's something I've been interested in researching myself in the near future, but since I haven't done it yet the best I can offer are these snippets:

Edit: I just noticed this question again and I realize it needs an edit to point out Carl's project django-form-utils which contains a BetterForm class which can contain fieldsets. If you like this project give him a +1 for his answer below :)

2
  • 1
    The second link gave me the clue that I needed to implement my rudimentary fieldset support. Thank you. Commented Feb 7, 2009 at 21:24
  • 1
    Nov. 2010, used django-form-utils successfully with 1.2.3. Thanks for the hint.
    – Boldewyn
    Commented Nov 24, 2010 at 14:18
16

One thing you can do is break your logical fieldsets into separate model form classes.

class PersonalInfoForm (forms.ModelForm):
    class Meta:
        model=MyModel
        fields=('field1', 'field2', ...)

class TermsForm (forms.ModelForm):
    class Meta:
        model=MyModel
        fields=('fieldX', 'fieldY', ...)

Pass them to your template in different variables and break up the formsets:

<form ...>
   <fieldset><legend>Personal Information</legend>
       {{ personal_info_form }}
   </fieldset>
   <fieldset><legend>Terms and Conditions</legend>
       {{ terms_form }}
   </fieldset>
</form>

In that sense each of your form classes is just a fragment of the actual HTML form.

It introduces a touch of complexity when you call save on the form. You'll probably want to pass commit=False and then merge the resultant objects. Or just avoid using ModelForm.save altogether and populate your model object by hand with 'cleaned_data'

4
  • 1
    This is way too much effort, especially in the view layer.
    – Greg
    Commented Apr 29, 2011 at 0:34
  • 5
    @Greg, The downvote doesn't bother me, but your comment is confusing. What do you mean by too much effort? Breaking a monolithic form into separate form fragments that you can manipulate independently of one another is an idiomatic approach to this problem. Django may have added more "fieldset" utilities in the two years since I provided this answer, but the approach is still valid nonetheless. Commented Apr 29, 2011 at 18:45
  • 1
    Greg's point of view is terrible invalid!.. This option is applicable and flexible. The only time it didn't worked, was when in the 'clean' method some data from two or more form-fieldsets may be required!
    – StefanNch
    Commented Aug 9, 2011 at 8:56
  • This is a fine approach, especially since working with many form segments often introduces added complexities, for instance "don't validate this section if the other section isn't filled in" etc... such logic is often better handled in separate forms.
    – benjaoming
    Commented Jul 17, 2013 at 18:58
4

Daniel Greenfelds django-uni-form solves this with a the Layout helper class. I'm trying it out right now and it looks pretty clean to me.

Uniform helpers can use layout objects. A layout can consist of fieldsets, rows, columns, HTML and fields.

I originally picked Django-uni-form because it complies with section 508.

3

You can use this package: https://pypi.org/project/django-forms-fieldset/

pip install django-forms-fieldset

Add forms_fieldset to your INSTALLED_APPS setting like this:

INSTALLED_APPS = [
    ...
    'forms_fieldset',
]

Add fieldsets in your form

from django.forms import ModelForm

from .models import Student

class StudentForm(ModelForm):
    fieldsets = [
        ("Student Information", {'fields': [
            ('first_name', 'last_name'),
            ('email', 'adress'),
        ]}),
        ("Parent Information", {'fields': [
            'mother_name',
            'father_name',
        ]}),
    ]
    class Meta:
        model = Student
        fields = '__all__'

In your views

def home(request):
    form = StudentForm()
    if request.method == 'POST':
        form = Form(request.POST, request.FILES)
        #save...
    context = {
        'form': form,
    }
    return render(request, 'home.html', context)

in your template

{% load forms_fieldset static %}
<link rel="stylesheet" type="text/css" href="{% static 'forms_fieldset/css/main.css' %}">

<form>
    {{ form|fieldset:'#42945c' }}
</form>
1
  • thanks! it worked perfect for me! it is way easier. Commented Dec 20, 2022 at 21:29
0

This was the code that I developed in order to understand custom tags (with links). I applied it to create a fieldset.

Disclaimer: I encourage the use of any of the above answers, this was just for the sake of learning.

templatetags/myextras.py:

from django import template
from django.template import Context

register = template.Library()


class FieldsetNode(template.Node):
    """ Fieldset renderer for 'fieldset' tag """
    def __init__(self, nodelist, fieldset_name):
        """ Initialize renderer class
        https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#writing-the-renderer
        :param nodelist: a list of the template nodes inside a block of 'fieldset'
        :param fieldset_name: the name of the fieldset
        :return: None
        """
        self.nodelist = nodelist
        self.fieldset_name = fieldset_name

    def render(self, context):
        """ Render the inside of a fieldset block based on template file
        https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#auto-escaping-considerations
        :param context: the previous template context
        :return: HTML string
        """
        t = context.template.engine.get_template('myapp/fieldset.html')
        return t.render(Context({
            'var': self.nodelist.render(context),
            'name': self.fieldset_name,
        }, autoescape=context.autoescape))


@register.tag
def fieldset(parser, token):
    """ Compilation function for fieldset block tag
    Render a form fieldset
    https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#writing-the-compilation-function
    https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#parsing-until-another-block-tag
    :param parser: template parser
    :param token: tag name and variables
    :return: HTML string
    """
    try:
        tag_name, fieldset_name = token.split_contents()
    except ValueError:
        raise template.TemplateSyntaxError("%r tag requires a single argument" % token.contents.split()[0])
    if not (fieldset_name[0] == fieldset_name[-1] and fieldset_name[0] in ('"', "'")):
        raise template.TemplateSyntaxError("%r tag's argument should be in quotes" % tag_name)
    nodelist = parser.parse(('endfieldset',))
    parser.delete_first_token()
    return FieldsetNode(nodelist, fieldset_name[1:-1])

templates/myapp/fieldset.html:

<div class="fieldset panel panel-default">
    <div class="panel-heading">{{ name }}</div>
    <div class="panel-body">{{ var }}</div>
</div>

templates/myapp/myform.html:

<form action="{% url 'myapp:myurl' %}" method="post">
    {% csrf_token %}
    {% fieldset 'General' %}
        {{form.myfield1 }}
    {% endfieldset %}
    {# my submit button #}
</form>

Not the answer you're looking for? Browse other questions tagged or ask your own question.