I have a flask app deployed on GAE, and am having a problem where the current_user.is_authenticated boolean returns inconsistent results on the deployed version. It works consistently on a local server, but on GAE, after logging in, while the rest of the app behaves correctly, displaying @login_required routes, the navbar, which relies on the current_user to display different menus for logged in users and non-logged in users, sometimes shows the correct menus and sometimes the incorrect. Repeated refreshes will switch inconsistently between the logged in view and the logged out.
I have tried adding the --preload tag to the gunicorn call in the app.yaml, but it has had no effect.
My init.py invokes the flask-login manager:
login_manager = LoginManager()
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id):
return User.query.filter_by(id=user_id).first()
My base layout creates the navbar:
{% if current_user.is_authenticated %}
<li class="nav-item active ml-3">
<span class="nav-link">User: <a href="/">{{ current_user.email }}</a></span></li>
<li class="nav-item ml-3">
<a class="btn btn-outline-secondary btn-sm mt-1 reg" href="{{ url_for('home.logout')}}">Logout <span class="sr-only">(current)</span></a></li>
{% else %}
<li class="nav-item active">
<a class="nav-link" href="/">Register <span class="sr only">(current)</span></a></li>
<li class="nav-item active">
<a class="nav-link" href="{{ url_for('home.login') }}">Login <span class="sr-only">(current)</span></a></li>
{% endif %}
app.yaml calls guinicorn:
runtime: python #for some reason this needs to be python-compat if you run with dev_appserver
env: flex
threadsafe: true
entrypoint: gunicorn -b :$PORT wsgi:app --preload --timeout 120
runtime_config:
python_version: 3
handlers:
- url: /.*
script: main.app
secure: always
I'm really stumped by this, anyone have any ideas?
EDIT --- Here is the User model:
class User(BaseModel, UserMixin):
__tablename__ = "user"
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name_last = db.Column(db.String, nullable=True)
name_first = db.Column(db.String, nullable=True)
email = db.Column(db.String, nullable=True)
is_admin = db.Column(db.Boolean, default=False)
opt_out = db.Column(db.Boolean, default=False)
print_opt_in = db.Column(db.Boolean, default=False)
date_created = db.Column(db.DateTime(), server_default=func.now())
date_modified = db.Column(db.DateTime(), onupdate=func.now())
organizations = db.relationship('Organization', secondary=user_organizations, lazy='subquery',
backref=db.backref('users', lazy=True))
code = db.Column(db.String, nullable=True)
# need this to make many-to-many name show up in flask-admin
def __str__(self):
return self.name_last + ', ' + self.name_first
def __repr__(self):
return self.name_first + ' ' + self.name_last
# return id to be used in storage
def get_id(self):
return self.id
The views are many and fairly complicated, and this issue seems to be global. But here at least is the login view, which is 2 separate routes, the first of which which emails a code to the user rather than using a password:
@home_blueprint.route('/', methods=['get','post'])
@home_blueprint.route('/login', methods=['get','post'])
def login():
form = LoginForm()
message = request.form.get('message')
if request.method=="POST":
if current_user:
logout_user()
user = User.query.filter_by(email=form.email.data.lower()).first()
if not user:
message = f'{form.email.data} not found.'
return render_template('home/login.html', form=form, message=message)
else:
### CREATE 4 DIGIT CODE
### SAVE TO USER TABLE
### EMAIL TO USER
### SEND TO 2nd LOGIN PAGE FOR EMAIL/CODE
code = randrange(1000,9999)
print(code)
user.code = code
db.session.add(user)
db.session.commit()
message = send_verification_email(form.email.data, code)
payload = {'email':form.email.data}
email_encoded = urlencode(payload)
return redirect(url_for('home.login2', message=message, email_encoded=email_encoded))
else:
return render_template('home/login.html', form=form, message=message)
@home_blueprint.route('/login2', methods=['get','post'])
def login2():
form = LoginForm2()
message = request.form.get('message')
email_encoded = request.args.get('email_encoded')
parsed = parse_qsl(email_encoded)
if request.method=="POST":
if current_user:
logout_user()
user = User.query.filter_by(email=form.email.data.lower()).first()
if not user:
flash(f'{form.email.data} not found.')
return render_template('home/login.html', form=form)
else:
code = request.form.get('code', type=str)
if code == user.code:
login_user(user)
return redirect(url_for('jobs.job_admin', email=user.email))
else:
message = "Code and email do not match."
form.email.data = parsed[0][1]
return render_template('home/login2.html', form=form, message=message)
else:
form.email.data=parsed[0][1]
return render_template('home/login2.html', form=form, message=message)