Flask-login - AttributeError: 'AnonymousUserMixin' object has no attribute '_sa_instance_state'

paulklee picture paulklee · Feb 4, 2019 · Viewed 10.9k times · Source

I'm setting up a flask application to run an anonymous online experiment. How can I track anonymous participants via the the current_user object of the Login-manager?

When a user is anonymous the current_user is set to AnonymousUserMixin. I tried to assign AnonymousUserMixin an id variable (through uuid.uuid4() for instance) to not have current_user=None but then it's not an ORM object anymore. I tried to follow the advice of the second answer of this post (How to track anonymous users with Flask), but I'm lacking knowledge how to transform the described way into actual code.

So I ended up with the error message 'AnonymousUserMixin' object has no attribute '_sa_instance_state' which I can't seem to get solved.

Any suggestions on how to use the current_user object with anonymous users?

Here some relevant code parts [EDIT: code was adjusted based on @Roch's suggestion] :

models.py

from website import db


# models for database structure
class DemographicData(db.Model):
    __tablename__ = 'demographic_data'
    id = db.Column(db.Integer, primary_key=True)
    gender = db.Column(db.String(1), nullable=False)
    age = db.Column(db.Integer, nullable=False)
    nationality = db.Column(db.String(50), nullable=False)
    # create relationships to the other models to link them to the demographicData
    consentRequest = db.relationship('ModalData', backref='participant_bref', lazy=True)


    def __repr__(self):
        return f"demographicData('{self.id}', '{self.gender}', '{self.age}', '{self.nationality}')"


class ModalData(db.Model):
    __tablename__ = 'modal_data'
    id = db.Column(db.Integer, primary_key=True)
    participantId = db.Column(db.Integer, db.ForeignKey('demographic_data.id'), nullable=False)
    consent = db.Column(db.String(3), nullable=False)

    def __repr__(self):
        return f"modalData('{self.participantId}', '{self.consent}')"

routes.py

from flask import render_template, url_for, redirect, request, session
from website import app, db
from website.models import DemographicData, ModalData
from website.questionnaires import DemographicsForm



@app.before_request
def make_session_permanent():
    session.permanent = True



# route to demographic information form
@app.route("/demographics", methods=['GET', 'POST'])
def demographics():
    form = DemographicsForm()
    if form.validate_on_submit():
        participant = DemographicData(gender=form.gender.data,
                                      age=form.age.data,
                                      nationality=form.nationality.data)
        session['anonymous_user_id'] = participant.id
        # save to database
        db.session.add(participant)
        db.session.commit()
        return redirect(url_for('megazine')) 
    return render_template('demographics.html', title='Demographic Information', form=form)



# route to a website I want to redirect to after demographics
@app.route("/megazine", methods=['GET', 'POST'])
def megazine():
    if request.method == 'POST':
        consentDecision = ModalData(participant_bref=session['anonymous_user_id'],
                                    consent=request.form['consentForm'])
        db.session.add(consentDecision)
        db.session.commit()
        return redirect(url_for('motivemag'))
    return render_template('/newswebsite_templates/megazine/index.html', title='Megazine')

Answer

Roch picture Roch · Feb 5, 2019

Do you want to keep track of the user during the session or after the session ?

If you need to keep some data on the anonymous user during his time using your app, you could easily use Flask session : http://flask.pocoo.org/docs/1.0/api/#flask.session

from flask import session

@app.route('/my_url1')
def my_view1():
    data = None
    if session.new:
         # ... do something like save data in the session
         session['foo'] = 'bar'
    # ... do something like read data in the session
    data = session['foo']
    return render_template('/my_template.html', data=data)

If you desire to keep the data of the user after the session is ended, I think the logical thing to do would be to create a user on the fly and log him.

from flask import session    
from models import User

@app.route('/my_url1')
def my_view1():
    data = None
    if session.new:
         user = User()
         session['anonymous_user_id'] = user.id
    else:
         user = User.query.get(session['anonymous_user_id'])
    # ... do whatever you like