There will be a short maintenance break on Wed 27.10. at 12:00. Estimated time 30 minutes.

Commit 4958ffad authored by Ossi Laine's avatar Ossi Laine
Browse files

Merge branch 'dev' into 'master'

Dev

See merge request tithei/pet-rating!6
parents dcfe881e e96f5aed
......@@ -13,4 +13,9 @@ config.py
*.db
/embody
/app/static/lib
.env
/app/static/*.png
/app/static/embody_images
.vscode/
documentation
......@@ -15,7 +15,7 @@ from flask_cors import CORS, cross_origin
app = Flask(__name__)
CORS(app)
#CORS(app, resources={r"/*": {"cors_allowed_origins":"*"} } )
# CORS(app, resources={r"/*": {"cors_allowed_origins":"*"} } )
#app.config['BABEL_DEFAULT_LOCALE'] = 'fin'
#app.config['BABEL_TRANSLATION_DIRECTORIES'] ='C:/Users/Timo/git/pet-rating/app/translations'
......@@ -68,9 +68,6 @@ def get_locale():
"""
# Run flask app with socketIO
socketio = SocketIO()
socketio.init_app(app)
#mariabd mysql portti 3306 tarkista?
......@@ -81,6 +78,11 @@ migrate = Migrate(app, db)
login = LoginManager(app)
login.login_view = 'login'
# Run flask app with socketIO
socketio = SocketIO(app, cors_allowed_origins="*")
# socketio = SocketIO()
socketio.init_app(app)
# Register blueprints
from .task.views import task_blueprint
from .experiment.views import experiment_blueprint
......
......@@ -3,8 +3,7 @@
<h1 class="container mt-5 display-4 text-center"><br>Add new embody picture:</h1>
<br>
<p class="lead">
Upload new embody image... instructions here for admins..
<p class="lead"> Upload new embody image. Submit without choosing file if you want to use default embody picture.
</p>
{% from "_formhelpers.html" import render_field %}
......@@ -12,8 +11,8 @@ Upload new embody image... instructions here for admins..
<form method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="Background questions">image question/explanation:</label>
<textarea class="form-control" rows="5" id="embody_picture_text" name="question"></textarea>
<label for="Background questions">Image question/explanation:</label>
<textarea class="form-control" rows="5" id="embody_picture_text" name="question">Color the regions whose activity you feel increasing or getting stronger</textarea>
</div>
<div class="custom-file">
......@@ -23,7 +22,7 @@ Upload new embody image... instructions here for admins..
<hr>
<button type="submit" class="btn btn-primary submit-file" disabled>Submit</button>
<button type="submit" class="btn btn-primary">Submit</button>
<a class="btn btn-primary" href="{{ request.referrer }}" role="button">Cancel</a>
</form>
......
......@@ -36,7 +36,27 @@
<tr>
<td>Number of finished ratings:</td>
<td>{{ finished_ratings }}
<a class="btn btn-primary btn-info float-right" href="{{ url_for('download_csv', exp_id=exp.idexperiment) }}" role="button">Export results (csv)</a>
<button data-value="{{ exp.idexperiment }}" class="btn btn-primary float-right get-csv-results">
export results
</button>
<div id="export-link-container" class="hidden">
<a id="export-link" class="float-right"
href="{{ url_for('experiment.download_csv', exp_id=exp.idexperiment) }}" role="button"></a>
<p id="export-error"></p>
</div>
<div class="progress hidden">
<div id="export-results-bar" class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
</div>
</div>
</td>
</tr>
</tbody>
......@@ -133,7 +153,10 @@
{% elif s.type == 'audio' %}
<td>
<div class="embed-responsive embed-responsive-16by9 ">
<iframe class="embed-responsive-item thumbnail" src="/{{ s.media }}" allowFullScreen></iframe>
<audio class="embed-responsive-item thumbnail" controls>
<source src="/{{ s.media }}">
Your browser does not support the audio element.
</audio>
</div>
</td>
{% else %}
......@@ -158,7 +181,8 @@
<div class="progress hidden" id="plotted-image">
<div id="image-loading-progress" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
<div id="image-loading-progress" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar"
aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
<!-- Creating image... -->
</div>
</div>
......@@ -193,7 +217,9 @@
</tbody>
</table>
<script src="{{ url_for('static', filename='lib/js/socket.io.js') }}" ></script>
<script src="{{ url_for('static', filename='js/getDrawing.js') }}" ></script>
<script src="{{ url_for('static', filename='lib/js/socket.io.js') }}"></script>
<script src="{{ url_for('static', filename='js/urls.js') }}"></script>
<script src="{{ url_for('static', filename='js/getDrawing.js') }}"></script>
<script src="{{ url_for('static', filename='js/getCSV.js') }}"></script>
{% endblock %}
\ No newline at end of file
......@@ -211,7 +211,6 @@
</table>
<h1 class="container mt-5 display-4 text-left"><br>Add embody tool:</h1>
<table class="table">
......@@ -231,13 +230,11 @@
{% endif %}
</td>
<td>
<a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.add_embody', exp_id=exp_id, default=true) }}" role="button">Add default</a>
<a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.add_embody', exp_id=exp_id) }}" role="button">Add new picture</a>
</td>
</tr>
{% for embody_picture in embody_pictures %}
<tr>
<td>ID: {{ embody_picture.idembody }} <br> {{ embody_picture.question }}</td>
<td><img src="{{ embody_picture.picture }}" class="thumbnail" /></td>
......
This diff is collapsed.
from sqlalchemy import and_
from flask import session
from app import db
from sqlalchemy import Column, Integer, String, Text, Boolean
from flask_wtf import FlaskForm
......@@ -15,7 +17,8 @@ class background_question(db.Model):
__tablename__ = "background_question"
idbackground_question = db.Column(db.Integer, primary_key=True)
background_question = db.Column(db.String(120))
answers = db.relationship('background_question_answer', backref='question', lazy='dynamic')
answers = db.relationship(
'background_question_answer', backref='question', lazy='dynamic')
experiment_idexperiment = db.Column(db.Integer)
def __repr__(self):
......@@ -25,7 +28,8 @@ class background_question(db.Model):
class background_question_option(db.Model):
__tablename__ = "background_question_option"
idbackground_question_option = db.Column(db.Integer, primary_key=True)
background_question_idbackground_question = db.Column(db.Integer, db.ForeignKey('background_question.idbackground_question'))
background_question_idbackground_question = db.Column(
db.Integer, db.ForeignKey('background_question.idbackground_question'))
option = db.Column(db.String(120))
def __repr__(self):
......@@ -59,13 +63,16 @@ class experiment (db.Model):
class answer_set (db.Model):
__tablename__ = "answer_set"
idanswer_set = db.Column(db.Integer, primary_key=True)
experiment_idexperiment = db.Column(db.Integer, db.ForeignKey('experiment.idexperiment'))
experiment_idexperiment = db.Column(
db.Integer, db.ForeignKey('experiment.idexperiment'))
session = db.Column(db.String(120))
agreement = db.Column(db.String(120))
answer_counter = db.Column(db.Integer)
answer_type = db.Column(db.String(120))
registration_time = db.Column(db.DateTime, index=True, default=datetime.utcnow)
last_answer_time = db.Column(db.DateTime, index=True, default=datetime.utcnow)
registration_time = db.Column(
db.DateTime, index=True, default=datetime.utcnow)
last_answer_time = db.Column(
db.DateTime, index=True, default=datetime.utcnow)
def __repr__(self):
return "<idanswer_set = '%s', experiment_idexperiment = '%s', session = '%s', agreement = '%s', answer_counter = '%s', registration_time = '%s', last_answer_time = '%s'>" % (self.idanswer_set, self.experiment_idexperiment, self.session, self.agreement, self.answer_counter, self.registration_time, self.last_answer_time)
......@@ -74,9 +81,11 @@ class answer_set (db.Model):
class background_question_answer(db.Model):
__tablename__ = "background_question_answer"
idbackground_question_answer = db.Column(db.Integer, primary_key=True)
answer_set_idanswer_set = db.Column(db.Integer, db.ForeignKey('answer_set.idanswer_set'))
answer_set_idanswer_set = db.Column(
db.Integer, db.ForeignKey('answer_set.idanswer_set'))
answer = db.Column(db.String(120))
background_question_idbackground_question = db.Column(db.Integer, db.ForeignKey('background_question.idbackground_question'))
background_question_idbackground_question = db.Column(
db.Integer, db.ForeignKey('background_question.idbackground_question'))
def __repr__(self):
return "<idbackground_question_answer = '%s', answer_set_idanswer_set = '%s', answer = '%s', background_question_idbackground_question = '%s'>" % (self.idbackground_question_answer, self.answer_set_idanswer_set, self.answer, self.background_question_idbackground_question)
......@@ -85,6 +94,7 @@ class background_question_answer(db.Model):
def background_question_answer_query():
return background_question_answer.query
"""
class ChoiceForm(FlaskForm):
opts = QuerySelectField(query_factory=background_question_answer_query, allow_blank=True)
......@@ -98,7 +108,8 @@ vastaukset = u.answers.all()
class question (db.Model):
__tablename__ = "question"
idquestion = db.Column(db.Integer, primary_key=True)
experiment_idexperiment = db.Column(db.Integer, db.ForeignKey('experiment.idexperiment'))
experiment_idexperiment = db.Column(
db.Integer, db.ForeignKey('experiment.idexperiment'))
question = db.Column(db.String(120))
left = db.Column(db.String(120))
right = db.Column(db.String(120))
......@@ -110,18 +121,20 @@ class question (db.Model):
class embody_question (db.Model):
__tablename__ = "embody_question"
idembody = db.Column(db.Integer, primary_key=True)
experiment_idexperiment = db.Column(db.Integer, db.ForeignKey('experiment.idexperiment'))
experiment_idexperiment = db.Column(
db.Integer, db.ForeignKey('experiment.idexperiment'))
picture = db.Column(db.Text)
question = db.Column(db.Text)
def __repr__(self):
return "<idembody = '%s', experiment_idexperiment = '%s', picture = '%s', question = '%s'>" % (self.idembody, self.experiment_idexperiment, self.picture, self.question )
return "<idembody = '%s', experiment_idexperiment = '%s', picture = '%s', question = '%s'>" % (self.idembody, self.experiment_idexperiment, self.picture, self.question)
class page (db.Model):
__tablename__ = "page"
idpage = db.Column(db.Integer, primary_key=True)
experiment_idexperiment = db.Column(db.Integer, db.ForeignKey('experiment.idexperiment'))
experiment_idexperiment = db.Column(
db.Integer, db.ForeignKey('experiment.idexperiment'))
type = db.Column(db.String(120), index=True)
text = db.Column(db.Text)
media = db.Column(db.String(120), index=True)
......@@ -133,11 +146,19 @@ class page (db.Model):
class answer (db.Model):
__tablename__ = "answer"
idanswer = db.Column(db.Integer, primary_key=True)
question_idquestion = db.Column(db.Integer, db.ForeignKey('question.idquestion'))
answer_set_idanswer_set = db.Column(db.Integer, db.ForeignKey('answer_set.idanswer_set'))
question_idquestion = db.Column(
db.Integer, db.ForeignKey('question.idquestion'))
answer_set_idanswer_set = db.Column(
db.Integer, db.ForeignKey('answer_set.idanswer_set'))
answer = db.Column(db.String(120))
page_idpage = db.Column(db.Integer, db.ForeignKey('page.idpage'))
def question(self):
return int(self.question_idquestion)
def result(self):
return int(self.answer)
def __repr__(self):
return "<idanswer = '%s', question_idquestion = '%s', answer_set_idanswer_set = '%s', answer = '%s', page_idpage = '%s'>" % (self.idanswer, self.question_idquestion, self.answer_set_idanswer_set, self.answer, self.page_idpage)
......@@ -145,11 +166,19 @@ class answer (db.Model):
class embody_answer (db.Model):
__tablename__ = "embody_answer"
idanswer = db.Column(db.Integer, primary_key=True)
answer_set_idanswer_set = db.Column(db.Integer, db.ForeignKey('answer_set.idanswer_set'))
answer_set_idanswer_set = db.Column(
db.Integer, db.ForeignKey('answer_set.idanswer_set'))
page_idpage = db.Column(db.Integer, db.ForeignKey('page.idpage'))
embody_question_idembody = db.Column(db.Integer, db.ForeignKey('embody_question.idembody'))
embody_question_idembody = db.Column(
db.Integer, db.ForeignKey('embody_question.idembody'))
coordinates = db.Column(db.Text)
def question(self):
return self.embody_question_idembody
def result(self):
return self.coordinates
def __repr__(self):
return "<idanswer = '%s', answer_set_idanswer_set = '%s', coordinates = '%s', page_idpage = '%s', embody_question_idembody='%s' >" % (self.idanswer, self.answer_set_idanswer_set, self.coordinates, self.page_idpage, self.embody_question_idembody)
......@@ -165,11 +194,18 @@ class trial_randomization (db.Model):
def __repr__(self):
return "<idtrial_randomization = '%s', page_idpage = '%s', randomized_idpage = '%s', answer_set_idanswer_set = '%s', experiment_idexperiment = '%s'>" % (self.idtrial_randomization, self.page_idpage, self.randomized_idpage, self.answer_set_idanswer_set, self.experiment_idexperiment)
@classmethod
def get_randomized_page(cls, page_id):
return cls.query.filter(and_(
cls.answer_set_idanswer_set == session['answer_set'],
cls.page_idpage == page_id)).first()
class forced_id (db.Model):
__tablename__ = "forced_id"
idforced_id = db.Column(db.Integer, primary_key=True)
experiment_idexperiment = db.Column(db.Integer, db.ForeignKey('experiment.idexperiment'))
experiment_idexperiment = db.Column(
db.Integer, db.ForeignKey('experiment.idexperiment'))
pregenerated_id = db.Column(db.String(120))
def __repr__(self):
......@@ -196,4 +232,3 @@ class user(UserMixin, db.Model):
@login.user_loader
def load_user(id):
return user.query.get(int(id))
\ No newline at end of file
import csv
import os
import random
import secrets
from datetime import datetime
import tempfile
import json
from flask import (
Flask,
render_template,
from flask import (render_template,
request,
session,
flash,
redirect,
url_for,
Blueprint,
send_file
)
url_for)
from sqlalchemy import and_
from flask_login import current_user, login_user, logout_user, login_required
from flask_babel import Babel, _, lazy_gettext as _l
from app import app, db, babel
from app import app, db
from app.models import background_question, experiment
from app.models import background_question_answer
from app.models import page, question, embody_question, embody_answer
from app.models import page
from app.models import background_question_option
from app.models import answer_set, answer, forced_id
from app.models import answer_set, forced_id
from app.models import user, trial_randomization
from app.forms import LoginForm, RegisterForm, StartWithIdForm
#Stimuli upload folder setting
# Stimuli upload folder setting
APP_ROOT = os.path.dirname(os.path.abspath(__file__))
@app.route('/')
@app.route('/index')
def index():
......@@ -88,16 +77,16 @@ def remove_language():
def participant_session():
'''Set up session variables and create answer_set (database level sessions)'''
#start session
# start session
session['exp_id'] = request.args.get('exp_id', None)
session['agree'] = request.args.get('agree', None)
#If user came via the route for "I have already a participant ID that I wish to use, Use that ID, otherwise generate a random ID
# If user came via the route for "I have already a participant ID that I wish to use, Use that ID, otherwise generate a random ID
if 'begin_with_id' in session:
session['user'] = session['begin_with_id']
session.pop('begin_with_id', None)
else:
#lets generate a random id. If the same id is allready in db, lets generate a new one and finally use that in session['user']
# lets generate a random id. If the same id is allready in db, lets generate a new one and finally use that in session['user']
random_id = secrets.token_hex(3)
check_id = answer_set.query.filter_by(session=random_id).first()
......@@ -108,8 +97,8 @@ def participant_session():
session['user'] = random_id
# Set session status variables
exp_status = experiment.query.filter_by(idexperiment=session['exp_id']).first()
exp_status = experiment.query.filter_by(
idexperiment=session['exp_id']).first()
# Create answer set for the participant in the database
the_time = datetime.now()
......@@ -122,26 +111,26 @@ def participant_session():
participant_answer_set = answer_set(experiment_idexperiment=session['exp_id'],
session=session['user'],
agreement = session['agree'],
answer_counter = '0',
answer_type = answer_set_type,
agreement=session['agree'],
answer_counter='0',
answer_type=answer_set_type,
registration_time=the_time,
last_answer_time=the_time)
db.session.add(participant_answer_set)
db.session.commit()
#If trial randomization is set to 'On' for the experiment, create a randomized trial order for this participant
#identification is based on the uniquie answer set id
# If trial randomization is set to 'On' for the experiment, create a randomized trial order for this participant
# identification is based on the uniquie answer set id
if exp_status.randomization == 'On':
session['randomization'] = 'On'
#create a list of page id:s for the experiment
experiment_pages = page.query.filter_by(experiment_idexperiment=session['exp_id']).all()
# create a list of page id:s for the experiment
experiment_pages = page.query.filter_by(
experiment_idexperiment=session['exp_id']).all()
original_id_order_list = [(int(o.idpage)) for o in experiment_pages]
#create a randomized page id list
# create a randomized page id list
helper_list = original_id_order_list
randomized_order_list = []
......@@ -150,26 +139,28 @@ def participant_session():
helper_list.remove(element)
randomized_order_list.append(element)
#Input values into trial_randomization table where the original page_ids have a corresponding randomized counterpart
experiment_pages = page.query.filter_by(experiment_idexperiment=session['exp_id']).all()
# Input values into trial_randomization table where the original page_ids have a corresponding randomized counterpart
experiment_pages = page.query.filter_by(
experiment_idexperiment=session['exp_id']).all()
original_id_order_list = [(int(o.idpage)) for o in experiment_pages]
for c in range(len(original_id_order_list)):
random_page = trial_randomization(page_idpage=original_id_order_list[c], randomized_idpage=randomized_order_list[c], answer_set_idanswer_set = participant_answer_set.idanswer_set, experiment_idexperiment = session['exp_id'])
random_page = trial_randomization(page_idpage=original_id_order_list[c], randomized_idpage=randomized_order_list[
c], answer_set_idanswer_set=participant_answer_set.idanswer_set, experiment_idexperiment=session['exp_id'])
db.session.add(random_page)
db.session.commit()
if exp_status.randomization == "Off":
session['randomization'] = "Off"
#store participants session id in session list as answer_set, based on experiment id and session id
session_id_for_participant = answer_set.query.filter(and_(answer_set.session==session['user'], answer_set.experiment_idexperiment==session['exp_id'])).first()
# store participants session id in session list as answer_set, based on experiment id and session id
session_id_for_participant = answer_set.query.filter(and_(
answer_set.session == session['user'], answer_set.experiment_idexperiment == session['exp_id'])).first()
session['answer_set'] = session_id_for_participant.idanswer_set
#collect experiments mediatype from db to session['type'].
#This is later used in task.html to determine page layout based on stimulus type
mediatype = page.query.filter_by(experiment_idexperiment=session['exp_id']).first()
# collect experiments mediatype from db to session['type'].
# This is later used in task.html to determine page layout based on stimulus type
mediatype = page.query.filter_by(
experiment_idexperiment=session['exp_id']).first()
if mediatype:
session['type'] = mediatype.type
else:
......@@ -189,39 +180,40 @@ def register():
form = RegisterForm(request.form)
questions_and_options = {}
questions = background_question.query.filter_by(experiment_idexperiment=session['exp_id']).all()
questions = background_question.query.filter_by(
experiment_idexperiment=session['exp_id']).all()
for q in questions:
options = background_question_option.query.filter_by(background_question_idbackground_question=q.idbackground_question).all()
options_list = [(o.option, o.idbackground_question_option) for o in options]
questions_and_options[q.idbackground_question, q.background_question] = options_list
options = background_question_option.query.filter_by(
background_question_idbackground_question=q.idbackground_question).all()
options_list = [(o.option, o.idbackground_question_option)
for o in options]
questions_and_options[q.idbackground_question,
q.background_question] = options_list
form.questions1 = questions_and_options
if request.method == 'POST'and form.validate():
if request.method == 'POST' and form.validate():
data = request.form.to_dict()
for key, value in data.items():
# tähän db insertit
#tähän db insertit
#flash(key)
#flash(value)
#Input registration page answers to database
participant_background_question_answers = background_question_answer(answer_set_idanswer_set=session['answer_set'], answer=value, background_question_idbackground_question=key)
# flash(key)
# flash(value)
# Input registration page answers to database
participant_background_question_answers = background_question_answer(
answer_set_idanswer_set=session['answer_set'], answer=value, background_question_idbackground_question=key)
db.session.add(participant_background_question_answers)
db.session.commit()
return redirect('/instructions')
return render_template('register.html', form=form)
@app.route('/begin_with_id', methods=['GET', 'POST'])
def begin_with_id():
'''Begin experiment with experiment ID. GET -method returns login page for starting the
......@@ -241,15 +233,17 @@ def begin_with_id():
variable = form.participant_id.data
#check if participant ID is found from db with this particular ID. If a match is found inform about error
participant = answer_set.query.filter(and_(answer_set.session==variable, answer_set.experiment_idexperiment==exp_id)).first()
is_id_valid = forced_id.query.filter(and_(forced_id.pregenerated_id==variable, forced_id.experiment_idexperiment==exp_id)).first()
# check if participant ID is found from db with this particular ID. If a match is found inform about error
participant = answer_set.query.filter(and_(
answer_set.session == variable, answer_set.experiment_idexperiment == exp_id)).first()
is_id_valid = forced_id.query.filter(and_(
forced_id.pregenerated_id == variable, forced_id.experiment_idexperiment == exp_id)).first()
if participant is not None:
flash(_('ID already in use'))
return redirect(url_for('begin_with_id', exp_id=exp_id))
#if there was not a participant already in DB:
# if there was not a participant already in DB:
if participant is None:
if is_id_valid is None:
......@@ -257,7 +251,7 @@ def begin_with_id():
return redirect(url_for('begin_with_id', exp_id=exp_id))
else:
#save the participant ID in session list for now, this is deleted after the session has been started in participant_session-view
# save the participant ID in session list for now, this is deleted after the session has been started in participant_session-view
session['begin_with_id'] = form.participant_id.data
return render_template('consent.html', exp_id=exp_id, experiment_info=experiment_info, instruction_paragraphs=instruction_paragraphs, consent_paragraphs=consent_paragraphs)
......@@ -274,15 +268,16 @@ def admin_dryrun():
if form.validate_on_submit():
#check if participant ID is found from db with this particular ID. If a match is found inform about error
participant = answer_set.query.filter(and_(answer_set.session==form.participant_id.data, answer_set.experiment_idexperiment==exp_id)).first()
# check if participant ID is found from db with this particular ID. If a match is found inform about error
participant = answer_set.query.filter(and_(
answer_set.session == form.participant_id.data, answer_set.experiment_idexperiment == exp_id)).first()
if participant is not None:
flash('ID already in use')
return redirect(url_for('admin_dryrun', exp_id=exp_id))
#if there was not a participant already in DB:
# if there was not a participant already in DB:
if participant is None:
#save the participant ID in session list for now, this is deleted after the session has been started in participant_session-view
# save the participant ID in session list for now, this is deleted after the session has been started in participant_session-view
session['begin_with_id'] = form.participant_id.data
return render_template('consent.html', exp_id=exp_id, experiment_info=experiment_info)
......@@ -293,7 +288,8 @@ def admin_dryrun():
def instructions():
participant_id = session['user']
instructions = experiment.query.filter_by(idexperiment = session['exp_id']).first()
instructions = experiment.query.filter_by(
idexperiment=session['exp_id']).first()
instruction_paragraphs = str(instructions.instruction)
instruction_paragraphs = instruction_paragraphs.split('<br>')
......@@ -308,7 +304,8 @@ def login():
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
user_details = user.query.filter_by(username=form.username.data).first()
user_details = user.query.filter_by(
username=form.username.data).first()
if user_details is None or not user_details.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
......@@ -337,121 +334,7 @@ def view_research_notification():
return render_template('view_research_notification.html', research_notification_filename=research_notification_filename)
@app.route('/download_csv')
@login_required
def download_csv():
exp_id = request.args.get('exp_id', None)
experiment_info = experiment.query.filter_by(idexperiment = exp_id).all()
# answer sets with participant ids
participants = answer_set.query.filter_by(experiment_idexperiment= exp_id).all()