2

I'm seeing the same strange behavior posted to Filling WTForms FormField FieldList with data results in HTML in fields where my raw fields are rendering with HTML rather than their default values. In that other example there's basically a one layer deep stacking of a FieldList over a single FormField. In my case I'm creating a 2D structure of a FieldList over a FieldList over a FormField. I can't figure out where I'm off here.

app.py

import os
from flask import Flask, redirect, render_template, request, send_file, url_for
from flask_wtf import FlaskForm
from flask_wtf.csrf import CSRFProtect
from wtforms import FieldList, FormField, RadioField,  TextAreaField, validators

app = Flask(__name__)
csrf = CSRFProtect(app)
SECRET_KEY = os.urandom(32)
app.config['SECRET_KEY'] = SECRET_KEY

#region FORMS
class TestCaseItem(FlaskForm) :
    pass_fail_radio = RadioField( '' , choices=[('Pass','Pass'), ('Fail','Fail')] ,  default='Pass' , validators=[validators.DataRequired()] )
    failure_message = TextAreaField(default='')

class TestCaseForm(FlaskForm) :
    test_items = FieldList( FormField( TestCaseItem ))

class ManualTestForm(FlaskForm):
    test_cases = FieldList( FormField(TestCaseForm))
#endregion

@app.route("/" , methods = ['POST', 'GET'])
def index():
    form = ManualTestForm()

    test_cases = ["test case {}".format(i) for i in range(5)]
    devices    = ["device {}".format(i) for i in range(3)]

    # Expand the field list for each test case
    for tc in test_cases :
        tcf = TestCaseForm()
        # expand its field list for each test device
        for device in devices :
            tci = TestCaseItem()
            tci.failure_message = 'abc'
            tcf.test_items.append_entry( tci )
        form.test_cases.append_entry( tcf )

    return render_template('test_template.html', form=form, test_cases=test_cases, devices=devices )

if __name__ == "__main__" :
    app.run(debug=True, port=5001) # http://127.0.0.1:5001

templates/test_template.html

<html>
<head>
</head>
<body>
  <h1>Manual Test Submission</h1>
  <h2>Test Suite</h2>
  <form  method="post">
    {{ form.csrf_token }}
    <!--TEST CASES-->
    <table>
      <tr>
        <th>Test Case ID</th>
        {% for test_item in form.test_cases[0].test_items %}
        {% set device = devices[loop.index0] %}
        <th>TC Status: {{device}}</th>
        <th>TC Input: {{device}}</th>{% endfor %}
      </tr>
      {% for test_case in form.test_cases %}
      {{test_case.hidden_tag()}}
      <tr>
        <td>{{ test_cases[ loop.index0 ]}}</td>
        {% for test_item in test_case.test_items %}
        <td>{{ test_item.pass_fail_radio }}</td>
        <td>{{ test_item.failure_message }}</td>{% endfor %}
      </tr>{% endfor %}
    </table>
  </form>
</body>
</html>

Nested Field List rendering HTML

jxramos
  • 7,356
  • 6
  • 57
  • 105
  • today4king's suggested fix on the GitHub issue works for me. Have you tried using `{{ field.data }}` in your template instead of just `{{ field }}`? – Hugo May 29 '19 at 17:11
  • Good catch @Hugo, replacing `{{ test_item.failure_message }}` with `{{ test_item.failure_message.data }}` in the template certainly did the trick. That's one variation I did not experiment with, all the discussion was focusing on doing or not doing `xxx.data = yyy` operations in the python source. Link to the corresponding GitHub [issue](https://github.com/wtforms/wtforms/issues/349) Go ahead and post an answer and I'll accept. – jxramos Jun 03 '19 at 23:01

1 Answers1

5

Set all the field values the usual way in your view, but in your template use field .data attributes for the fields belonging to nested forms:

{% for test_case in form.test_cases %}
  {{ test_case.hidden_tag() }}
  {{ test_cases[loop.index0] }}

  {% for test_item in test_case.test_items %}
    {{ test_item.pass_fail_radio.data }}
    {{ test_item.failure_message.data }}
  {% endfor %}

{% endfor %}

For what it's worth, there's another gotcha when dealing with nested FieldLists: if you construct the form in the logical way, the field id and name attributes won't be fully namespaced due to the way .append_entry() works. As a result, the expected values won't be POSTed and validation also breaks.

Broken:

form = RecipientsForm()

for proprietor in proprietors:
    proprietor_form = ProprietorForm()
    # Set proprietor name in hidden input field.
    proprietor_form.prop_name = proprietor['name']
    # populate and append addresses to proprietor form.
    for address in proprietor['addresses']:
        address_form = AddressForm()
        address_form.address = address['address']
        address_form.address_type = address['type']
        proprietor_form.addresses.append_entry(address_form)
    form.proprietors.append_entry(proprietor_form)

Works:

form = RecipientsForm()

proprietors = proprietor_api_call()
# Populate and append proprietors to Recipients form.
for idx, proprietor in enumerate(proprietors):
    proprietor_form = ProprietorForm()
    proprietor_form.prop_name = proprietor['name']
    form.proprietors.append_entry(proprietor_form)
    # Populate and append addresses to Proprietor form.
    for address in proprietor['addresses']:
        address_form = AddressForm()
        address_form.address = address['address']
        address_form.address_type = address['type']
        form.proprietors[idx].addresses.append_entry(address_form)
Hugo
  • 582
  • 4
  • 10
  • You know what's still mysterious too is that I did not alter the template expression for the pass_fail_radio element. Somehow that renders fine but not for the text element stuff. Maybe it is indeed still broken but has no means to communicate that state unlike the text fields. – jxramos Jun 04 '19 at 14:37
  • 1
    Inspect the element for your checkboxes, there's a good chance it contains HTML code inside the `value` attribute or something. – Hugo Jun 06 '19 at 08:57