7

I'm receiving user registration data from an iOS application and I'd like to use the validators that come with wtforms to make sure the email and password are acceptable. However, I'm not using a flask form since users are inputting the data from iOS textfields. Is it possible to check the incoming JSON data with the wtforms validators?

@auth.route('/register', methods=['POST'])
def register():
    try:
        user = User.register_fromJSON(request.json)

        email_success = validate_email(user)
        username_success = validate_username(user)

        if email_success == 1 and username_success == 1:
            db.session.add(user)
            db.session.commit()
            return jsonify({'Success': 1})
        else:
            return jsonify({'Failure': 0})

    except Exception:
        return jsonify({'Failure': 0})

def validate_email(user):
    if User.query.filter_by(email=user.email).first() == None:
        return 1
    else:
        return 0

def validate_username(user):
    if User.query.filter_by(username=user.username).first() == None:
        return 1
    else:
        return 0

EDIT

I created a Registration form:

class RegistrationForm(Form):
    email = StringField('Email', validators=[Required(), Length(1,64), Email()])
    username = StringField('Username', validators=[Required(), Length(1, 64), Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0, 'Usernames must have only letters, 'numbers, dots or underscores')])
    password = PasswordField('Password', validators=[Required()])


    def validate_email(self, field):
        if User.query.filter_by(email=field.data).first():
            print("Email already registered.")
            raise ValidationError('Email already registered.')

    def validate_username(self, field):
        if User.query.filter_by(username=field.data).first():
            print("Username already in use.")
            raise ValidationError('Username already in use.')

registration function has been updated to:

@auth.route('/register', methods=['POST'])
def register():
    try:
        data = MultiDict(mapping=request.json)
        form = RegistrationForm(data)

        if form.validate():
            user = User.register_fromJSON(request.json)
            db.session.add(user)
            db.session.commit()
            return jsonify({'Success': 1})
        else:
            return jsonify({'Success': 2})

    except Exception:
        return jsonify({'Success': 3})
Brosef
  • 2,945
  • 6
  • 34
  • 69
  • Absolutely. I'm actually writing a library that does exactly that. On my phone, but I'll post an answer when I'm back on a keyboard – nathancahill Aug 18 '15 at 02:27

2 Answers2

9

Yes, this is entirely possible - the wtforms.Form constructor takes any MultiDict like interface (it just needs to have getlist), so you can just create an instance of werkzeug.datastructures.MultiDict from your JSON:

data = MultiDict(mapping=request.json)
form = YourForm(data)
if form.validate():
    # Data is correct

(assuming the field names match) and things will just work.

Sean Vieira
  • 155,703
  • 32
  • 311
  • 293
  • 1
    So are you essentially making a fake form that the JSON data gets assigned to and is then validated? – Brosef Aug 18 '15 at 19:49
  • Yes, effectively. I'm building a dictionary-like object from the parsed dictionary of `request.json`, using the underlying data structure that Flask / Werkzeug uses for `request.form` - so WTForms processes it. WTForms doesn't care whether the data was parsed from a HTML form POST or from an INI file - it just needs to be provided in a data structure that quacks like a `MultiDict`. – Sean Vieira Aug 18 '15 at 20:13
  • I can't seem to get the form to validate. Am I missing something? I can print the form data by doing form.email.data and it all comes out right. – Brosef Aug 19 '15 at 00:10
  • If you are using Flask-WTF make sure you have disabled CSRF protection otherwise the form will never validate :-) – Sean Vieira Aug 19 '15 at 02:01
  • That was it! Thank you! I'm still a bit confused on whats happening with MultiDict line. Why couldn't i use a regular dictionary? I'm somewhat new to python/flask and I'm not too familiar with the whole mapping thing. – Brosef Aug 19 '15 at 03:07
  • That's a limitation of WTForms, not of Python or Flask - WTForms wants an interface that is dictionary-like, but allows you to store multiple values for one key (because that's the way HTTP GET and POST requests work - `/some.url?key=value-1&key=value-2` is a perfectly licit URL). `MultiDict` provides the necessary method (`getlist`) while a normal `dict` does not. – Sean Vieira Aug 19 '15 at 04:49
3

Here's a little utility called Flask-Inputs that I'm working on to solve this. The goal is to allow all incoming data (forms, queries, headers, etc) to be validated with wtform validators.

Here's how validation would work with your data:

from flask_inputs import Inputs
from wtforms.validators import Length, Email, ValidationError


class RegisterInputs(Inputs):
    json = {
        'email': [Email(), unique_email],
        'username': [Length(min=3, max=15), unique_username]
    }

def unique_email(form, field):
    if User.query.filter_by(email=field.data).first():
        raise ValidationError('Email is already registered.')

def unique_username(form, field):
    if User.query.filter_by(username=field.data).first():
        raise ValidationError('Username is already registered.')


@auth.route('/register', methods=['POST'])
def register():
    inputs = RegisterInputs(request)

    if inputs.validate():
        user = User.register_fromJSON(request.json)

        db.session.add(user)
        db.session.commit()

        return jsonify(success=1)
    else:
        return jsonify(failure=0, errors=inputs.errors)
nathancahill
  • 10,452
  • 9
  • 51
  • 91
  • So under `register()` the request is sent as a parameter to `RegisterInputs()`. Now, is `inputs` just a dictionary that holds the key and values for email and username? Isn't this all happening before the data is converted from JSON since everything is happening before `user = User.register_fromJSON(request.json)`? – Brosef Aug 18 '15 at 20:16
  • inputs is like a form instance in wtforms. You can call inputs.validate() to check if the json is valid – nathancahill Aug 18 '15 at 20:21
  • Sorry if I'm being nitpicky, but I thought the JSON data needs to be parsed first, then inputted into the form instance. Is my logic wrong? Your answer looks like the form instance is being filled with the straight JSON data. – Brosef Aug 18 '15 at 22:46
  • No, request.json is already a parsed json object. But you can follow Sean's anwser to do it manually like that. The Flask-Inputs library is just something I'm writing to make it easier. – nathancahill Aug 18 '15 at 23:16