Flask WTForms always give false on validate_on_submit()

Question:

I have created a signup form using wtforms. I am using FormField in it so that I don’t have to repeat some of the elements of the form again. But whenever I click on the Submit button it always give me false on validate_on_submit method invocation. Not getting why is this happening.

My form.py is as follows:

class ProfileInfoForm(Form):
    firstname = TextField('firstname', validators=
                          [validators.Required("Please enter First name.")])
    lastname = TextField('lastname', validators=
                         [validators.Required("Please enter Last name.")])
    email = EmailField('email', validators=
                       [validators.Required("Please enter your valid email.")])
    gender = RadioField('gender', validators=
                        [validators.Required("Please select gender")],
                        choices=[('female', 'Female'), ('male', 'Male')])
    dob = TextField('dob', validators=
                    [validators.Required("Please select date of birth.")])
    languages = SelectMultipleField('languages', choices=[('', '')],
                                    validators=
                                    [validators.Required("Please select
                                                         atleast one 
                                                         language.")])


class RegistrationForm(Form):
    profilefield = FormField(ProfileInfoForm)
    password = PasswordField('password',
                             validators=
                             [validators.Required("Please enter password."),
                              validators.Length(min=8),
                              validators.EqualTo('confirm_password',
                                                 message='Password and confirm
                                                 password must match')])
    confirm_password = PasswordField('confirm_password',
                                     validators=
                                     [validators.Required("Please enter
                                                          confirm password.")])
    tnc = BooleanField('tnc', validators=
                       [validators.Required("Please select Terms and 
                                            Conditions")], default=False)

    submit = SubmitField('Create My Account')

Signup method is as follows:

@module.route('/signup', methods=['GET', 'POST'])
  @handle_error
  def signup():
      if hasattr(g, 'user') and g.user:
          # TODO: do some operations if needed else keep it blank
          return redirect(url_for('index'))
      else:
          signup_form = RegistrationForm()
          # Add choices for the user
          signup_form.profilefield.languages.choices = getLanguages()
          if signup_form.validate_on_submit():
              firstname = signup_form.profilefield.firstname.data
              lastname = signup_form.profilefield.lastname.data
              email = signup_form.profilefield.email.data
              password = signup_form.password.data
              #  confirm_password = signup_form.confirm_password.data
              gender = signup_form.profilefield.gender.data
              dob = signup_form.profilefield.dob.data
              languages = signup_form.profilefield.languages.data
              tnc = signup_form.tnc.data

              payload = {'firstname': firstname, 'lastname': lastname,
                         'email': email, 'password': password, 'gender': gender,
                         'dob': dob, 'languages': languages,
                         'tnc': ('1' if tnc else '0')}
              try:
                  buildApiUrl = BuildApiUrl()
                  response = requests.post(buildApiUrl.getUrl("user", "signup"),
                                           data=payload)

                  if response.status_code == requests.codes.ok:
                      data = json.loads(response.text)
                      if 'status' in data and data['status'] != 200:
                          flash(data['message'], category="error")
                      else:
                          flash(data['message'] +
                                ': Your account is created successfully! ' +
                                'Please login to your account!',
                                category="success")
                          return redirect(url_for('index'))
              except requests.exceptions.RequestException:
                  flash('Internal Server side error occured', category="error")
                  return redirect(url_for('server_error', e='500'))

      return render_template('public/index.html',
                             signup_form=signup_form, login_form=LoginForm())

HTML form is present on gist here

FYI: I am putting all the required fields with actual data needed. Still getting false when I call validate_on_submit(). What is wrong in my code?

EDIT: getLanguages is a method that retrieves languages from database and put in select list. This functionality is happening as expected and I can get list of languages.

Edit 2: Realize one thing here. This is happening due to FormField, since I tested by adding all the fields of ProfileInfoForm() into RegistrationForm() method, and everything worked just fine and I could signup. So some issue with the FormField or the way I am using it, but not sure where it is going wrong.

Found out that the problem is not with FormField but with my ProfileInfoForm(). It returns false always. Not yet got reason but I think I may have to write my own validation for that matter. Any thoughts?

Edit:

On dump I got following (used pprint here):

{'SECRET_KEY': '1e4c35233e50840483467e8d6cfe556c',
 '_errors': None,
 '_fields': {'csrf_token': <wtforms.ext.csrf.fields.CSRFTokenField object at 0x2207290>,
             'dob': <wtforms.fields.simple.TextField object at 0x2207650>,
             'email': <flask_wtf.html5.EmailField object at 0x22074d0>,
             'firstname': <wtforms.fields.simple.TextField object at 0x2207350>,
             'gender': <wtforms.fields.core.RadioField object at 0x2207590>,
             'languages': <wtforms.fields.core.SelectMultipleField object at 0x2207710>,
             'lastname': <wtforms.fields.simple.TextField object at 0x2207410>},
 '_prefix': u'profilefield-',
 'csrf_enabled': True,
 'csrf_token': <wtforms.ext.csrf.fields.CSRFTokenField object at 0x2207290>,
 'dob': <wtforms.fields.simple.TextField object at 0x2207650>,
 'email': <flask_wtf.html5.EmailField object at 0x22074d0>,
 'firstname': <wtforms.fields.simple.TextField object at 0x2207350>,
 'gender': <wtforms.fields.core.RadioField object at 0x2207590>,
 'languages': <wtforms.fields.core.SelectMultipleField object at 0x2207710>,
 'lastname': <wtforms.fields.simple.TextField object at 0x2207410>}

Edit:

I dig little bit and found that the error is generated is due to csrf token missing. But I have included {{ signup_form.hidden_tag() }} in my form template in html. and I can see hidden tag in html generated when I do inspect element and can see csrf_token field with hash value. So what is wrong in here?

Asked By: Rahul Shelke

||

Answers:

I solved my problem with the following function:

def __init__(self, *args, **kwargs):
    kwargs['csrf_enabled'] = False
    super(ProfileInfoForm, self).__init__(*args, **kwargs)

I added this function in ProfileInfoForm()

The issue was FormField includes csrf_token field as well as Actual form, i.e., RegistrationForm was also including csrf_token, so there were two csrf_token which were to be verified and only one was getting rendered actually in form. So, I disabled csrf_token in ProfileInfoForm so when FormField rendered it, it had csrf_token = False.

And RegistrationForm does have csrf_token enabled still now so the form is still safe.

My Guess is this does also required to be done in FormField as well.

FYI: This solution might be wrong due to my interpretation of the FormField code. SO please correct me if I am wrong in above solution.

Answered By: Rahul Shelke

I had the same issue and I was able to fix it.

The problem was related to the fact that the LoginForm had the id and username with a validators while the html form was not requiring the information

            <h1>Login</h1>

            <form action="" method="POST"  name="login">
        {{ login_form.csrf_token }}
        {{ login_form.hidden_tag() }}

        <p>
            {{ login_form.email.label }}<br>
            {{ login_form.email(size=64) }}<br>
            {% for error in login_form.email.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>
            {{ login_form.password.label }}<br>
            {{ login_form.password(size=32) }}<br>
            {% for error in login_form.password.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>{{ login_form.remember_me }} Remember Me</p>
{#            <input type="submit" value="Sign In">#}
        <p>{{ login_form.submit() }}</p>
    </form>



class LoginForm(FlaskForm):
    ***# user_id = StringField('user_id',validators=[DataRequired()])
    # user_name = StringField('user_name',validators=[DataRequired(), Length(min=3, max=20)])***
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    remember_me = BooleanField('remember_me', default=False)
    submit = SubmitField('LogIn')
Answered By: Stefano Tuveri

Just print csrf_token with jinja and it will return True.

Answered By: Orbytoo