1

I want to create a form with fields for each of the last 10 years. So for 2020-2011 I want, say, an IntegerField for each year with the variable names year_2020, year_2019, year_ 2018, ... It'd also be nice if they had the appropriate labels too, i.e. 2020, 2019...

I could do this by writing something out for each year individually but I thought it'd be nicer and more efficient (?) to generate them using a for loop. Is this possible?

I saw this question about generating fields in the template using a for loop but I'm wondering how to generate fields in the python form class itself (not sure what to call it; please excuse my ignorance). I've seen this question but I can't seem to get it to work. I'm also not fond of that solution since it doesn't give the fields descriptive names like year_2020.

This is my code so far; apologies for any errors.

The python:

forms.py
    class YearForm(FlaskForm):
        year = IntegerField(validators=[NumberRange(min=0,max=100000,message='Please enter an integer above 0.'),
                            InputRequired(message='Please enter a value.')])
    
    class RentForm(FlaskForm):
        years = FieldList(FormField(YearForm), min_entries=10)

The template:

form.html
    for year in rentForm.years:
        <p>{{ year.label }}: {{ year(size=32) }}
            {% for error in year.errors %}
                <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>

It looks like I'm accessing the fields incorrectly in the template. What is the correct way? And how can I give the fields more descriptive labels and variable names?

Any help would be greatly appreciated; thanks!

EDIT: if this could be more easily done in another language please let me know. I just started learning Flask/Python and am not married to either.

2 Answers 2

1

I am doing something similar. Perhaps my example will help. I am extending the form, in your case RentForm.years in my Flask handler in Python, NOT in my template. Here is my form...

class TemplateFormRow(FlaskForm):
    col = StringField(label='Column Name')
    data_type = SelectField(label='Data Type',
                            choices=[("boolean", "boolean"), ("datetime", "datetime"),
                                     ("integer", "integer"), ("decimal", "decimal"), ("string", "string")])
    sequence = HiddenField()
    delete = SubmitField(label='Delete')


class TemplateForm(FlaskForm):
    rows = FieldList(unbound_field=FormField(TemplateFormRow))
    add_row = SubmitField(label='Add Row', render_kw={'class': "btn btn-primary"})
    confirm = SubmitField(label='Save', render_kw={'class': "btn btn-primary"})
    btn_cancel = SubmitField(label='Cancel', render_kw={'formnovalidate': True, 'class': "btn btn-primary"})

Notice that in my case I put, on the parent form, a button that allows the user to add another row. You'd handle it a bit differently if you always want 10 rows.

Here is part of the Python code that works with this form. The append_entry line is particularly important to you...

if request.method == 'POST':

if form.btn_cancel.data:
    return redirect(url_for('admin'))

if form.add_row.data:  # User pressed the add row button
    form.rows.append_entry()

and here is the Python code that renders the template...

return render_template(template_name_or_list='generic_entry_page.html', page_hdr='Template',
                       show_form=form, form_msg=msg)

Finally, here is the part where my template processes this...

                {% for element in show_form %}
                    {% if element is iterable %}  {# This is a vertical set of forms #}
                        <table>
                            <tr>
                                {% for field in element[0] %}
                                    {#                                      {% for field in element.rows[0] %}#}
                                    {% if field.type != 'HiddenField' and field.label.text != 'CSRF Token' %}
                                        <th>{{ field.label }}</th>
                                    {% else %}
                                        <th>{{ field }}</th>
                                    {% endif %}
                                {% endfor %}
                            </tr>
                            {% for row in element %}
                                <tr>
                                    {% for field in row %}
                                        <td>
                                            {% if field.type == 'SubmitField' %}
                                                <button {{ field }} {{ field.label.text }} </button>
                                            {% else %}
                                                {{ field }}
                                            {% endif %}
                                        </td>
                                    {% endfor %}
                                </tr>
                            {% endfor %}
                        </table>
                        <br>

The resultant screen looks like this... Hit add row button to add a row

Does this help?

Sign up to request clarification or add additional context in comments.

4 Comments

Hey thanks for the answer! I tried to copy+paste your code and make it work but couldn't. I think I had to close a for loop and an if statement but I still get a blank screen. Can you explain what show_form is? That might help me understand what's going wrong.
Sure! I just edited my answer to add the part where I render my template. You'll see that show_form is just a variable that I send to the template. It contains the actual form that I want to show on the screen (hence the name show_form). Does that help clear it up? Feel free to ask for clarification, I know it's a touch complex.
That does clear things up, but it doesn't seem to have changed anything. In routes.py I have testForm = TestForm() ... return render_template('test.html', title='Test Form', show_form=testForm) In forms.py I have class TemplateFormRow(FlaskForm): (everything you wrote) class TestForm(FlaskForm): (everything you wrote, but with a different class name) In test.html (the template) I have the code you put up. I only closed a for loop and an if statement.... but it still renders as a blank page. I'm stumped.
Looks like you got something to work. Congrats! You should mark your answer as the correct one.
1

Okay I was able to make a satisfactory toy example work using the information from this question.

The python:

class ImageForm(FlaskForm):
    frequency = SelectField(choices=[('monthly', 'Monthly'),('weekly', 'Weekly')])
    caption = StringField('Caption')
    credit = StringField('Credit')

class TestForm(FlaskForm):
    images = FieldList(FormField(ImageForm), min_entries=10)

The template:

<form action="" method="post">

    {{ testForm.hidden_tag() }}

    <table>
    {% for image in testForm.images %}
        <tr>
            <td> {{ image['frequency'] }} </td>
            <td> {{ image['caption'] }} </td>
            <td> {{ image['credit'] }} </td>
        </tr>
    {% endfor %}
    <table>
    
</form>

The result: Rendered HTML (ignore all the tabs; I have a problem)

I think this should suit my needs nicely.

PS: And you can access individual fields like so:

testForm.images[0]['frequency']

For the first dropdown menu.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.