Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

version 1.0 (MTurk) #42

Merged
merged 2 commits into from
Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 66 additions & 29 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from app import consent, alert, experiment, complete, error
from .io import write_metadata
from .utils import gen_code
__version__ = '0.9.8'
__version__ = '1.0'

## Define root directory.
ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
Expand All @@ -20,19 +20,19 @@
reject_dir = os.path.join(ROOT_DIR, cfg['IO']['REJECT'])
if not os.path.isdir(reject_dir): os.makedirs(reject_dir)

## Check Flask password.
if cfg['FLASK']['SECRET_KEY'] == "PLEASE_CHANGE_THIS":
msg = "WARNING: Flask password is currently default. This should be changed prior to production."
warnings.warn(msg)
## Check Flask mode; if debug mode, clear session variable.
debug = cfg['FLASK'].getboolean('DEBUG')
if debug:
warnings.warn("WARNING: Flask currently in debug mode. This should be changed prior to production.")

## Check Flask mode.
if cfg['FLASK']['DEBUG'] != "FALSE":
msg = "WARNING: Flask currently in debug mode. This should be changed prior to production."
warnings.warn(msg)
## Check Flask password.
secret_key = cfg['FLASK']['SECRET_KEY']
if secret_key == "PLEASE_CHANGE_THIS":
warnings.warn("WARNING: Flask password is currently default. This should be changed prior to production.")

## Initialize Flask application.
app = Flask(__name__)
app.secret_key = cfg['FLASK']['SECRET_KEY']
app.secret_key = secret_key

## Apply blueprints to the application.
app.register_blueprint(consent.bp)
Expand All @@ -45,11 +45,14 @@
@app.route('/')
def index():

## Debug mode: clear session.
if debug:
session.clear()

## Store directories in session object.
session['data'] = data_dir
session['metadata'] = meta_dir
session['reject'] = reject_dir
session['debug'] = cfg['FLASK']['DEBUG'] != "FALSE"

## Record incoming metadata.
info = dict(
Expand All @@ -63,6 +66,7 @@ def index():
tp_b = request.args.get('tp_b'), # TurkPrime metadata
c = request.args.get('c'), # TurkPrime metadata
tp_c = request.args.get('tp_c'), # TurkPrime metadata
address = request.remote_addr, # NivTurk metadata
browser = request.user_agent.browser, # User metadata
platform = request.user_agent.platform, # User metadata
version = request.user_agent.version, # User metadata
Expand All @@ -71,49 +75,82 @@ def index():
## Case 1: workerId absent.
if info['workerId'] is None:

## Redirect participant to error (admin error).
## Redirect participant to error (missing workerId).
return redirect(url_for('error.error', errornum=1000))

## Case 2: mobile user.
elif info['platform'] in ['android','iphone','ipad','wii']:

## Redirect participant to error (admin error).
## Redirect participant to error (platform error).
return redirect(url_for('error.error', errornum=1001))

## Case 3: repeat visit, manually changed workerId.
## Case 3: repeat visit, preexisting log but no session data.
elif not 'workerId' in session and info['workerId'] in os.listdir(meta_dir):

## Consult log file.
with open(os.path.join(session['metadata'], info['workerId']),'r') as f:
logs = f.read()

## Case 3a: previously started experiment.
if 'experiment' in logs:

## Update metadata.
session['workerId'] = info['workerId']
session['ERROR'] = '1004: Suspected incognito user.'
session['complete'] = 'error'
write_metadata(session, ['ERROR','complete'], 'a')

## Redirect participant to error (previous participation).
return redirect(url_for('error.error', errornum=1004))

## Case 3b: no previous experiment starts.
else:

## Update metadata.
for k, v in info.items(): session[k] = v
session['WARNING'] = "Assigned new subId."
write_metadata(session, ['subId','WARNING'], 'a')

## Redirect participant to consent form.
return redirect(url_for('consent.consent'))

## Case 4: repeat visit, manually changed workerId.
elif 'workerId' in session and session['workerId'] != info['workerId']:

## Update metadata.
session['ERROR'] = '1002: workerId tampering detected.'
write_metadata(session, ['ERROR'], 'a')
session['ERROR'] = '1005: workerId tampering detected.'
session['complete'] = 'error'
write_metadata(session, ['ERROR','complete'], 'a')

## Redirect participant to error (unusual activity).
return redirect(url_for('error.error', errornum=1002))
return redirect(url_for('error.error', errornum=1005))

## Case 4: repeat visit, preexisting activity.
elif 'workerId' in session or info['workerId'] in os.listdir(meta_dir):
## Case 5: repeat visit, previously completed experiment.
elif 'complete' in session:

## Update metadata.
session['WARNING'] = "Revisited home."
write_metadata(session, ['WARNING'], 'a')

## Redirect participant to complete page.
return redirect(url_for('complete.complete'))

## Case 6: repeat visit, preexisting activity.
elif 'workerId' in session:

## Update metadata.
for k, v in info.items(): session[k] = v
session['WARNING'] = "Revisited home."
write_metadata(session, ['WARNING'], 'a')

## Redirect participant to consent form.
return redirect(url_for('consent.consent'))

## Case 5: first visit, workerId present.
## Case 7: first visit, workerId present.
else:

## Update metadata.
for k, v in info.items(): session[k] = v
write_metadata(session, ['workerId','hitId','assignmentId','subId','browser','platform','version'], 'w')
write_metadata(session, ['workerId','hitId','assignmentId','subId','address','browser','platform','version'], 'w')

## Redirect participant to consent form.
return redirect(url_for('consent.consent'))

## DEV NOTE:
## The following route is strictly for development purpose and should be commented out before deployment.
# @app.route('/clear')
# def clear():
# session.clear()
# return 'Complete!'
32 changes: 24 additions & 8 deletions app/alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,24 @@
def alert():
"""Present alert to participant."""

## Case 1: first visit.
if not 'alert' in session:
## Error-catching: screen for missing session.
if not 'workerId' in session:

## Update participant metadata.
session['alert'] = True
write_metadata(session, ['alert'], 'a')
## Redirect participant to error (missing workerId).
return redirect(url_for('error.error', errornum=1000))

## Present alert page.
return render_template('alert.html')
## Case 1: previously completed experiment.
elif 'complete' in session:

## Update metadata.
session['WARNING'] = "Revisited alert page."
write_metadata(session, ['WARNING'], 'a')

## Redirect participant to complete page.
return redirect(url_for('complete.complete'))

## Case 2: repeat visit.
else:
elif 'alert' in session:

## Update participant metadata.
session['WARNING'] = "Revisited alert page."
Expand All @@ -28,6 +34,16 @@ def alert():
## Redirect participant to error (previous participation).
return redirect(url_for('experiment.experiment'))

## Case 3: first visit.
else:

## Update participant metadata.
session['alert'] = True
write_metadata(session, ['alert'], 'a')

## Present alert page.
return render_template('alert.html')

@bp.route('/alert', methods=['POST'])
def alert_post():
"""Process participant repsonse to alert page."""
Expand Down
16 changes: 14 additions & 2 deletions app/app.ini
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
[FLASK]

# Flask secret key for encrypting session objects
# Suggested: get key from https://randomkeygen.com
SECRET_KEY = PLEASE_CHANGE_THIS
DEBUG = FALSE

# Toggle debug mode (allow repeat visits from same session)
# Accepts true or false
DEBUG = true

[IO]
DATA = ../data

# Path to metadata folder [default: ../metadata]
METADATA = ../metadata

# Path to data folder [default: ../data]
DATA = ../data

# Path to reject folder [default: ../reject]
REJECT = ../reject
55 changes: 43 additions & 12 deletions app/complete.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,52 @@
def complete():
"""Present completion screen to participant."""

## Error-catching: screen for previous visits.
if 'complete' in session:
## Access query string.
query_info = request.args

## Update participant metadata.
session['WARNING'] = "Revisited complete page."
## Confirm all TurkPrime metadata present.
fields = ['workerId','assignmentId','hitId','a','tp_a','b','tp_b','c','tp_c']
all_fields = all([f in query_info for f in fields])

## Error-catching: screen for missing session.
if not 'workerId' in session:

## Redirect participant to error (missing workerId).
return redirect(url_for('error.error', errornum=1000))

## Case 1: visit complete page with previous rejection.
elif session.get('complete') == 'reject':

## Update metadata.
session['WARNING'] = "Revisited complete."
write_metadata(session, ['WARNING'], 'a')

else:
## Redirect participant to error (unusual activity).
return redirect(url_for('error.error', errornum=1005))

## Case 2: visit complete page with previous error.
elif session.get('complete') == 'error':

## Update participant metadata.
session['complete'] = True
write_metadata(session, ['complete'], 'a')
## Update metadata.
session['WARNING'] = "Revisited complete."
write_metadata(session, ['WARNING'], 'a')

## DEV NOTE:
## If you want a custom completion code, replace the return statement with:
## > render_template('complete.html', value=session['complete'])
## Redirect participant to error (unusual activity).
return redirect(url_for('error.error', errornum=1005))

## Case 3: visit complete page but missing metadata.
elif session.get('complete') == 'success' and not all_fields:

## Update metadata.
session['WARNING'] = "Revisited complete."
write_metadata(session, ['WARNING'], 'a')

## Redirect participant with complete metadata.
url = "/complete?workerId=%s&assignmentId=%s&hitId=%s&a=%s&tp_a=%s&b=%s&tp_b=%s&c=%s&tp_c=%s" %(session['workerId'], session['assignmentId'], session['hitId'], session['a'], session['tp_a'], session['b'], session['tp_b'], session['c'], session['tp_c'])
return redirect(url)

## Case 4: all else.
else:

return render_template('complete.html')
## Redirect participant with completion code.
return render_template('complete.html')
51 changes: 39 additions & 12 deletions app/consent.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,57 @@
def consent():
"""Present consent form to participant."""

## Case 1: first visit.
if not 'consent' in session:
## Error-catching: screen for missing session.
if not 'workerId' in session:

## Redirect participant to error (missing workerId).
return redirect(url_for('error.error', errornum=1000))

## Case 1: previously completed experiment.
elif 'complete' in session:

## Update metadata.
session['WARNING'] = "Revisited consent page."
write_metadata(session, ['WARNING'], 'a')

## Redirect participant to complete page.
return redirect(url_for('complete.complete'))

## Case 2: first visit.
elif not 'consent' in session:

## Present consent form.
return render_template('consent.html')

## Case 2: repeat visit, previous consent.
elif session['consent']:
## Case 3: repeat visit, previous bot-detection.
elif session['consent'] == 'BOT':

## Update participant metadata.
session['WARNING'] = "Revisited consent form."
write_metadata(session, ['WARNING'], 'a')

## Redirect participant to alert page.
return redirect(url_for('alert.alert'))
## Redirect participant to error (unusual activity).
return redirect(url_for('error.error', errornum=1005))

## Case 3: repeat visit, previous non-consent.
else:
## Case 4: repeat visit, previous non-consent.
elif session['consent'] == False:

## Update participant metadata.
session['WARNING'] = "Revisited consent form."
write_metadata(session, ['WARNING'], 'a')

## Redirect participant to error (decline consent).
return redirect(url_for('error.error', errornum=1003))
return redirect(url_for('error.error', errornum=1002))

## Case 5: repeat visit, previous consent.
else:

## Update participant metadata.
session['WARNING'] = "Revisited consent form."
write_metadata(session, ['WARNING'], 'a')

## Redirect participant to alert page.
return redirect(url_for('alert.alert'))

@bp.route('/consent', methods=['POST'])
def consent_post():
Expand All @@ -48,10 +73,12 @@ def consent_post():

## Update participant metadata.
session['consent'] = 'BOT'
write_metadata(session, ['consent'], 'a')
session['experiment'] = False # Prevents incognito users
session['complete'] = 'error'
write_metadata(session, ['consent','experiment','complete'], 'a')

## Redirect participant to error (unusual activity).
return redirect(url_for('error.error', errornum=1002))
return redirect(url_for('error.error', errornum=1005))

## Check participant response.
elif subj_consent:
Expand All @@ -70,4 +97,4 @@ def consent_post():
write_metadata(session, ['consent'], 'a')

## Redirect participant to error (decline consent).
return redirect(url_for('error.error', errornum=1003))
return redirect(url_for('error.error', errornum=1002))
Loading