Search code examples
pythonpython-3.xflaskflask-wtforms

how to access min_entries in Flask WTF, in another file?


please tell me how to access min_entries correctly, I need it to generate fields for the form.

My codes:

forms.py:

class ToSend(FlaskForm):
    send = FieldList(FormField(AddEquipment), min_entries=())

equipment_add.py:

@app.route('/equipment_add', methods=['GET', 'POST'])
def addEquipment():

    update = 0
    if request.method == "POST":
        update = int(request.form['update'])
        print(update)
        form = ToSend()
        form.send.min_entries = update

        return render_template('equipment_add.html', form=form, update=update)

    return render_template('equipment_add.html',  update=update)

And so I turn form.send.min_entries = update and there's a mistake:

    while len(self.entries) < self.min_entries:
TypeError: '<' not supported between instances of 'int' and 'tuple'

What am I doing wrong?


Solution

  • The problem arises because you didn't specify an integer as the value for the minimum number when defining the FieldList, but a pair of brackets.
    If you don't enter a value here, the default value of 0 will be used automatically.

    As I understand your code, you want to dynamically add form fields depending on the number requested by another form.
    You can solve your problem in different ways. I'll show you two options, using a GET request to query how many fields the form should contain.

    The first option is to add the fields manually using append_entry().

    from flask import (
        Flask, 
        render_template, 
        request
    )
    from flask_wtf import FlaskForm, Form
    from wtforms import FieldList, FormField, StringField, SubmitField
    
    app = Flask(__name__)
    app.secret_key = 'your secret here'
    
    class EquipmentForm(Form):
        name = StringField('Name')
    
    class AddEquipmentForm(FlaskForm):
        items = FieldList(FormField(EquipmentForm))
        submit = SubmitField('Add Items')
    
    @app.route('/equipment/add', methods=['GET', 'POST'])
    def equipment_add():
        num_fields = max(0, request.args.get('cnt', 0, type=int))
        form = AddEquipmentForm(request.form)
        form.items.min_entries = num_fields
        while len(form.items.entries) < form.items.min_entries:
            form.items.append_entry()
        if form.validate_on_submit():
            print(form.items.data)
        return render_template('equipment_add.html', **locals())
    

    The second option uses a factory to recreate the form for each request and sets the min_entries attribute of the FieldList.

    from flask import (
        Flask, 
        render_template, 
        request
    )
    from flask_wtf import FlaskForm, Form
    from wtforms import FieldList, FormField, StringField, SubmitField
    
    app = Flask(__name__)
    app.secret_key = 'your secret here'
    
    class EquipmentForm(Form):
        name = StringField('Name')
    
    class AddEquipmentForm(FlaskForm):
        submit = SubmitField('Add Items')
    
    @app.route('/equipment/add', methods=['GET', 'POST'])
    def equipment_add():
        def _factory(num_items):
            class F(AddEquipmentForm): pass
            setattr(F, 'items', FieldList(FormField(EquipmentForm), min_entries=num_items))
            return F
    
        num_fields = max(0, request.args.get('cnt', 0, type=int))
        form = _factory(num_fields)(request.form)
        if form.validate_on_submit():
            print(form.items.data)
        return render_template('equipment_add.html', **locals())
    

    The template would then look like this for both examples.

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title></title>
    </head>
    <body>
    
        <form>
            <label for="cnt">Add Item Count</label>
            <input type="number" name="cnt" value="{{ num_fields }}" min="0" />
            <input type="submit" />
        </form>
    
        <hr/>
    
        {% if form.items | count > 0 -%}
        <form method='post'>
            {{ form.csrf_token }}
            {% for subform in form.items -%}
                {% for field in subform -%}
                <div class="">
                    {{ field.label() }}
                    {{ field() }}
                    {% if field.errors -%}
                    <ul>
                        {% for error in field.errors -%}
                        <li>{{ error }}</li>
                        {% endfor -%}
                    </ul>
                    {% endif -%}
                </div>
                {% endfor -%}
            {% endfor -%}
            {{ form.submit() }}
        </form>
        {% endif -%}
    </body>
    </html>