简体   繁体   中英

Using a dictionary to insert WTForm fields. jinja2.exceptions.UndefinedError: 'wtforms.fields.core.UnboundField object' has no attribute 'label'

I am trying to populate a form in jinja2 using form fields that are in a dictionary.

#forms.py
class MyForm(FlaskForm):
    name = StringField('New Name', validators=[DataRequired()])

    fields = {}
    fields['Field1'] = StringField('Field 1', validators=[DataRequired()])
    fields['Field2'] = StringField('Field 2', validators=[DataRequired()])

#routes.py
@app.route('/test', methods=['GET', 'POST'])
def test():
    form = MyForm()
    return render_template('_test.html', form=form)

My python code is similar to the above code. If I try to insert the name field in jinja2 it works fine.

{{ form.name.label(class="form-control-label form-control-sm") }}

However, I don't know how to do the same for a field in the fields dictionary. If I use the following, it gives me an error.( jinja2.exceptions.UndefinedError: 'wtforms.fields.core.UnboundField object' has no attribute 'label' )

{{ form.fields['Field1'].label(class="form-control-label form-control-sm") }}

Is it possible to use a dictionary the way I tried to use it or is there an alternative if I have large amount of fields. My goal in using a dictionary was to use a jinja2 loop to iterate over the dictionary elements to insert all the fields without typing one by one.

Issue

WTForms doesn't support having Fields defined within a class level dictionary attribute. The Form base class uses FormMeta (from the same file) as a metaclass to identify unbound WTForm Fields in the class definition and bind them to the current Form and this only discovers class level attributes.

Solution

A minimal working example using a form factory function is below.

The extra form fields are passed in as a lambda function via the field_factory parameter so that we can delay the creation of the extra fields until after the name field is created (although any callable that returns a dict will work). This is needed as WTForms sorts the fields by creation order and not by the order they are supplied in the items dict inside the make_form() function.

You can then render them out by iterating over the form instead of specifying a manual order.

Tested on python 3.7.1.

from flask import Flask
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired

app = Flask(__name__)
app.config['SECRET_KEY'] = "secret"


def make_form(field_factory, name="MyForm"):
    items = dict(name=StringField('New Name', validators=[DataRequired()]),
                 **field_factory())
    my_form = type(name, (FlaskForm,), items)
    return my_form


if __name__ == "__main__":
    with app.test_request_context("/"):
        my_form = make_form(field_factory=lambda: dict(
            Field1=StringField('Field 1', validators=[DataRequired()]),
            Field2=StringField('Field 2', validators=[DataRequired()])
        ))

        form = my_form()
        for field in form:
            print(field())


Output

<input id="name" name="name" required type="text" value="">
<input id="Field1" name="Field1" required type="text" value="">
<input id="Field2" name="Field2" required type="text" value="">
<input id="csrf_token" name="csrf_token" type="hidden" value="IjY5ZWMyNWYxYzg3MzU2MTM1MGMyMTI0OTNiOGY1ZTk4OWFkZWU2Y2Qi.XMNLww.uvari0GZi4weboIecdtv9Vl8Jvg">

You can't do this. Fields need to be defined directly in the form itself.

But you don't need to. If you only want to iterate over the fields, you can just do that, as shown in the WTForms docs .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM