0
votes

I (mostly) followed Per Wagner Nielsen's excellent tutorial, but then tried (and failed) to add a boolean field to the form. Every user created with the Signup form seems to have the boolean value set to True even though the default is False and the checkbox is unchecked on form submit. I test that with the line in the login controller if user.is_admin(), and the controller does return user is admin . I'm showing all the code because I really don't see where my error lies -- sorry if that's too much. Any help is greatly appreciated.

app.py

from flask import Flask, reque,st, render_template, redirect, url_for, flash, g
from forms import SignupForm, LoginForm
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, login_user, login_required, logout_user
from flask.ext.login import current_user

app = Flask(__name__)
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)

login_manager = LoginManager()
login_manager.init_app(app)

######################################################################
# Models
######################################################################
class User(db.Model):
    email = db.Column(db.String(80), primary_key=True, unique=True)
    password = db.Column(db.String(80))
    is_admin = db.Column(db.Boolean, default=False)

    def __init__(self, email, password, is_admin):
        self.email = email
        self.password = password
        self.is_admin = is_admin

    def is_authenticated(self):
        return True

    def is_active(self):
        return True

    def is_anonymous(self):
        return False

    def get_id(self):
        return str(self.email)

    def is_admin(self):
        if self.is_admin:
            return True
        else:
            return False

    def __repr__(self):
        return '<User %r>' % self.email

######################################################################
@login_manager.user_loader
def load_user(email):
    return User.query.filter_by(email = email).first()

@app.before_request
def before_request():
    g.user = current_user

######################################################################
# Routes
######################################################################
@app.route('/')
def index():
    return "Welcome to Flask"

@app.route('/signup', methods=['GET', 'POST'])
def signup():
    form = SignupForm()
    if request.method == 'GET':
        return render_template('signup.html', form = form)
    elif request.method == 'POST':
        if form.validate_on_submit():
            if User.query.filter_by(email=form.email.data).first():
                return "Email address already exists"
            else:
                newuser = User(form.email.data, form.password.data, form.is_admin.data)
                db.session.add(newuser)
                db.session.commit()
                return "User created"
        else:
            #return "Form didn't validate"
            return render_template('signup.html', form=form)

@app.route('/login', methods=['GET','POST'])
def login():
    form = LoginForm()
    if request.method == 'GET':
        return render_template('login.html', form=form)
    elif request.method == 'POST':
        if form.validate_on_submit():
            user=User.query.filter_by(email=form.email.data).first()
            if user:
                if user.password == form.password.data:
                    login_user(user)
                    #return "User logged in"
                    if user.is_admin():
                        return "user is admin"
                    else:
                        return "user is not admin"
                else:
                    return "Wrong password"
            else:
                return "user doesn't exist"
    else:
        return "form not validated"

@app.route("/logout")
@login_required
def logout():
    logout_user()
    return "Logged out"

@app.route('/protected')
@login_required
def protected():
    return "protected area, user: " + current_user.email + ", " + str(g.user.is_admin)
######################################################################

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80, debug=True)

forms.py

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, validators, BooleanField
from wtforms.validators import Email, DataRequired


class SignupForm(FlaskForm):
    email = StringField('Email',
                [DataRequired(),Email()])
    password = PasswordField(
                'Password',
                [DataRequired()])
    is_admin = BooleanField('Is an Admin')
    submit = SubmitField("Sign In")


class LoginForm(FlaskForm):
    email = StringField('Email',
                [DataRequired(),Email()])
    password = PasswordField(
                'Password',
                [DataRequired()])
    submit = SubmitField("Sign In")

templates/signup.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Access</title>
</head>
<body>
    <h1>Sign-up</h1>
    <form method="POST" action="/signup">
        {{ form.hidden_tag() }}
        {{ form.email.label }} {{ form.email(size=20) }}<br>
        {{ form.password.label }} {{ form.password(size=20) }}<br>
        {{ form.is_admin.label }} {{ form.is_admin() }}<br>

        <input type="submit" value="Signup">
    </form>

<h1>Form Errors</h1>
{{ form.errors }}
</body>
</html>

templates/login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form method="POST" action="/login">
        {{ form.hidden_tag() }}
        {{ form.email.label }} {{ form.email(size=20) }}
        {{ form.password.label }} {{ form.password(size=20) }}
        <input type="submit" value="login">
    </form>
</body>
</html>
1

1 Answers

2
votes

The problem is coming from the fact that you have a column is_admin and a function called is_admin() in you model class. In Python, if you want to use the same name for a class variable and a class method, you better know what you are doing (but don't).

To avoid that simply, you coud rename your function is_admin() to is_administrator().

But if you think about it, evaluating directly the field in your view function, without having to declare a class method would be even easier:

if user.is_admin:
    return "user is admin"
else:
    return "user is not admin"

I would also recommend to have look into SQL Expression and Mapped Attributes to have a better understanding of what you are doing with all those class methods in your model.