Thiru
Thiru

Reputation: 3363

Django: how to change label using formset extra?

I use formset to produce extra fields, but i don't know how to change the label for extra field produced by formset.

My code:

class GetMachine(forms.Form):
    Number_of_Lines = forms.IntegerField(max_value=4)

class GetLine(forms.Form):
    beamline_name = forms.CharField(max_length=15, label='Name of Beamline-%i')

def install(request):
    MachineFormSet = formset_factory(GetMachine, extra=1)
    formset = MachineFormSet()
    if request.method == 'POST':
#        formset = MachineFormSet(request.POST) 
#        if formset.is_valid(): # All validation rules pass
        line_no = request.POST['form-0-Number_of_Lines']
        GetLineFormSet = formset_factory(GetLine, extra=int(line_no))
        formset = GetLineFormSet()
        return render_to_response('install.html', { 'formset': formset, 'action': 'step1'})
    return render_to_response('install.html', { 'formset': formset, })    

install.html template:

{% for form in formset.forms %}
{% for field in form %}
    <tr>
        <td>{{ field.label_tag }}</td>  <td>{{ field }}</td><td>{{ field.errors }}</td>
    </tr>
{% endfor %}
{% endfor %}

For example if the "Number_of_Lines" = 2, then i expect the next form with the labels,

Name of Beamline-1:
Name of Beamline-2:

Upvotes: 4

Views: 3859

Answers (2)

stellarchariot
stellarchariot

Reputation: 2932

I'm assuming you want the result of the first form to determine the number of fields and their labels of the second, you might want to look into Django form wizards. But here's a simple, non-form-wizard (and probably less ideal/maintainable) way to do it, utilising the __init__ method of the formset to modify the form labels*:


forms.py:

# File: forms.py
from django import forms
from django.forms.formsets import BaseFormSet


# What you've called 'GetMachine'
class MachineForm(forms.Form):
    no_of_lines = forms.IntegerField(max_value=4)


# What you've called 'GetLine'
class LineForm(forms.Form):
    beamline_name = forms.CharField(max_length=15, label='Name of Beamline')


# Create a custom formset and override __init__
class BaseLineFormSet(BaseFormSet):
    def __init__(self, *args, **kwargs):
        super(BaseLineFormSet, self).__init__(*args, **kwargs)
        no_of_forms = len(self)
        for i in range(0, no_of_forms):
            self[i].fields['beamline_name'].label += "-%d" % (i + 1)

views.py:

# File: views.py
from django.forms.formsets import formset_factory
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from forms import MachineForm, LineForm, BaseLineFormSet


def get_no_of_lines(request):
    if request.method == 'POST':
        machine_form = MachineForm(request.POST)
        if machine_form.is_valid():
            # At this point, form fields have already been 
            # converted to Python data types :)
            # so no need to convert `line_no` to an integer
            no_of_lines = machine_form.cleaned_data['no_of_lines']
            return HttpResponseRedirect(reverse('line_form', kwargs={'no_of_lines': no_of_lines}))
    else:
        # It looks to me like you probably don't mean to
        # use formsets here (but a form instead)
        machine_form = MachineForm()

    c = RequestContext(request, {
        'machine_form': machine_form,
    })
    return render_to_response('get_no_of_lines.html', c)


def line_form(request, no_of_lines):
    # You probably should validate this number (again).
    # In fact, you probably need to validate first form (MachineForm).
    # ...But I'm assuming it'll be valid in this example.
    no_of_lines = int(no_of_lines)
    LineFormSet = formset_factory(LineForm, extra=no_of_lines, formset=BaseLineFormSet)
    if request.method == "POST":
        formset = LineFormSet(request.POST, request.FILES)
        if formset.is_valid():
            pass
            # Do stuff with form submission
            # Redirect

    else:
        formset = LineFormSet()

    c = RequestContext(request, {
        'formset': formset,
    })
    return render_to_response('line_form.html', c)

urls.py:

from django.conf.urls import url, patterns
from views import get_no_of_lines, line_form


urlpatterns = patterns('',
     url(r'^$', get_no_of_lines, name='get_no_of_lines'),
     url(r'^line_form/(?P<no_of_lines>\d{1})$', line_form, name='line_form'),
)

get_no_of_lines.html:

<form method="POST">
{% csrf_token %}
{{ machine_form }}
</form>

line_form.html:

<form method="POST">
{% csrf_token %}
{{ formset.as_p }}

The reason why I say this approach is probably not the best way to do this is because you have to validate no_of_lines being passed to line_form view (which could be > 4, so you'll have to perform validation here and introduce validation logic rather than having it one place — the form). And if you need to add a new field to the first form, you'll likely end up having to modify the code. So hence why I'd recommend looking into form wizards.


Upvotes: 5

Jonas Geiregat
Jonas Geiregat

Reputation: 5432

The only way, I can think of , to accomplish this behavior would be by adjusting the FormSet in your view right before it is passed to your template.

You can iterate over the different forms and labels and change their value accordingly.

Another possible solution would be to set the default label to "Name of Beamline-". And in your template do something like

{% for field in form %}
    <td>{{ field.label_tag }}{{ forloop.counter1 }}</td> 
{% endfor %}

Upvotes: 0

Related Questions