w8eight
w8eight

Reputation: 623

Form does not validate. csrf_token included

I have following issue with validate_on_submit() method in my Flask application.

Quick overview what I have:

My form class is created dynamically based on input:

class EditUsers(FlaskForm):

    submit = SubmitField('Submit')

def form_builder(roles_list):

    class EditUsersForm(EditUsers):
        pass

    for role in roles_list:
        if isinstance(role, tuple) and len(role) == 5:
            if not role[2] and role[3]:
                setattr(EditUsersForm, f'{role[0]}_{role[4]}_bool', BooleanField(label=role[0]))
                setattr(EditUsersForm, f'{role[0]}_{role[4]}_date', SelectField('Expiration Period', choices=choices))
        else:
            raise IncorrectType

IncorrectType is custom Exception i prepared and choices are created using datetime in same file (This is irrelevant so I am not including it in code).

My route in flask app (simplified):

#### EDIT: I pasted the wrong route, POST and GET Methods are included###
@edit_users.route('/users', methods=['GET', 'POST'])

def users():
...  # Some code there
form = form_builder(roles_to_form)

print(form.is_submitted())
print(form.validate())
print(form.validate_on_submit())

return render_template('edit_user.html',
                       data_dict=data_dict,  # in data_dict i pass form fields names and some other data
                       form=form,
                       )

My template:

<!-- Some usual stuff goes there css js etc -->

<div >
    <form class="form form-horizontal" method="post" role="form" style="margin: auto; text-align: center; width: 40%;">
        {{ form.csrf_token() }}
        <table class="table" align="center">
            <thead>
                <th>Role</th>
                <th>Expiration Date</th>
                <th>Give Role?</th>
            </thead>
            {% for field in data_dict['id_list'] %}
            <tr>
                <td align="left">{{ field[2] }}</td>
                <td align="left">
                    {{ form[field[1]] }}
                </td>
                <td align="left">{{ form[field[0]] }}</td>
            </tr>
            {% endfor %}
            <tr>
                <td colspan="3" align="center">{{ form.submit() }}</td>
            </tr>
        </table>
    </form>
</div>

What is my issue?

When I am hitting my Submit button, is_submitted() returns True but validate() does not, thus I cannot use typical if form.validated_on_submit() as it returns False even when I submit my form.

I dig a little and spot something unusual. I used protected members of form attributes, to check whatever validate() function sees as input:

for name in form._fields:
    print(name)
    inline = getattr(form.__class__, 'validate_%s' % name, None)
    print(inline)

The output (don't mind names of fields):

submit

None

Engineer_2_bool

None

Engineer_2_date

None

Operator_3_bool

None

Operator_3_date

None

Supervisor_4_bool

None

Supervisor_4_date

None

Guest_5_bool

None

Guest_5_date

None

csrf_token

None

I don't know why my form does not have validate_{name} attributes.

Upvotes: 0

Views: 81

Answers (2)

w8eight
w8eight

Reputation: 623

The problem was with type of data passed to choices of SelectField. I passed list of tuples with datetime.datetime to it. When changed type to str everything works smoothly.

Upvotes: 0

JBLaf
JBLaf

Reputation: 828

I made a working example from your code, and I ended up with the problem you described.

If I reversed well your code, it seems that form[field[1]] is your BooleanField or SelectField. So to render it in a template your have to use form[field[1]]() (can use render_field as well). So :

<tr>
    <td align="left">{{ field[2] }}</td>
    <td align="left">
        {{ form[field[1]] }}
    </td>
    <td align="left">{{ form[field[0]] }}</td>
</tr>

corrected into :

<tr>
    <td align="left">{{ field[2] }}</td>
    <td align="left">
        {{ form[field[1]]() }}
    </td>
    <td align="left">{{ form[field[0]]() }}</td>
</tr>

https://flask.palletsprojects.com/en/1.1.x/patterns/wtforms/#forms-in-templates

As you are using a FlaskForm, in your template you have to replace {{ form.csrf_token() }} by {{ form.csrf_token }} https://flask-wtf.readthedocs.io/en/stable/csrf.html#html-forms [EDIT Does not make any difference]

[Edited after w8eight's comment] The route is not authorized for POST (and the form makes a POST request as seen in <form class="form form-horizontal" method="post" [...]. So you have to change : @edit_users.route('/users', methods=['GET']) to @edit_users.route('/users', methods=['GET','POST'])

Upvotes: 1

Related Questions