BrotherJack
BrotherJack

Reputation: 329

Multiple WTForms with Same Radio Field on Same Page

OK, I've looked around online, in the documentation, and in the source code and I'm not getting anywhere on this problem. So I'm working on a Flask project with wtforms and bootstrap where I need to display the same form multiple times on a page. Specifically, this form:

class ProblemForm(Form):
    def __init__(self, formdata=None, obj=None, prefix='', title="", **kwargs):
        super(self.__class__, self).__init__(formdata=None, obj=None, prefix='', **kwargs)
        self.title = title

    opts = RadioField("Possible Corrections")
    alias_of = StringField("Alias of")
    select_alias = BooleanField("select_alias")
    user_input = StringField("New Entry")
    select_uinput = BooleanField("select_uinput")

In the controller logic (see bellow) I found that I could just create an array of ProblemForm instances, with the choices set for each "Problem" from data already available in the session.

@blueprint.route("/clean/review/<int:start>-<int:end>", methods=["GET", "POST"])
@login_required
def cleaning(start, end):
    prob_forms = []
    for pif in xrange(start, end+1):
        pf = ProblemForm(request.form, title=session["problems"][pif][0])
        pf.opts.choices = [(pr, pr) for pr in session["problems"][pif][1]]+[('<null>', '<null>')]
        prob_forms.append(pf)
    if request.method == "POST":        
        pass
    return render_template("main/cleaning.html", forms=prob_forms, buttons=ControlButtons(), 
                           start=start, end=end)

Problem Form example

The frustrating thing is that this almost works. When the form is posted the request.form.lists() contains: [('select_uinput', [u'7']), ('user_input', [u'', u'', u'', u'', u'', u'', u'', u'']), ('select_alias', [u'2']), ('alias_of', [u'', u'Dirty Jobs with Mike Rowe', u'', u'', u'', u'', u'', u'']), ('opts', [u'Paradise']), ('next', [u'Next'])]. I can work with this; one of the checkboxes was selected on the 7th form, the other was selected on the 2nd form, on the second form the user typed 'Dirty Jobs with Mike Rowe' in the "alias_of" box, etc.

The problem is the RadioField named "opts". It seems to only save the last button the user pressed. Anyone know why this might be, or where I should look for more information?

Here is the template, if that is of any use:

{% extends "layout.html" %}
{% block content %}
    <form action="{{ url_for('main.cleaning', start=start, end=end) }}" method="POST" enctype="multipart/form-data">
        {% for form in forms %}
        {% set outloop = loop %}
            <div class="well well-lg">
                <h3><em>{{ form.title }}</em>?</h3>
                <!-- Possible Master List entries -->
                <div class="btn-group" id="opts_{{outloop.index}}" role="group" data-toggle="buttons" aria-label="...">   
                   {% for opt in form.opts %}
                        {% if loop.first %}
                            <label class="btn btn-default active">                        
                        {% else %}
                            <label class="btn btn-default">
                        {% endif %}
                            {{ opt }} {{ opt.label.text }}
                        </label>
                   {% endfor %}
                </div><br />

                <!-- Alias input -->
                <div class="row">
                  <div class="col-lg-6">
                    <div class="input-group">
                      <span class="input-group-addon">
                        {{ form.select_alias(value=outloop.index) }} 
                      </span>
                      <span class="input-group-addon" id="basic-addon2">Alias of</span>
                      {{ form.alias_of(class_="form-control") }}
                    </div><!-- /input-group -->
                  </div><!-- /.col-lg-6 -->
                 </div> <!-- /row -->
                 <!-- END Alias input -->

                <!-- New Entry input -->
                <div class="row">
                  <div class="col-lg-6">
                    <div class="input-group">
                      <span class="input-group-addon">
                        {{ form.select_uinput(value=outloop.index) }} 
                      </span>
                      <span class="input-group-addon" id="basic-addon2">New Entry</span>
                      {{ form.user_input(placeholder=form.title, class_="form-control") }}
                    </div><!-- /input-group -->
                  </div><!-- /.col-lg-6 -->
                 </div> <!-- /row -->
                </div>
                <!-- END Alias input -->
        {% endfor %}
        {{ buttons.back() }} {{ buttons.next() }} {{ buttons.save() }}
    </form>
{% endblock %}

Upvotes: 1

Views: 1872

Answers (1)

Crast
Crast

Reputation: 16336

Option 1: Give each form instance its own prefix:

for pif in xrange(start, end+1):
    pf = ProblemForm(request.form, title=session["problems"][pif][0], prefix='problem-%d' % pif)
    # etc

Option 2: Alternately, WTForms can generate prefixed forms for you if you come up with an enclosing form using a FieldList of FormFields.

class EnclosingForm(Form):
    problems = FieldList(FormField(ProblemForm))

Then use append_entry to add subforms in your loop instead:

form = EnclosingForm(request.form)
for i, pif in enumerate(xrange(start, end+1)):
    if len(form.problems.entries) <= i:
        form.problems.append_entry({'title': session["problems"][pif][0]})
    # etc

Upvotes: 2

Related Questions