1

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')

paulklee
  • 59
  • 1
  • 1
  • 7

1 Answers1

2

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
Roch
  • 94
  • 6
  • I would like to create a unique random id for each anonymous user in order to track the user between different forms I'm gonna direct him to. I thought I could do this with the `flask-login` module (saving the `current_user` id in my database) but since this is focused on login/logout and not so much on anonymous users I couldn't figure out how to add an id to each anonymous user without getting the error mentioned in the header. – paulklee Feb 05 '19 at 14:29
  • In this case, just use the session provided by flask. It is done for that purpose. – Roch Feb 05 '19 at 16:54
  • But as you said, if I desire to keep the data of the user after the session has enden I should create a user on the fly and log him. Can you point me in a direction on how to best assign an id to the user and log him (any example code?)? (given that I'm not using the `flask-login` module now). Thanks for your help. – paulklee Feb 06 '19 at 09:51
  • As I said, in your case, you can use the session to save the data you need for other requests. I updated the main answer to give you some code. – Roch Feb 06 '19 at 17:15
  • Thank you @Roch. Although I get the following error when I try to use the session.anonymous_user_id object in another `@app.route` (for instance to refer via backref to a certain user `user=session.anonymous_user_id`): AttributeError: 'SecureCookieSession' object has no attribute 'anonymous_user_id' – paulklee Feb 07 '19 at 14:27
  • Could you share your new code in the initial question ? – Roch Feb 08 '19 at 17:22
  • edited the code parts. So basically I get an error if I use `session.anonymous_user_id` in the megazine route, maybe because it was created in the demographics route. But I thought the idea of sessions is that this is possible (linking objects across routes). – paulklee Feb 11 '19 at 09:04
  • Hi. I made a little mistake in my example. The data associated with the session is stored as a dictionary and not as attributes. So you need to use: `session['anonymous_user_id']` – Roch Feb 11 '19 at 14:40
  • Thanks this got rid of initial error but somehow the link between the id created in the demographics route, which should be accessed again in the megazine route, still doesn't work. The variable `consentDecision` which I want to add to the database in the megazine route always has `None` as participantId, which means that it couldn't grab the id saved in the session. Is there anything else to keep in mind when working with sessions? I'll post the error I get in a new comment. (I adjusted the inicial code again btw) – paulklee Feb 12 '19 at 15:33
  • sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) NOT NULL constraint failed: modal_data.participantId [SQL: 'INSERT INTO modal_data ("participantId", consent) VALUES (?, ?)'] [parameters: (None, 'DNA')] (Background on this error at: http://sqlalche.me/e/gkpj) – paulklee Feb 12 '19 at 15:33
  • 1
    When you create the user, it doesn't have any id before it is saved in the database. So `session['anonymous_user_id'] = participant.id` should be called after `db.session.commit()` – Roch Feb 13 '19 at 21:23
  • I realized this yesterday evening as well, you are right. This was my first mistake. The second mistake was that I have to make a database call in the megazine route and assign the id from DemographicsData to some variable, which I can then link to the backref in ModalData `participant = DemographicData.query.get(session['anonymous_user_id'])`. Thanks for all your effort. – paulklee Feb 14 '19 at 08:47