From fe529331293d6f60bb7daa18337470382b8b71d9 Mon Sep 17 00:00:00 2001 From: Ossi Laine <ossi.laine@utu.fi> Date: Wed, 13 Mar 2019 12:22:37 +0200 Subject: [PATCH] Refactoring routes.py file to own blueprints (create, experiment, task) --- .gitignore | 1 + .gitmodules | 3 + app/__init__.py | 25 +- .../templates/create_experiment.html | 0 .../create_experiment_bgquestions.html | 0 .../create_experiment_questions.html | 0 .../create_experiment_upload_stimuli.html | 1 + app/create/views.py | 203 ++ app/experiment/__init__.py | 0 .../edit_bg_question.html | 0 .../templates/add_bg_question.html | 0 .../templates/add_questions.html | 0 .../templates/add_stimuli.html | 15 +- .../templates/edit_experiment.html | 2 +- .../templates/edit_question.html | 0 .../templates/edit_stimuli.html | 0 .../templates/experiment_statistics.html | 0 .../templates/remove_experiment.html | 2 +- .../upload_research_notification.html | 0 .../templates/view_experiment.html | 52 +- .../templates/view_forced_id_list.html | 0 app/experiment/views.py | 953 +++++++++ app/routes.py | 1768 +---------------- app/static/img/dummyG.png | Bin 0 -> 82796 bytes app/task/__init__.py | 0 app/{ => task}/templates/continue_task.html | 0 app/{ => task}/templates/quit_task.html | 0 app/{ => task}/templates/task.html | 38 +- app/{ => task}/templates/task_completed.html | 0 app/task/views.py | 220 ++ app/templates/base.html | 2 +- app/templates/index.html | 36 +- app/templates/test_page.html | 12 - config.py | 7 +- create_rating_db.txt | 8 +- 35 files changed, 1523 insertions(+), 1825 deletions(-) create mode 100644 .gitmodules rename app/{ => create}/templates/create_experiment.html (100%) rename app/{ => create}/templates/create_experiment_bgquestions.html (100%) rename app/{ => create}/templates/create_experiment_questions.html (100%) rename app/{ => create}/templates/create_experiment_upload_stimuli.html (99%) create mode 100644 app/create/views.py create mode 100644 app/experiment/__init__.py rename app/{templates => experiment}/edit_bg_question.html (100%) rename app/{ => experiment}/templates/add_bg_question.html (100%) rename app/{ => experiment}/templates/add_questions.html (100%) rename app/{ => experiment}/templates/add_stimuli.html (94%) rename app/{ => experiment}/templates/edit_experiment.html (94%) rename app/{ => experiment}/templates/edit_question.html (100%) rename app/{ => experiment}/templates/edit_stimuli.html (100%) rename app/{ => experiment}/templates/experiment_statistics.html (100%) rename app/{ => experiment}/templates/remove_experiment.html (88%) rename app/{ => experiment}/templates/upload_research_notification.html (100%) rename app/{ => experiment}/templates/view_experiment.html (85%) rename app/{ => experiment}/templates/view_forced_id_list.html (100%) create mode 100644 app/experiment/views.py create mode 100644 app/static/img/dummyG.png create mode 100644 app/task/__init__.py rename app/{ => task}/templates/continue_task.html (100%) rename app/{ => task}/templates/quit_task.html (100%) rename app/{ => task}/templates/task.html (94%) rename app/{ => task}/templates/task_completed.html (100%) create mode 100644 app/task/views.py delete mode 100644 app/templates/test_page.html diff --git a/.gitignore b/.gitignore index 32b42d4..876e85f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ scrap_script.py config.py *.pyc *.db +/embody diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f2923ec --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "embody"] + path = embody + url = https://version.aalto.fi/gitlab/eglerean/embody.git diff --git a/app/__init__.py b/app/__init__.py index 4051a1c..aefa4b3 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,16 +1,18 @@ -from flask import Flask + +import pymysql + +from flask import Flask, request, session, flash + from flask_bootstrap import Bootstrap -from config import Config from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from flask_login import LoginManager from flask_babel import Babel -from flask import request -from flask import session -from flask import flash -import pymysql + +from config import Config app = Flask(__name__) + #app.config['BABEL_DEFAULT_LOCALE'] = 'fin' #app.config['BABEL_TRANSLATION_DIRECTORIES'] ='C:/Users/Timo/git/pet-rating/app/translations' babel = Babel(app) @@ -38,13 +40,10 @@ def get_locale(): if session['lang'] == 'zh': session['language'] = 'Chinese' - - return session.get('lang', 'en') - """ @babel.localeselector def get_locale(): @@ -73,6 +72,14 @@ migrate = Migrate(app, db) login = LoginManager(app) login.login_view = 'login' +# Register blueprints +from .task.views import task_blueprint +from .experiment.views import experiment_blueprint +from .create.views import create_blueprint + +app.register_blueprint(task_blueprint) +app.register_blueprint(experiment_blueprint) +app.register_blueprint(create_blueprint) app.secret_key = 'random string' """app.secret_key = os.urandom(24)""" diff --git a/app/templates/create_experiment.html b/app/create/templates/create_experiment.html similarity index 100% rename from app/templates/create_experiment.html rename to app/create/templates/create_experiment.html diff --git a/app/templates/create_experiment_bgquestions.html b/app/create/templates/create_experiment_bgquestions.html similarity index 100% rename from app/templates/create_experiment_bgquestions.html rename to app/create/templates/create_experiment_bgquestions.html diff --git a/app/templates/create_experiment_questions.html b/app/create/templates/create_experiment_questions.html similarity index 100% rename from app/templates/create_experiment_questions.html rename to app/create/templates/create_experiment_questions.html diff --git a/app/templates/create_experiment_upload_stimuli.html b/app/create/templates/create_experiment_upload_stimuli.html similarity index 99% rename from app/templates/create_experiment_upload_stimuli.html rename to app/create/templates/create_experiment_upload_stimuli.html index 979ed4a..9a7d9e3 100644 --- a/app/templates/create_experiment_upload_stimuli.html +++ b/app/create/templates/create_experiment_upload_stimuli.html @@ -42,6 +42,7 @@ Audio </label> </div> + </fieldset> <div class="form-group"> diff --git a/app/create/views.py b/app/create/views.py new file mode 100644 index 0000000..bca9240 --- /dev/null +++ b/app/create/views.py @@ -0,0 +1,203 @@ +import os +from datetime import datetime + +from flask import ( + Flask, + render_template, + request, + session, + flash, + redirect, + url_for, + Blueprint +) + +from flask_login import login_required + +from app.routes import APP_ROOT +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 +from app.models import background_question_option +from app.models import answer_set, answer, forced_id +from app.models import user, trial_randomization +from app.forms import ( + CreateExperimentForm, CreateBackgroundQuestionForm, + CreateQuestionForm, UploadStimuliForm +) + +create_blueprint = Blueprint("create", __name__, + template_folder='templates', + #static_folder='static', + url_prefix='/create') + +@create_blueprint.route('/experiment', methods=['GET', 'POST']) +@login_required +def create_experiment(): + + form = CreateExperimentForm(request.form) + + if request.method == 'POST' and form.validate(): + + the_time = datetime.now() + the_time = the_time.replace(microsecond=0) + + new_exp = experiment(name=request.form['name'], instruction=request.form['instruction'], language=request.form['language'], status='Hidden', randomization='Off', single_sentence_instruction=request.form['single_sentence_instruction'], short_instruction=request.form['short_instruction'], creator_name=request.form['creator_name'], is_archived='False', creation_time=the_time, stimulus_size='7', consent_text=request.form['consent_text'], use_forced_id='Off') + db.session.add(new_exp) + db.session.commit() + exp_id = new_exp.idexperiment + + #data = request.form.to_dict() + #for key, value in data.items(): + #tähän db insertit + + #flash(key) + #flash(value) + #flash('{}'.format(form.name.data)) + + #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(url_for('create.experiment_bgquestions', exp_id=exp_id)) + + return render_template('create_experiment.html', form=form) + + +@create_blueprint.route('/experiment_bgquestions', methods=['GET', 'POST']) +@login_required +def experiment_bgquestions(): + + exp_id = request.args.get('exp_id', None) + form = CreateBackgroundQuestionForm(request.form) + + if request.method == 'POST' and form.validate(): + str = form.bg_questions_and_options.data + + #Split the form data into a list that separates questions followed by the corresponding options + str_list = str.split('/n') + + #Iterate through the questions and options list + for a in range(len(str_list)): + + #Split the list cells further into questions and options + list = str_list[a].split(';') + + #Input the first item of the list as a question in db and the items followed by that as options for that question + for x in range(len(list)): + + if x == 0: + add_bgquestion = background_question(background_question=list[x], experiment_idexperiment=exp_id) + db.session.add(add_bgquestion) + db.session.commit() + + else: + add_bgq_option = background_question_option(background_question_idbackground_question=add_bgquestion.idbackground_question, option=list[x]) + db.session.add(add_bgq_option) + db.session.commit() + + return redirect(url_for('create.experiment_questions', exp_id=exp_id)) + + return render_template('create_experiment_bgquestions.html', form=form, exp_id=exp_id) + + +@create_blueprint.route('/experiment_questions', methods=['GET', 'POST']) +@login_required +def experiment_questions(): + + # TODO: add embody -type here + + exp_id = request.args.get('exp_id', None) + form = CreateQuestionForm(request.form) + + if request.method == 'POST' and form.validate(): + + str = form.questions_and_options.data + str_list = str.split('/n') + + for a in range(len(str_list)): + + list = str_list[a].split(';') + + #If there are the wrong amount of values for any of the the slider input values + #redirect back to the form + if len(list) != 3: + + flash("Error Each slider must have 3 parameters separated by ; Some slider has:") + flash(len(list)) + + return redirect(url_for('create.experiment_questions', exp_id=exp_id)) + + #If all the slider inputs were of length 3 items + #we can input them to db + for a in range(len(str_list)): + + list = str_list[a].split(';') + + add_question = question(experiment_idexperiment=exp_id, question=list[0], left=list[1], right=list[2]) + db.session.add(add_question) + db.session.commit() + + return redirect(url_for('create.experiment_upload_stimuli', exp_id=exp_id)) + + return render_template('create_experiment_questions.html', form=form) + + +@create_blueprint.route('/experiment_upload_stimuli', methods=['GET', 'POST']) +@login_required +def experiment_upload_stimuli(): + + exp_id = request.args.get('exp_id', None) + form = UploadStimuliForm(request.form) + + if request.method == 'POST' and form.validate(): + + #If stimulus type is text lets parse the information and insert it to database + if form.type.data == 'text': + string = form.text.data + str_list = string.split('/n') + + for a in range(len(str_list)): + add_text_stimulus = page(experiment_idexperiment=exp_id, type='text', text=str_list[a], media='none') + db.session.add(add_text_stimulus) + db.session.commit() + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + else: + #Upload stimuli into /static/experiment_stimuli/exp_id folder + #Create the pages for the stimuli by inserting experiment_id, stimulus type, text and names of the stimulus files (as a path to the folder) + + path = 'static/experiment_stimuli/' + str(exp_id) + target = os.path.join(APP_ROOT, path) + + if not os.path.isdir(target): + os.mkdir(target) + + #This returns a list of filenames: request.files.getlist("file") + for file in request.files.getlist("file"): + + #save files in the correct folder + filename = file.filename + destination = "/".join([target, filename]) + file.save(destination) + + #add pages to the db + db_path = path + str('/') + str(filename) + new_page = page(experiment_idexperiment=exp_id, type=form.type.data, media=db_path) + + db.session.add(new_page) + db.session.commit() + + #flash("Succes!") + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + return redirect(url_for('create.experiment_upload_stimuli', exp_id=exp_id)) + + return render_template('create_experiment_upload_stimuli.html', form=form) + + +# EOF \ No newline at end of file diff --git a/app/experiment/__init__.py b/app/experiment/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/templates/edit_bg_question.html b/app/experiment/edit_bg_question.html similarity index 100% rename from app/templates/edit_bg_question.html rename to app/experiment/edit_bg_question.html diff --git a/app/templates/add_bg_question.html b/app/experiment/templates/add_bg_question.html similarity index 100% rename from app/templates/add_bg_question.html rename to app/experiment/templates/add_bg_question.html diff --git a/app/templates/add_questions.html b/app/experiment/templates/add_questions.html similarity index 100% rename from app/templates/add_questions.html rename to app/experiment/templates/add_questions.html diff --git a/app/templates/add_stimuli.html b/app/experiment/templates/add_stimuli.html similarity index 94% rename from app/templates/add_stimuli.html rename to app/experiment/templates/add_stimuli.html index 334ce1f..113b828 100644 --- a/app/templates/add_stimuli.html +++ b/app/experiment/templates/add_stimuli.html @@ -19,24 +19,15 @@ </div> <div class="custom-file"> <input type="file" class="custom-file-input" id="file" name="file" accept="audio/*,image/*, .mp4" multiple> + + <!-- TODO add hidden type field { stimulus_type } else this will be None!! --> + <label class="custom-file-label" for="upload_file">Choose file</label> </div> </div> {% endif %} - - - - - - - - - - - - <br> <button type="submit" class="btn btn-primary">Submit</button> <a class="btn btn-primary" href="{{ request.referrer }}" role="button">Cancel</a> diff --git a/app/templates/edit_experiment.html b/app/experiment/templates/edit_experiment.html similarity index 94% rename from app/templates/edit_experiment.html rename to app/experiment/templates/edit_experiment.html index daac537..72174f7 100644 --- a/app/templates/edit_experiment.html +++ b/app/experiment/templates/edit_experiment.html @@ -44,7 +44,7 @@ </div> <br> <button type="submit" class="btn btn-primary">Update</button> - <a class="btn btn-primary" href="{{ url_for('view_experiment', exp_id=exp_id) }}" role="button">Cancel</a> + <a class="btn btn-primary" href="{{ url_for('experiment.view', exp_id=exp_id) }}" role="button">Cancel</a> </form> diff --git a/app/templates/edit_question.html b/app/experiment/templates/edit_question.html similarity index 100% rename from app/templates/edit_question.html rename to app/experiment/templates/edit_question.html diff --git a/app/templates/edit_stimuli.html b/app/experiment/templates/edit_stimuli.html similarity index 100% rename from app/templates/edit_stimuli.html rename to app/experiment/templates/edit_stimuli.html diff --git a/app/templates/experiment_statistics.html b/app/experiment/templates/experiment_statistics.html similarity index 100% rename from app/templates/experiment_statistics.html rename to app/experiment/templates/experiment_statistics.html diff --git a/app/templates/remove_experiment.html b/app/experiment/templates/remove_experiment.html similarity index 88% rename from app/templates/remove_experiment.html rename to app/experiment/templates/remove_experiment.html index 03af89c..fddf761 100644 --- a/app/templates/remove_experiment.html +++ b/app/experiment/templates/remove_experiment.html @@ -19,7 +19,7 @@ <input type="text" class="form-control" id="remove" name="remove" required> <br> <button type="submit" class="btn btn-primary">Remove</button> - <a class="btn btn-primary" href="{{ url_for('view_experiment', exp_id=exp_id) }}" role="button">Cancel</a> + <a class="btn btn-primary" href="{{ url_for('experiment.view', exp_id=exp_id) }}" role="button">Cancel</a> </form> diff --git a/app/templates/upload_research_notification.html b/app/experiment/templates/upload_research_notification.html similarity index 100% rename from app/templates/upload_research_notification.html rename to app/experiment/templates/upload_research_notification.html diff --git a/app/templates/view_experiment.html b/app/experiment/templates/view_experiment.html similarity index 85% rename from app/templates/view_experiment.html rename to app/experiment/templates/view_experiment.html index 5553b58..1574471 100644 --- a/app/templates/view_experiment.html +++ b/app/experiment/templates/view_experiment.html @@ -32,7 +32,7 @@ </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> - <a class="btn btn-primary" href="{{ url_for('remove_experiment', exp_id=exp.idexperiment) }}" role="button">Yes, remove</a> + <a class="btn btn-primary" href="{{ url_for('experiment.remove', exp_id=exp.idexperiment) }}" role="button">Yes, remove</a> </div> </div> </div> @@ -45,12 +45,12 @@ <td>{{ exp.status }}</td> <td> {% if exp.status == 'Hidden' %} - <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('publish_experiment', exp_id=exp.idexperiment) }}" role="button">Publish (visible)</a> - <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('private_experiment', exp_id=exp.idexperiment) }}" role="button">Publish (private)</a> + <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.publish', exp_id=exp.idexperiment) }}" role="button">Publish (visible)</a> + <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.private', exp_id=exp.idexperiment) }}" role="button">Publish (private)</a> </td> {% endif %} {% if exp.status != 'Hidden' %} - <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('hide_experiment', exp_id=exp.idexperiment) }}" role="button">Unpublish</a></td> + <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.hide', exp_id=exp.idexperiment) }}" role="button">Unpublish</a></td> {% endif %} </tr> @@ -81,10 +81,10 @@ <td>{{ exp.randomization }}</td> <td nowrap> {% if exp.randomization == 'Off' %} - <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('enable_randomization', exp_id=exp.idexperiment) }}" role="button">Enable</a></td> + <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.randomization', exp_id=exp.idexperiment, set='On') }}" role="button">Enable</a></td> {% endif %} {% if exp.randomization == 'On' %} - <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('disable_randomization', exp_id=exp.idexperiment) }}" role="button">Disable</a></td> + <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.randomization', exp_id=exp.idexperiment, set='Off') }}" role="button">Disable</a></td> {% endif %} </td> @@ -93,13 +93,13 @@ <td>{{ exp.use_forced_id }}</td> <td nowrap> {% if exp.use_forced_id == 'Off' %} - <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('enable_forced_id', exp_id=exp.idexperiment) }}" role="button">Enable</a> + <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.set_forced_id', exp_id=exp.idexperiment, set='On') }}" role="button">Enable</a> {% endif %} {% if exp.use_forced_id == 'On' %} - <a class="btn btn-primary w-50 btn-sm btn-info" href="{{ url_for('disable_forced_id', exp_id=exp.idexperiment) }}" role="button">Disable</a> - <a class="btn btn-primary w-50 btn-sm btn-info" href="{{ url_for('view_forced_id_list', exp_id=exp.idexperiment) }}" role="button">Check ID</a> + <a class="btn btn-primary w-50 btn-sm btn-info" href="{{ url_for('experiment.set_forced_id', exp_id=exp.idexperiment, set='Off') }}" role="button">Disable</a> + <a class="btn btn-primary w-50 btn-sm btn-info" href="{{ url_for('experiment.view_forced_id_list', exp_id=exp.idexperiment) }}" role="button">Check ID</a> {% endif %} </td> @@ -108,9 +108,9 @@ <td> Research bulletin:</td> <td> {{ exp.research_notification_filename }}</td> {% if exp.research_notification_filename %} - <td> <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('remove_research_notification', exp_id=exp.idexperiment) }}" role="button">Remove</a></td> + <td> <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.remove_research_notification', exp_id=exp.idexperiment) }}" role="button">Remove</a></td> {% else %} - <td> <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('upload_research_notification', exp_id=exp.idexperiment) }}" role="button">Upload</a></td> + <td> <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.upload_research_notification', exp_id=exp.idexperiment) }}" role="button">Upload</a></td> {% endif %} </tr> @@ -121,7 +121,7 @@ <td class="text-justify">{{ exp.instruction }}</td> <td nowrap> - <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('edit_experiment', exp_id=exp.idexperiment) }}" role="button">Edit properties</a> + <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.edit', exp_id=exp.idexperiment) }}" role="button">Edit properties</a> </td> </tr> <tr> @@ -190,12 +190,12 @@ </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> - <a class="btn btn-primary" href="{{ url_for('remove_bg_question', idbackground_question=options[0], exp_id=exp_id) }}" role="button">Yes, remove</a> + <a class="btn btn-primary" href="{{ url_for('experiment.remove_bg_question', idbackground_question=options[0], exp_id=exp_id) }}" role="button">Yes, remove</a> </div> </div> </div> </div> - <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('edit_bg_question', idbackground_question=options[0]) }}" role="button">Edit</a> + <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('experiment.edit_bg_question', idbackground_question=options[0]) }}" role="button">Edit</a> </td> </tr> @@ -206,7 +206,7 @@ <table class="table"> <tbody> <td class="text-nowrap align-bottom text-right col-8"> - <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('add_bg_question', exp_id=exp_id) }}" role="button">Add more</a> + <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('experiment.add_bg_question', exp_id=exp_id) }}" role="button">Add more</a> </td> </tbody> </table> @@ -214,6 +214,9 @@ <h1 class="container mt-5 display-4 text-left"><br>Rating set:</h1> + +<hr> +TODO: add embody tool <hr> <br> {% for category in categories1 %} @@ -247,12 +250,12 @@ </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> - <a class="btn btn-primary" href="{{ url_for('remove_question', idquestion=category[0], exp_id=exp_id) }}" role="button">Yes, remove</a> + <a class="btn btn-primary" href="{{ url_for('experiment.remove_question', idquestion=category[0], exp_id=exp_id) }}" role="button">Yes, remove</a> </div> </div> </div> </div> - <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('edit_question', idquestion=category[0]) }}" role="button">Edit</a> + <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('experiment.edit_question', idquestion=category[0]) }}" role="button">Edit</a> </div> @@ -264,7 +267,7 @@ <table class="table"> <tbody> <td class="text-nowrap align-bottom text-right col-8"> - <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('add_questions', exp_id=exp_id) }}" role="button">Add more</a> + <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('experiment.add_questions', exp_id=exp_id) }}" role="button">Add more</a> </td> </tbody> </table> @@ -304,12 +307,12 @@ </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> - <a class="btn btn-primary" href="{{ url_for('remove_page', idpage=page.idpage, exp_id=exp_id) }}" role="button">Yes, remove</a> + <a class="btn btn-primary" href="{{ url_for('experiment.remove_stimuli', idpage=page.idpage, exp_id=exp_id) }}" role="button">Yes, remove</a> </div> </div> </div> </div> - <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('edit_stimuli', idpage=page.idpage, exp_id=exp_id) }}" role="button">Edit</a> + <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('experiment.edit_stimuli', idpage=page.idpage, exp_id=exp_id) }}" role="button">Edit</a> </td> </tr> {% endfor %} @@ -317,7 +320,7 @@ <table class="table"> <tbody> <td class="text-nowrap align-bottom text-right col-8"> - <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('add_stimuli', exp_id=exp_id, stimulus_type=mtype.type) }}" role="button">Add more</a> + <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('experiment.add_stimuli', exp_id=exp_id, stimulus_type=mtype.type) }}" role="button">Add more</a> </td> </tbody> </table> @@ -346,12 +349,12 @@ </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> - <a class="btn btn-primary" href="{{ url_for('remove_page', idpage=page.idpage, exp_id=exp_id) }}" role="button">Yes, remove</a> + <a class="btn btn-primary" href="{{ url_for('experiment.remove_stimuli', idpage=page.idpage, exp_id=exp_id) }}" role="button">Yes, remove</a> </div> </div> </div> </div> - <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('edit_stimuli', idpage=page.idpage, exp_id=exp_id) }}" role="button">Replace</a> + <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('experiment.edit_stimuli', idpage=page.idpage, exp_id=exp_id) }}" role="button">Replace</a> </td> </tr> @@ -360,7 +363,8 @@ <table class="table"> <tbody> <td class="text-nowrap align-bottom text-right col-8"> - <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('add_stimuli', exp_id=exp_id, stimulus_type=mtype.type) }}" role="button">Add more</a> + {{ mtype }} + <a class="btn btn-primary btn-sm btn-info" href="{{ url_for('experiment.add_stimuli', exp_id=exp_id, stimulus_type=mtype.type) }}" role="button">Add more</a> </td> </tbody> </table> diff --git a/app/templates/view_forced_id_list.html b/app/experiment/templates/view_forced_id_list.html similarity index 100% rename from app/templates/view_forced_id_list.html rename to app/experiment/templates/view_forced_id_list.html diff --git a/app/experiment/views.py b/app/experiment/views.py new file mode 100644 index 0000000..360f7ec --- /dev/null +++ b/app/experiment/views.py @@ -0,0 +1,953 @@ + + +import os +import secrets + +from flask import ( + Flask, + render_template, + request, + session, + flash, + redirect, + url_for, + Blueprint +) + +from wtforms import Form +from sqlalchemy import and_, update +from flask_login import login_required + +from app import app, db +from app.routes import APP_ROOT +from app.models import background_question, experiment +from app.models import background_question_answer +from app.models import page, question +from app.models import background_question_option +from app.models import answer_set, answer, forced_id +from app.models import user, trial_randomization +from app.forms import ( + CreateBackgroundQuestionForm, + CreateQuestionForm, UploadStimuliForm, EditBackgroundQuestionForm, + EditQuestionForm, EditExperimentForm, UploadResearchBulletinForm, + EditPageForm, RemoveExperimentForm, GenerateIdForm +) + +#Stimuli upload folder setting +#APP_ROOT = os.path.dirname(os.path.abspath(__file__)) + +experiment_blueprint = Blueprint("experiment", __name__, + template_folder='templates', + #static_folder='static', + url_prefix='/experiment') + + +@experiment_blueprint.route('/view') +@login_required +def view(): + + #crap:3lines + exp_id = request.args.get('exp_id', None) + media = page.query.filter_by(experiment_idexperiment=exp_id).all() + + ## TODO: mtype can be multiple! + ## is this even necessary anymore? + mtype = page.query.filter_by(experiment_idexperiment=exp_id).first() + + #experiment info + experiment_info = experiment.query.filter_by(idexperiment = exp_id).all() + + #background questions + questions_and_options = {} + questions = background_question.query.filter_by(experiment_idexperiment=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 + + questions1 = questions_and_options + + #sliderset + categories_and_scales = {} + categories = question.query.filter_by(experiment_idexperiment=exp_id).all() + + for cat in categories: + + scale_list = [(cat.left, cat.right)] + categories_and_scales[cat.idquestion, cat.question] = scale_list + + categories1 = categories_and_scales + + return render_template('view_experiment.html', exp_id=exp_id, media=media, mtype=mtype, experiment_info=experiment_info, categories1=categories1, questions1=questions1) + + + + + + + + + +# Experiment info: + +@experiment_blueprint.route('/remove', methods=['GET', 'POST']) +@login_required +def remove(): + + exp_id = request.args.get('exp_id', None) + exp_status = experiment.query.filter_by(idexperiment=exp_id).first() + + if exp_status.status != 'Hidden': + + flash("Experiment is public. Cannot modify structure.") + return redirect(url_for('experiment.view', exp_id=exp_id)) + + else: + + form = RemoveExperimentForm(request.form) + + if request.method == 'POST' and form.validate(): + + if form.remove.data == 'DELETE': + + #This removes all experiment data from the database! + + #Remove research bulletin if it exists + empty_filevariable = experiment.query.filter_by(idexperiment=exp_id).first() + + if empty_filevariable.research_notification_filename is not None: + target = os.path.join(APP_ROOT, empty_filevariable.research_notification_filename) + + if os.path.exists(target): + os.remove(target) + + #Tables + remove_forced_id = forced_id.query.filter_by(experiment_idexperiment=exp_id).all() + + for b in range(len(remove_forced_id)): + db.session.delete(remove_forced_id[b]) + db.session.commit() + + #background_question_option & background_question & background question answers: + remove_background_question = background_question.query.filter_by(experiment_idexperiment=exp_id).all() + + #cycle through all bg questions and delete their options + for a in range(len(remove_background_question)): + + remove_background_question_option = background_question_option.query.filter_by(background_question_idbackground_question=remove_background_question[a].idbackground_question).all() + + for b in range(len(remove_background_question_option)): + + db.session.delete(remove_background_question_option[b]) + db.session.commit() + + #Remove all background questions and all answers given to each bg question + for a in range(len(remove_background_question)): + + remove_background_question_answers = background_question_answer.query.filter_by(background_question_idbackground_question=remove_background_question[a].idbackground_question).all() + + for b in range(len(remove_background_question_answers)): + db.session.delete(remove_background_question_answers[b]) + db.session.commit() + + db.session.delete(remove_background_question[a]) + db.session.commit() + + #Remove all questions and answers + remove_question = question.query.filter_by(experiment_idexperiment=exp_id).all() + + for a in range(len(remove_question)): + + remove_question_answers = answer.query.filter_by(question_idquestion=remove_question[a].idquestion).all() + + for b in range(len(remove_question_answers)): + db.session.delete(remove_question_answers[b]) + db.session.commit() + + db.session.delete(remove_question[a]) + db.session.commit() + + #Remove all pages and datafiles + remove_pages = page.query.filter_by(experiment_idexperiment=exp_id).all() + + for a in range(len(remove_pages)): + if remove_pages[a].type == 'text': + db.session.delete(remove_pages[a]) + db.session.commit() + else: + target = os.path.join(APP_ROOT, remove_pages[a].media) + + if os.path.exists(target): + os.remove(target) + + #Now that the files are removed we can delete the page + db.session.delete(remove_pages[a]) + db.session.commit() + + #Remove all answer_sets and trial_randomization orders + remove_answer_set = answer_set.query.filter_by(experiment_idexperiment=exp_id).all() + + for a in range(len(remove_answer_set)): + remove_trial_randomizations = trial_randomization.query.filter_by(answer_set_idanswer_set=remove_answer_set[a].idanswer_set).all() + + for b in range(len(remove_trial_randomizations)): + db.session.delete(remove_trial_randomizations[b]) + db.session.commit() + + db.session.delete(remove_answer_set[a]) + db.session.commit() + + #Remove experiment table + remove_experiment = experiment.query.filter_by(idexperiment=exp_id).first() + db.session.delete(remove_experiment) + db.session.commit() + + flash("Experiment was removed from database!") + return redirect(url_for('index')) + + else: + flash("Experiment was not removed!") + return redirect(url_for('experiment.view', exp_id=exp_id)) + + return render_template('remove_experiment.html', form=form, exp_id=exp_id) + + +@experiment_blueprint.route('/publish') +@login_required +def publish(): + exp_id = request.args.get('exp_id', None) + publish_experiment = experiment.query.filter_by(idexperiment = exp_id).first() + publish_experiment.status = 'Public' + flash("Changed status to Public") + db.session.commit() + return redirect(url_for('experiment.view', exp_id=exp_id)) + + +@experiment_blueprint.route('/hide') +@login_required +def hide(): + exp_id = request.args.get('exp_id', None) + hide_experiment = experiment.query.filter_by(idexperiment = exp_id).first() + hide_experiment.status = 'Hidden' + flash("Changed status to Hidden") + db.session.commit() + return redirect(url_for('experiment.view', exp_id=exp_id)) + + +@experiment_blueprint.route('/private') +@login_required +def private(): + exp_id = request.args.get('exp_id', None) + private_experiment = experiment.query.filter_by(idexperiment = exp_id).first() + private_experiment.status = 'Private' + flash("Changed status to Private") + db.session.commit() + return redirect(url_for('experiment.view', exp_id=exp_id)) + + +@experiment_blueprint.route('/randomization') +@login_required +def randomization(): + exp_id = request.args.get('exp_id', None) + status = request.args.get('set') + + if status == 'On': + flash("Enabled trial randomization") + elif status == 'Off': + flash("Disabled trial randomization") + + experiment.query.filter_by(idexperiment = exp_id).first().randomization = status + db.session.commit() + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + +@experiment_blueprint.route('/set_forced_id') +@login_required +def set_forced_id(): + '''By using forced ID login subjects can only participate to a rating task + by logging in with a pregenerated ID.''' + + exp_id = request.args.get('exp_id', None) + status = request.args.get('set') + + if status == 'On': + flash("Enabled forced ID login") + elif status == 'Off': + flash("Disabled forced ID login") + + experiment.query.filter_by(idexperiment = exp_id).first().use_forced_id = status + db.session.commit() + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + +@experiment_blueprint.route('/view_forced_id_list', methods=['GET', 'POST']) +@login_required +def view_forced_id_list(): + '''Forced ID login for participants''' + + exp_id = request.args.get('exp_id', None) + id_list = forced_id.query.filter_by(experiment_idexperiment=exp_id).all() + form = GenerateIdForm(request.form) + + if request.method == 'POST' and form.validate(): + + for i in range(int(request.form['number'])): + random_id = str(request.form['string']) + str(secrets.token_hex(3)) + check_answer_set = answer_set.query.filter_by(session=random_id).first() + check_forced_id = forced_id.query.filter_by(pregenerated_id=random_id).first() + + #here we check if the generated id is found from given answers from the whole database in answer_set table + #or from forced_id table. If so another id is generated instead to avoid a duplicate + if check_answer_set is not None or check_forced_id is not None: + + #flash("ID already existed; generated a new one") + random_id = secrets.token_hex(3) + check_answer_set = answer_set.query.filter_by(session=random_id).first() + check_forced_id = forced_id.query.filter_by(pregenerated_id=random_id).first() + + input_id = forced_id(experiment_idexperiment=exp_id, pregenerated_id=random_id) + db.session.add(input_id) + db.session.commit() + + return redirect(url_for('view_forced_id_list', exp_id=exp_id)) + + return render_template('view_forced_id_list.html', exp_id=exp_id, id_list=id_list) + + +@experiment_blueprint.route('/upload_research_notification', methods=['GET', 'POST']) +@login_required +def upload_research_notification(): + '''Upload research bulletin''' + + exp_id = request.args.get('exp_id', None) + form = UploadResearchBulletinForm(request.form) + + if request.method == 'POST': + path = 'static/experiment_stimuli/' + str(exp_id) + target = os.path.join(APP_ROOT, path) + + if not os.path.isdir(target): + os.mkdir(target) + + #This returns a list of filenames: request.files.getlist("file") + for file in request.files.getlist("file"): + #save files in the correct folder + filename = file.filename + destination = "/".join([target, filename]) + file.save(destination) + + #add pages to the db + db_path = path + str('/') + str(filename) + bulletin = experiment.query.filter_by(idexperiment=exp_id).first() + bulletin.research_notification_filename = db_path + db.session.commit() + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + return render_template('upload_research_notification.html', exp_id=exp_id, form=form) + + +@experiment_blueprint.route('/remove_research_notification') +@login_required +def remove_research_notification(): + '''Remove research bulletin''' + + exp_id = request.args.get('exp_id', None) + empty_filevariable = experiment.query.filter_by(idexperiment=exp_id).first() + target = os.path.join(APP_ROOT, empty_filevariable.research_notification_filename) + + if os.path.exists(target): + os.remove(target) + + empty_filevariable.research_notification_filename = None + db.session.commit() + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + +@experiment_blueprint.route('/edit', methods=['GET', 'POST']) +@login_required +def edit(): + '''Edit experiment details''' + + exp_id = request.args.get('exp_id', None) + current_experiment = experiment.query.filter_by(idexperiment=exp_id).first() + + form = EditExperimentForm(request.form, obj=current_experiment) + form.language.default = current_experiment.language + + if request.method == 'POST' and form.validate(): + + form.populate_obj(current_experiment) + db.session.commit() + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + return render_template('edit_experiment.html', form=form, exp_id=exp_id) + + + + + +# Background questions: + +@experiment_blueprint.route('/add_bg_question', methods=['GET', 'POST']) +@login_required +def add_bg_question(): + + exp_id = request.args.get('exp_id', None) + exp_status = experiment.query.filter_by(idexperiment=exp_id).first() + + if exp_status.status != 'Hidden': + + flash("Experiment is public. Cannot modify structure.") + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + else: + + form = CreateBackgroundQuestionForm(request.form) + + if request.method == 'POST' and form.validate(): + + str = form.bg_questions_and_options.data + + #Split the form data into a list that separates questions followed by the corresponding options + str_list = str.split('/n') + + #Iterate through the questions and options list + for a in range(len(str_list)): + + #Split the list cells further into questions and options + list = str_list[a].split(';') + + #Input the first item of the list as a question in db and the items followed by that as options for that question + for x in range(len(list)): + + if x == 0: + #flash("Kysymys") + #flash(list[x]) + add_bgquestion = background_question(background_question=list[x], experiment_idexperiment=exp_id) + db.session.add(add_bgquestion) + db.session.commit() + + else: + #flash("optio") + #flash(list[x]) + add_bgq_option = background_question_option(background_question_idbackground_question=add_bgquestion.idbackground_question, option=list[x]) + db.session.add(add_bgq_option) + db.session.commit() + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + return render_template('add_bg_question.html', form=form) + + +@experiment_blueprint.route('/edit_bg_question', methods=['GET', 'POST']) +@login_required +def edit_bg_question(): + + bg_question_id = request.args.get('idbackground_question', None) + + #Search for the right question and for the right options. Form a string of those separated with ";" and insert the + #formed string into the edit form + current_bg_question = background_question.query.filter_by(idbackground_question=bg_question_id).first() + exp_id=current_bg_question.experiment_idexperiment + question_string = current_bg_question.background_question + options = background_question_option.query.filter_by(background_question_idbackground_question=bg_question_id).all() + + for o in range(len(options)): + + question_string = str(question_string) + str("; ") + str(options[o].option) + + form = EditBackgroundQuestionForm(request.form) + form.bg_questions_and_options.data = question_string + + #After user chooses to update the question and options lets replace the old question and options with the ones from the form + if request.method == 'POST' and form.validate(): + + + #Explode the string with new values from the form + form_values = form.new_values.data + form_values_list = form_values.split(';') + + #Check and remove possible whitespaces from string beginnings with lstrip + for x in range(len(form_values_list)): + + form_values_list[x] = form_values_list[x].lstrip() + + #Cycle through strings and update db + for x in range(len(form_values_list)): + + #Replace question and update the object to database + if x == 0: + + #flash("delete kys:") + #flash(current_bg_question.background_question) + #flash("insert kys:") + current_bg_question.background_question = form_values_list[x] + #flash(current_bg_question.background_question) + #flash(current_bg_question.idbackground_question) + #flash(current_bg_question.experiment_idexperiment) + + db.session.commit() + + #Delete old options from db + for o in options: + + db.session.delete(o) + db.session.commit() + + #Insert new options to db + else: + + #flash("insert opt:") + #flash(form_values_list[x]) + new_option = background_question_option(background_question_idbackground_question=current_bg_question.idbackground_question, option=form_values_list[x]) + db.session.add(new_option) + db.session.commit() + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + return render_template('edit_bg_question.html', form=form, exp_id=exp_id) + + +@experiment_blueprint.route('/remove_bg_question') +@login_required +def remove_bg_question(): + + exp_id = request.args.get('exp_id', None) + exp_status = experiment.query.filter_by(idexperiment=exp_id).first() + + if exp_status.status != 'Hidden': + + flash("Experiment is public. Cannot modify structure.") + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + else: + + remove_id = request.args.get('idbackground_question', None) + remove_options = background_question_option.query.filter_by(background_question_idbackground_question=remove_id).all() + + for a in range(len(remove_options)): + + #flash(remove_options[a].idbackground_question_option) + + db.session.delete(remove_options[a]) + db.session.commit() + + + remove_question = background_question.query.filter_by(idbackground_question=remove_id).first() + + db.session.delete(remove_question) + db.session.commit() + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + + + + + +# Rating set: + +@experiment_blueprint.route('/add_questions', methods=['GET', 'POST']) +@login_required +def add_questions(): + + exp_id = request.args.get('exp_id', None) + exp_status = experiment.query.filter_by(idexperiment=exp_id).first() + + if exp_status.status != 'Hidden': + + flash("Experiment is public. Cannot modify structure.") + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + else: + + form = CreateQuestionForm(request.form) + + if request.method == 'POST' and form.validate(): + + str = form.questions_and_options.data + str_list = str.split('/n') + + for a in range(len(str_list)): + + list = str_list[a].split(';') + + #If there are the right amount of values for the slider input values + if len(list) == 3: + + #flash("Question:") + #flash(list[0]) + #flash("Left:") + #flash(list[1]) + #flash("Right:") + #flash(list[2]) + + add_question = question(experiment_idexperiment=exp_id, question=list[0], left=list[1], right=list[2]) + db.session.add(add_question) + db.session.commit() + + #If slider has too many or too litlle parameters give an error and redirect back to input form + else: + flash("Error Each slider must have 3 parameters separated by ; Some slider has:") + flash(len(list)) + + return redirect(url_for('create.experiment_questions', exp_id=exp_id)) + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + return render_template('add_questions.html', form=form) + + +@experiment_blueprint.route('/edit_question', methods=['GET', 'POST']) +@login_required +def edit_question(): + + question_id = request.args.get('idquestion', None) + current_question = question.query.filter_by(idquestion=question_id).first() + form = EditQuestionForm(request.form, obj=current_question) + + if request.method == 'POST' and form.validate(): + + form.populate_obj(current_question) + db.session.commit() + + return redirect(url_for('experiment.view', exp_id=current_question.experiment_idexperiment)) + + return render_template('edit_question.html', form=form) + + +@experiment_blueprint.route('/remove_question') +@login_required +def remove_question(): + + exp_id = request.args.get('exp_id', None) + exp_status = experiment.query.filter_by(idexperiment=exp_id).first() + + if exp_status.status != 'Hidden': + flash("Experiment is public. Cannot modify structure.") + return redirect(url_for('experiment.view', exp_id=exp_id)) + + else: + + remove_id = request.args.get('idquestion', None) + remove_question = question.query.filter_by(idquestion=remove_id).first() + + db.session.delete(remove_question) + db.session.commit() + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + + + + +# Stimuli: + +@experiment_blueprint.route('/add_stimuli', methods=['GET', 'POST']) +@login_required +def add_stimuli(): + + exp_id = request.args.get('exp_id', None) + exp_status = experiment.query.filter_by(idexperiment=exp_id).first() + + if exp_status.status != 'Hidden': + flash("Experiment is public. Cannot modify structure.") + return redirect(url_for('experiment.view', exp_id=exp_id)) + else: + #If there are no pages set for the experiment lets reroute user to create experiment stimuli upload instead + + is_there_any_stimuli = page.query.filter_by(experiment_idexperiment = exp_id).first() + + if is_there_any_stimuli is None: + return redirect(url_for('create.experiment_upload_stimuli', exp_id=exp_id)) + + stimulus_type = request.args.get('stimulus_type', None) + form = UploadStimuliForm(request.form) + + if request.method == 'POST': + if stimulus_type == 'text': + + #flash("db insert text") + + string = form.text.data + str_list = string.split('/n') + + for a in range(len(str_list)): + + #flash("lisättiin:") + #flash(str_list[a]) + add_text_stimulus = page(experiment_idexperiment=exp_id, type='text', text=str_list[a], media='none') + db.session.add(add_text_stimulus) + db.session.commit() + + #flash("Succes!") + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + else: + #Upload stimuli into /static/experiment_stimuli/exp_id folder + #Create the pages for the stimuli by inserting experiment_id, stimulus type, text and names of the stimulus files (as a path to the folder) + path = 'static/experiment_stimuli/' + str(exp_id) + target = os.path.join(APP_ROOT, path) + + if not os.path.isdir(target): + os.mkdir(target) + #flash("make dir") + + #This returns a list of filenames: request.files.getlist("file") + + for file in request.files.getlist("file"): + + #save files in the correct folder + #flash(file.filename) + filename = file.filename + destination = "/".join([target, filename]) + #flash("destination") + #flash(destination) + file.save(destination) + + #add pages to the db + db_path = path + str('/') + str(filename) + + #flash("db path") + #flash(db_path) + + new_page = page(experiment_idexperiment=exp_id, type=form.type.data, media=db_path) + + db.session.add(new_page) + db.session.commit() + + #flash("Succes!") + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + return render_template('add_stimuli.html', form=form, stimulus_type=stimulus_type) + + +@experiment_blueprint.route('/edit_stimuli', methods=['GET', 'POST']) +@login_required +def edit_stimuli(): + + exp_id = request.args.get('exp_id', None) + page_id = request.args.get('idpage', None) + edit_page = page.query.filter_by(idpage=page_id).first() + form = EditPageForm(request.form, obj=edit_page) + + if request.method == 'POST' and form.validate(): + + #If the stimulus type is not text, then the old stimulus file is deleted from os and replaced + if edit_page.type != 'text': + + #remove old file + target = os.path.join(APP_ROOT, edit_page.media) + #flash("Remove:") + #flash(target) + os.remove(target) + + #upload new file + + path = 'static/experiment_stimuli/' + str(exp_id) + target = os.path.join(APP_ROOT, path) + #flash(target) + + if not os.path.isdir(target): + os.mkdir(target) + #flash("make dir") + + #This returns a list of filenames: request.files.getlist("file") + + for file in request.files.getlist("file"): + #save files in the correct folder + #flash(file.filename) + filename = file.filename + destination = "/".join([target, filename]) + #flash("destination") + #flash(destination) + file.save(destination) + + #update db object + db_path = path + str('/') + str(filename) + + #flash("db path") + #flash(db_path) + + edit_page.media=db_path + + db.session.commit() + + #flash("Succes!") + + #If editing text stimulus no need for filehandling + else: + form.populate_obj(edit_page) + db.session.commit() + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + return render_template('edit_stimuli.html', form=form, edit_page=edit_page) + + +@experiment_blueprint.route('/remove_stimuli') +@login_required +def remove_stimuli(): + + exp_id = request.args.get('exp_id', None) + exp_status = experiment.query.filter_by(idexperiment=exp_id).first() + + if exp_status.status != 'Hidden': + + flash("Experiment is public. Cannot modify structure.") + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + else: + + remove_id = request.args.get('idpage', None) + remove_page = page.query.filter_by(idpage=remove_id).first() + experiment_pages = page.query.filter_by(experiment_idexperiment=exp_id).all() + + #if stimulustype is text, the stimulus itself is text on the database, other stimulus types are real files + #on the server and need to be deleted + if remove_page.type != 'text': + + #helper variable + do_not_delete_file = 'False' + + #if the file to be deleted is in duplicate use of another page then we won't delete the file + for a in range(len(experiment_pages)): + if experiment_pages[a].media == remove_page.media and experiment_pages[a].idpage != remove_page.idpage: + do_not_delete_file = 'True' + + #If no other page is using the file then lets remove it + if do_not_delete_file == 'False': + target = os.path.join(APP_ROOT, remove_page.media) + os.remove(target) + + db.session.delete(remove_page) + db.session.commit() + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + if remove_page.type == 'text': + + db.session.delete(remove_page) + db.session.commit() + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + + +# Misc: + +@experiment_blueprint.route('/statistics') +@login_required +def statistics(): + + exp_id = request.args.get('exp_id', None) + + experiment_info = experiment.query.filter_by(idexperiment = exp_id).all() + participants = answer_set.query.filter_by(experiment_idexperiment= exp_id).all() + + + #Rating task headers + question_headers = question.query.filter_by(experiment_idexperiment=exp_id).all() + stimulus_headers = page.query.filter_by(experiment_idexperiment=exp_id).all() + + pages = page.query.filter_by(experiment_idexperiment=exp_id).all() + questions = question.query.filter_by(experiment_idexperiment=exp_id).all() + pages_and_questions = {} + + for p in pages: + + questions_list = [(p.idpage, a.idquestion) for a in questions] + pages_and_questions[p.idpage] = questions_list + + #List of answers per participant in format question Stimulus ID/Question ID + #those are in answer table as page_idpage and question_idquestion respectively + + + + """ + pages = page.query.filter_by(experiment_idexperiment=exp_id).all() + + participants_and_answers = {} + + #participants on kaikki expin osallistujat + for participant in participants: + + #kaikki yhden khn vastaukset ko experimentille koska answer_setin id matchaa answereiden kanssa + flash(participant.session) + for p in pages: + + answers = answer.query.filter_by(answer_set_idanswer_set=participant.idanswer_set).all() + #kaikki yhden participantin vastaukset pagelle + answers_for_page = answer.query.filter(and_(answer.answer_set_idanswer_set==participant.idanswer_set, answer.page_idpage==p.idpage)).all() + + for ans in answers: + if ans.page_idpage == p.idpage: + #flash(ans.page_idpage) + flash("X") + else: + flash("NA") + + #pages on kaikki experimentin paget + + for a in answers: + if p.idpage == a.page_idpage: + flash("match") + else: + flash("no match") + flash("participant:") + flash(participant.session) + flash("stimulus:") + flash(a.page_idpage) + flash("Kysymys") + flash(a.question_idquestion) + flash("vastaus:") + flash(a.answer) + + #answers_list = (a.idanswer, a.question_idquestion, a.answer_set_idanswer_set, a.answer, a.page_idpage) + #participants_and_answers[participant.session] = answers_list + """ + + participants_and_answers = {} + + for participant in participants: + + answers = answer.query.filter_by(answer_set_idanswer_set=participant.idanswer_set).all() + answers_list = [(a.idanswer, a.question_idquestion, a.answer_set_idanswer_set, a.answer, a.page_idpage) for a in answers] + participants_and_answers[participant.session] = answers_list + + #Background question answers + + bg_questions = background_question.query.filter_by(experiment_idexperiment=exp_id).all() + bg_answers_for_participants = {} + + for participant in participants: + + bg_answers = background_question_answer.query.filter_by(answer_set_idanswer_set=participant.idanswer_set).all() + bg_answers_list = [(a.answer) for a in bg_answers] + bg_answers_for_participants[participant.session] = bg_answers_list + + #started and finished ratings counters + started_ratings = answer_set.query.filter_by(experiment_idexperiment=exp_id).count() + experiment_page_count = page.query.filter_by(experiment_idexperiment=exp_id).count() + finished_ratings = answer_set.query.filter(and_(answer_set.answer_counter==experiment_page_count, answer_set.experiment_idexperiment==exp_id)).count() + + return render_template('experiment_statistics.html', experiment_info=experiment_info, participants_and_answers=participants_and_answers, pages_and_questions=pages_and_questions, bg_questions=bg_questions, bg_answers_for_participants=bg_answers_for_participants, started_ratings=started_ratings, finished_ratings=finished_ratings, question_headers=question_headers, stimulus_headers=stimulus_headers) + + diff --git a/app/routes.py b/app/routes.py index 5b6bf45..faec69b 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,69 +1,46 @@ -from app import app, db -from flask import render_template, request + + +import csv +import os +import random +import secrets +from datetime import datetime + +from flask import ( + Flask, + render_template, + request, + session, + flash, + redirect, + url_for, + Blueprint +) + +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.models import background_question, experiment from app.models import background_question_answer from app.models import page, question from app.models import background_question_option from app.models import answer_set, answer, forced_id -from flask import session -from app.forms import LoginForm, RegisterForm -from flask import flash, redirect -from flask import url_for -from wtforms_sqlalchemy.fields import QuerySelectField -from app.forms import Answers, Questions -from flask_bootstrap import Bootstrap -from flask_sqlalchemy import SQLAlchemy, BaseQuery -from app.forms import BackgroundQuestionForm -from wtforms import Form, TextField, TextAreaField, validators, StringField, SubmitField -from app.forms import TestForm, TestForm1, TestForm2, TaskForm -from collections import OrderedDict -from sqlalchemy import func, desc -from app.forms import ContinueTaskForm -from sqlalchemy import and_ from app.models import user, trial_randomization -from flask_login import current_user, login_user -from flask_login import logout_user -from flask_login import login_required -from sqlalchemy import update -from app.forms import StartWithIdForm -import secrets -from app.forms import CreateExperimentForm, CreateBackgroundQuestionForm, CreateQuestionForm, UploadStimuliForm, EditBackgroundQuestionForm, EditQuestionForm, EditExperimentForm, UploadResearchBulletinForm -from app.forms import EditPageForm, RemoveExperimentForm -import os -import random -from flask import Flask, make_response -import pyexcel as pe -import io -from io import BytesIO -import csv -from flask import send_file -from flask import make_response -from flask_babel import Babel -from app import babel -from datetime import datetime -from app.forms import GenerateIdForm -import math -from flask_babel import _ -from flask_babel import lazy_gettext as _l +from app.forms import LoginForm, RegisterForm, StartWithIdForm #Stimuli upload folder setting APP_ROOT = os.path.dirname(os.path.abspath(__file__)) - - - @app.route('/') @app.route('/index') def index(): - experiments = experiment.query.all() if session: - flash("") - else: - #flash("sessio ei voimassa") session['language'] = "English" @@ -73,7 +50,6 @@ def index(): @app.route('/consent') def consent(): exp_id = request.args.get('exp_id', None) - experiment_info = experiment.query.filter_by(idexperiment=exp_id).first() instruction_paragraphs = str(experiment_info.short_instruction) @@ -81,86 +57,68 @@ def consent(): consent_paragraphs = str(experiment_info.consent_text) consent_paragraphs = consent_paragraphs.split('<br>') - - - - if experiment_info.use_forced_id == 'On': - return redirect(url_for('begin_with_id', exp_id=exp_id)) - - - - return render_template('consent.html', exp_id=exp_id, experiment_info=experiment_info, instruction_paragraphs=instruction_paragraphs, consent_paragraphs=consent_paragraphs) + return render_template('consent.html', + exp_id=exp_id, + experiment_info=experiment_info, + instruction_paragraphs=instruction_paragraphs, + consent_paragraphs=consent_paragraphs) @app.route('/set_language') def set_language(): - session['language'] = request.args.get('language', None) - lang = request.args.get('lang', None) - - return redirect(url_for('index', lang=lang)) + @app.route('/remove_language') def remove_language(): - - experiments = experiment.query.all() - - return render_template('index.html', title='Home', experiments=experiments) - - - @app.route('/session') def participant_session(): + # TODO: too long method? + #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 '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'] - random_id = secrets.token_hex(3) check_id = answer_set.query.filter_by(session=random_id).first() while check_id is not None: - #flash("ID already existed; generated a new one") random_id = secrets.token_hex(3) check_id = answer_set.query.filter_by(session=random_id).first() - - session['user'] = random_id - #create answer set for the participant in the database the_time = datetime.now() the_time = the_time.replace(microsecond=0) - participant_answer_set = answer_set(experiment_idexperiment=session['exp_id'], session=session['user'], agreement = session['agree'], answer_counter = '0', registration_time=the_time, last_answer_time=the_time) + participant_answer_set = answer_set(experiment_idexperiment=session['exp_id'], + session=session['user'], + agreement = session['agree'], + answer_counter = '0', + 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 @@ -177,10 +135,8 @@ def participant_session(): experiment_pages = page.query.filter_by(experiment_idexperiment=session['exp_id']).all() original_id_order_list = [(int(o.idpage)) for o in experiment_pages] - #flash("original Page id order:") #for a in range(len(original_id_order_list)): - #flash(original_id_order_list[a]) #create a randomized page id list @@ -188,29 +144,22 @@ def participant_session(): randomized_order_list = [] for i in range(len(helper_list)): - element = random.choice(helper_list) 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() 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']) 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 #old: was missing experiment id so made duplicates @@ -230,7 +179,6 @@ def participant_session(): flash('No pages or mediatype set for experiment') return redirect('/') - if 'user' in session: user = session['user'] #flash('Session started for user {}'.format(user)) @@ -242,7 +190,6 @@ def participant_session(): @app.route('/register', methods=['GET', 'POST']) def register(): - form = RegisterForm(request.form) questions_and_options = {} questions = background_question.query.filter_by(experiment_idexperiment=session['exp_id']).all() @@ -277,83 +224,11 @@ def register(): return render_template('register.html', form=form) -@app.route('/task_completed') -def task_completed(): - - session.pop('user', None) - session.pop('exp_id', None) - session.pop('agree', None) - session.pop('answer_set', None) - session.pop('type', None) - session.pop('randomization', None) - - - return render_template('task_completed.html') - - -@app.route('/continue_task', methods=['GET', 'POST']) -def continue_task(): - - - exp_id = request.args.get('exp_id', None) - form = ContinueTaskForm() - - - if form.validate_on_submit(): - - #check if participant ID is found from db and that the answer set is linked to the correct experiment - participant = answer_set.query.filter(and_(answer_set.session==form.participant_id.data, answer_set.experiment_idexperiment==exp_id)).first() - if participant is None: - flash('Invalid ID') - return redirect(url_for('continue_task', exp_id=exp_id)) - - #flash('Login requested for participant {}'.format(form.participant_id.data)) - - #if correct participant_id is found with the correct experiment ID; start session for that user - session['exp_id'] = exp_id - session['user'] = form.participant_id.data - session['answer_set'] = participant.idanswer_set - mediatype = page.query.filter_by(experiment_idexperiment=session['exp_id']).first() - - rand = experiment.query.filter_by(idexperiment=session['exp_id']).first() - - session['randomization'] = rand.randomization - - if mediatype: - session['type'] = mediatype.type - else: - flash('No pages or mediatype set for experiment') - return redirect('/') - - - #If participant has done just the registration redirect to the first page of the experiment - if participant.answer_counter == 0: - #flash("Ei vastauksia ohjataan ekalle sivulle") - return redirect( url_for('task', page_num=1)) - - - redirect_to_page = participant.answer_counter + 1 - - - #flash("redirect to page:") - #flash(redirect_to_page) - - experiment_page_count = db.session.query(page).filter_by(experiment_idexperiment=session['exp_id']).count() - - #If participant has ansvered all pages allready redirect to task completed page - if experiment_page_count == participant.answer_counter: - - return redirect( url_for('task_completed')) - - - return redirect( url_for('task', page_num=redirect_to_page)) - - return render_template('continue_task.html', exp_id=exp_id, 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 + experiment and POST -method verifys users ID before starting new experiment''' exp_id = request.args.get('exp_id', None) form = StartWithIdForm() @@ -364,8 +239,6 @@ def begin_with_id(): consent_paragraphs = str(experiment_info.consent_text) consent_paragraphs = consent_paragraphs.split('<br>') - - if form.validate_on_submit(): @@ -383,13 +256,10 @@ def begin_with_id(): if participant is None: if is_id_valid is None: - - flash(_('No such ID set for this experiment')) 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 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) @@ -401,7 +271,6 @@ def begin_with_id(): @login_required def admin_dryrun(): - exp_id = request.args.get('exp_id', None) form = StartWithIdForm() experiment_info = experiment.query.filter_by(idexperiment=exp_id).first() @@ -419,17 +288,10 @@ def admin_dryrun(): #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) - return render_template('admin_dryrun.html', exp_id=exp_id, form=form) - -@app.route('/create_task') -def create_task(): - return render_template('create_task.html') - - @app.route('/instructions') def instructions(): @@ -438,148 +300,10 @@ def instructions(): instruction_paragraphs = str(instructions.instruction) instruction_paragraphs = instruction_paragraphs.split('<br>') - - return render_template('instructions.html', instruction_paragraphs=instruction_paragraphs, participant_id=participant_id) -@app.route('/task/<int:page_num>', methods=['GET', 'POST']) -def task(page_num): - - - experiment_info = experiment.query.filter_by(idexperiment=session['exp_id']).first() - rating_instruction = experiment_info.single_sentence_instruction - stimulus_size = experiment_info.stimulus_size - - #for text stimuli the size needs to be calculated since the template element utilises h1-h6 tags. - #A value of stimulus size 12 gives h1 and value of 1 gives h6 - stimulus_size_text = 7-math.ceil((int(stimulus_size)/2)) - - pages = page.query.filter_by(experiment_idexperiment=session['exp_id']).paginate(per_page=1, page=page_num, error_out=True) - progress_bar_percentage = round((pages.page/pages.pages)*100) - - #this variable is feeded to the template as empty if trial randomization is set to "off" - randomized_stimulus = "" - - - - #if trial randomization is on we will still use the same functionality that is used otherwise - #but we will pass the randomized pair of the page_id from trial randomization table to the task.html - if session['randomization'] == 'On': - - - randomized_page_id = trial_randomization.query.filter(and_(trial_randomization.answer_set_idanswer_set==session['answer_set'], trial_randomization.page_idpage==pages.items[0].idpage)).first() - #answer.query.filter(and_(answer.answer_set_idanswer_set==session['answer_set'], answer.page_idpage==session['current_idpage'])).first() - #flash("randomized page:") - #flash(randomized_page_id.randomized_idpage) - #set the stimulus to be shown if randomization is on - randomized_stimulus = page.query.filter_by(idpage=randomized_page_id.randomized_idpage).first() - - - - for p in pages.items: - session['current_idpage'] = p.idpage - - #slider set - form = TaskForm(request.form) - categories_and_scales = {} - categories = question.query.filter_by(experiment_idexperiment=session['exp_id']).all() - - for cat in categories: - - scale_list = [(cat.left, cat.right)] - categories_and_scales[cat.idquestion, cat.question] = scale_list - - form.categories1 = categories_and_scales - - - #slider set form handling - if request.method == 'POST'and form.validate(): - - - #Lets check if there are answers in database already for this page_id (eg. if user returned to previous page and tried to answer again) - #If so flash ("Page has been answered already. Answers discarded"), else insert values in to db - #this has to be done separately for trial randomization "on" and "off" situations - - if session['randomization'] == 'On': - - check_answer = answer.query.filter(and_(answer.answer_set_idanswer_set==session['answer_set'], answer.page_idpage==randomized_page_id.randomized_idpage)).first() - - if session['randomization'] == 'Off': - - check_answer = answer.query.filter(and_(answer.answer_set_idanswer_set==session['answer_set'], answer.page_idpage==session['current_idpage'])).first() - - - - if check_answer is None: - - the_time = datetime.now() - the_time = the_time.replace(microsecond=0) - - update_answer_counter = answer_set.query.filter_by(idanswer_set=session['answer_set']).first() - update_answer_counter.answer_counter = int(update_answer_counter.answer_counter) + 1 - update_answer_counter.last_answer_time = the_time - - #flash("vastauksia:") - #flash(update_answer_counter.answer_counter) - db.session.commit() - - data = request.form.to_dict() - for key, value in data.items(): - - #flash(key) - #flash(value) - #flash(session['current_idpage']) - - - #Insert slider values to database - - - #If trial randomization is set to 'Off' the values are inputted for session['current_idpage'] - #Otherwise the values are set for the corresponding id found in the trial randomization table - - if session['randomization'] == 'Off': - - participant_answer = answer(question_idquestion=key, answer_set_idanswer_set=session['answer_set'], answer=value, page_idpage=session['current_idpage']) - db.session.add(participant_answer) - db.session.commit() - - else: - - participant_answer = answer(question_idquestion=key, answer_set_idanswer_set=session['answer_set'], answer=value, page_idpage=randomized_page_id.randomized_idpage) - db.session.add(participant_answer) - db.session.commit() - - - else: - flash("Page has been answered already. Answers discarded") - - page_num=pages.next_num - - if pages.has_next: - return redirect( url_for('task', page_num=pages.next_num)) - - return redirect ( url_for('task_completed')) - - - return render_template('task.html', pages=pages, progress_bar_percentage=progress_bar_percentage, form=form, randomized_stimulus=randomized_stimulus, rating_instruction=rating_instruction, stimulus_size=stimulus_size, stimulus_size_text=stimulus_size_text) - - - -@app.route('/quit_task') -def quit_task(): - - user_id = session['user'] - session.pop('user', None) - session.pop('exp_id', None) - session.pop('agree', None) - session.pop('answer_set', None) - session.pop('type', None) - - return render_template('quit_task.html', user_id=user_id) - - @app.route('/researcher_login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: @@ -606,1408 +330,38 @@ def logout(): return redirect(url_for('index')) -@app.route('/experiment_statistics') -@login_required -def experiment_statistics(): +@app.route('/view_research_notification') +def view_research_notification(): exp_id = request.args.get('exp_id', None) + image = experiment.query.filter_by(idexperiment=exp_id).first() + research_notification_filename = image.research_notification_filename - experiment_info = experiment.query.filter_by(idexperiment = exp_id).all() - participants = answer_set.query.filter_by(experiment_idexperiment= exp_id).all() - - - #Rating task headers - question_headers = question.query.filter_by(experiment_idexperiment=exp_id).all() - stimulus_headers = page.query.filter_by(experiment_idexperiment=exp_id).all() - - - - - - pages = page.query.filter_by(experiment_idexperiment=exp_id).all() - questions = question.query.filter_by(experiment_idexperiment=exp_id).all() - pages_and_questions = {} - - for p in pages: - - questions_list = [(p.idpage, a.idquestion) for a in questions] - pages_and_questions[p.idpage] = questions_list + return render_template('view_research_notification.html', research_notification_filename=research_notification_filename) +@app.route('/download_csv') +@login_required +def download_csv(): - - #List of answers per participant in format question Stimulus ID/Question ID - #those are in answer table as page_idpage and question_idquestion respectively - - + exp_id = request.args.get('exp_id', None) """ - pages = page.query.filter_by(experiment_idexperiment=exp_id).all() - - participants_and_answers = {} - - #participants on kaikki expin osallistujat - for participant in participants: - - - #kaikki yhden khn vastaukset ko experimentille koska answer_setin id matchaa answereiden kanssa - - - flash(participant.session) - for p in pages: - - answers = answer.query.filter_by(answer_set_idanswer_set=participant.idanswer_set).all() - #kaikki yhden participantin vastaukset pagelle - answers_for_page = answer.query.filter(and_(answer.answer_set_idanswer_set==participant.idanswer_set, answer.page_idpage==p.idpage)).all() - - - for ans in answers: - - - - if ans.page_idpage == p.idpage: - - #flash(ans.page_idpage) - flash("X") - - - - - else: - - flash("NA") - - #pages on kaikki experimentin paget - - - - for a in answers: - - if p.idpage == a.page_idpage: - flash("match") - - else: - flash("no match") - flash("participant:") - flash(participant.session) - flash("stimulus:") - flash(a.page_idpage) - flash("Kysymys") - flash(a.question_idquestion) - flash("vastaus:") - flash(a.answer) - - - - - #answers_list = (a.idanswer, a.question_idquestion, a.answer_set_idanswer_set, a.answer, a.page_idpage) - - #participants_and_answers[participant.session] = answers_list - - - """ - - participants_and_answers = {} - - for participant in participants: - - answers = answer.query.filter_by(answer_set_idanswer_set=participant.idanswer_set).all() - answers_list = [(a.idanswer, a.question_idquestion, a.answer_set_idanswer_set, a.answer, a.page_idpage) for a in answers] - participants_and_answers[participant.session] = answers_list - - - - #Background question answers + with open('export_new.csv', 'w', newline='') as f: - bg_questions = background_question.query.filter_by(experiment_idexperiment=exp_id).all() - bg_answers_for_participants = {} - - for participant in participants: + thewriter = csv.writer(f) - bg_answers = background_question_answer.query.filter_by(answer_set_idanswer_set=participant.idanswer_set).all() - bg_answers_list = [(a.answer) for a in bg_answers] - bg_answers_for_participants[participant.session] = bg_answers_list - - - #started and finished ratings counters - started_ratings = answer_set.query.filter_by(experiment_idexperiment=exp_id).count() - experiment_page_count = page.query.filter_by(experiment_idexperiment=exp_id).count() - finished_ratings = answer_set.query.filter(and_(answer_set.answer_counter==experiment_page_count, answer_set.experiment_idexperiment==exp_id)).count() - - - - return render_template('experiment_statistics.html', experiment_info=experiment_info, participants_and_answers=participants_and_answers, pages_and_questions=pages_and_questions, bg_questions=bg_questions, bg_answers_for_participants=bg_answers_for_participants, started_ratings=started_ratings, finished_ratings=finished_ratings, question_headers=question_headers, stimulus_headers=stimulus_headers) - - - -#EDIT FUNCTIONS - -@app.route('/create_experiment', methods=['GET', 'POST']) -@login_required -def create_experiment(): + thewriter.writerow(['1','2','3','4']) + thewriter.writerow(['a','b','c','d']) + """ - form = CreateExperimentForm(request.form) - - - if request.method == 'POST' and form.validate(): - - - the_time = datetime.now() - the_time = the_time.replace(microsecond=0) - - new_exp = experiment(name=request.form['name'], instruction=request.form['instruction'], language=request.form['language'], status='Hidden', randomization='Off', single_sentence_instruction=request.form['single_sentence_instruction'], short_instruction=request.form['short_instruction'], creator_name=request.form['creator_name'], is_archived='False', creation_time=the_time, stimulus_size='7', consent_text=request.form['consent_text'], use_forced_id='Off') - db.session.add(new_exp) - db.session.commit() - - #flash("lol") - #flash(new_exp.idexperiment) - - exp_id = new_exp.idexperiment - - #data = request.form.to_dict() - #for key, value in data.items(): - #tähän db insertit - - #flash(key) - #flash(value) - #flash('{}'.format(form.name.data)) - - #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(url_for('create_experiment_bgquestions', exp_id=exp_id)) - - return render_template('create_experiment.html', form=form) - + return redirect(url_for('experiment.statistics', exp_id=exp_id)) -@app.route('/create_experiment_bgquestions', methods=['GET', 'POST']) +@app.route('/researcher_info') @login_required -def create_experiment_bgquestions(): - - exp_id = request.args.get('exp_id', None) - form = CreateBackgroundQuestionForm(request.form) - - if request.method == 'POST' and form.validate(): - - #data = request.form.to_dict() - - #flash(data) - #flash(form.bg_questions_and_options.data) - - - str = form.bg_questions_and_options.data - - #Split the form data into a list that separates questions followed by the corresponding options - str_list = str.split('/n') +def researcher_info(): + return render_template('researcher_info.html') - #Iterate through the questions and options list - for a in range(len(str_list)): - - #Split the list cells further into questions and options - list = str_list[a].split(';') - - #flash(list[0]) - #flash("id oikein?") - #flash(add_bgquestion.idbackground_question) - - - #Input the first item of the list as a question in db and the items followed by that as options for that question - for x in range(len(list)): - - if x == 0: - #flash("Kysymys") - #flash(list[x]) - add_bgquestion = background_question(background_question=list[x], experiment_idexperiment=exp_id) - db.session.add(add_bgquestion) - db.session.commit() - - else: - #flash("optio") - #flash(list[x]) - add_bgq_option = background_question_option(background_question_idbackground_question=add_bgquestion.idbackground_question, option=list[x]) - db.session.add(add_bgq_option) - db.session.commit() - - return redirect(url_for('create_experiment_questions', exp_id=exp_id)) - - return render_template('create_experiment_bgquestions.html', form=form, exp_id=exp_id) - - -@app.route('/create_experiment_questions', methods=['GET', 'POST']) -@login_required -def create_experiment_questions(): - - exp_id = request.args.get('exp_id', None) - - form = CreateQuestionForm(request.form) - - if request.method == 'POST' and form.validate(): - - str = form.questions_and_options.data - - str_list = str.split('/n') - - for a in range(len(str_list)): - - list = str_list[a].split(';') - - #If there are the wrong amount of values for any of the the slider input values - #redirect back to the form - if len(list) != 3: - - - flash("Error Each slider must have 3 parameters separated by ; Some slider has:") - flash(len(list)) - - return redirect(url_for('create_experiment_questions', exp_id=exp_id)) - - #If all the slider inputs were of length 3 items - #we can input them to db - for a in range(len(str_list)): - - list = str_list[a].split(';') - - #flash("Question:") - #flash(list[0]) - #flash("Left:") - #flash(list[1]) - #flash("Right:") - #flash(list[2]) - - add_question = question(experiment_idexperiment=exp_id, question=list[0], left=list[1], right=list[2]) - db.session.add(add_question) - db.session.commit() - - - return redirect(url_for('create_experiment_upload_stimuli', exp_id=exp_id)) - - return render_template('create_experiment_questions.html', form=form) - - -@app.route('/create_experiment_upload_stimuli', methods=['GET', 'POST']) -@login_required -def create_experiment_upload_stimuli(): - - exp_id = request.args.get('exp_id', None) - - form = UploadStimuliForm(request.form) - - if request.method == 'POST' and form.validate(): - - #flash("validated") - #flash(form.type.data) - #flash(form.text.data) - - - #If stimulus type is text lets parse the information and insert it to database - - if form.type.data == 'text': - - #flash("db insert text") - - string = form.text.data - str_list = string.split('/n') - - for a in range(len(str_list)): - - #flash("lisättiin:") - #flash(str_list[a]) - add_text_stimulus = page(experiment_idexperiment=exp_id, type='text', text=str_list[a], media='none') - db.session.add(add_text_stimulus) - db.session.commit() - - #flash("Succes!") - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - - - else: - - #Upload stimuli into /static/experiment_stimuli/exp_id folder - #Create the pages for the stimuli by inserting experiment_id, stimulus type, text and names of the stimulus files (as a path to the folder) - path = 'static/experiment_stimuli/' + str(exp_id) - - target = os.path.join(APP_ROOT, path) - #flash(target) - - if not os.path.isdir(target): - os.mkdir(target) - #flash("make dir") - - - #This returns a list of filenames: request.files.getlist("file") - - for file in request.files.getlist("file"): - - #save files in the correct folder - #flash(file.filename) - filename = file.filename - destination = "/".join([target, filename]) - #flash("destination") - #flash(destination) - file.save(destination) - - #add pages to the db - db_path = path + str('/') + str(filename) - - #flash("db path") - #flash(db_path) - - new_page = page(experiment_idexperiment=exp_id, type=form.type.data, media=db_path) - - db.session.add(new_page) - db.session.commit() - - #flash("Succes!") - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - return redirect(url_for('create_experiment_upload_stimuli', exp_id=exp_id)) - - return render_template('create_experiment_upload_stimuli.html', form=form) - - -@app.route('/view_experiment') -@login_required -def view_experiment(): - - #crap:3lines - exp_id = request.args.get('exp_id', None) - media = page.query.filter_by(experiment_idexperiment=exp_id).all() - mtype = page.query.filter_by(experiment_idexperiment=exp_id).first() - - #experiment info - experiment_info = experiment.query.filter_by(idexperiment = exp_id).all() - - #background questions - questions_and_options = {} - questions = background_question.query.filter_by(experiment_idexperiment=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 - - questions1 = questions_and_options - - #sliderset - categories_and_scales = {} - categories = question.query.filter_by(experiment_idexperiment=exp_id).all() - - for cat in categories: - - scale_list = [(cat.left, cat.right)] - categories_and_scales[cat.idquestion, cat.question] = scale_list - - categories1 = categories_and_scales - - - return render_template('view_experiment.html', exp_id=exp_id, media=media, mtype=mtype, experiment_info=experiment_info, categories1=categories1, questions1=questions1) - - -@app.route('/edit_experiment', methods=['GET', 'POST']) -@login_required -def edit_experiment(): - - exp_id = request.args.get('exp_id', None) - - current_experiment = experiment.query.filter_by(idexperiment=exp_id).first() - - form = EditExperimentForm(request.form, obj=current_experiment) - form.language.default = current_experiment.language - - if request.method == 'POST' and form.validate(): - - form.populate_obj(current_experiment) - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - - return render_template('edit_experiment.html', form=form, exp_id=exp_id) - - - - - -@app.route('/edit_bg_question', methods=['GET', 'POST']) -@login_required -def edit_bg_question(): - - - bg_question_id = request.args.get('idbackground_question', None) - - - #Search for the right question and for the right options. Form a string of those separated with ";" and insert the - #formed string into the edit form - current_bg_question = background_question.query.filter_by(idbackground_question=bg_question_id).first() - exp_id=current_bg_question.experiment_idexperiment - question_string = current_bg_question.background_question - options = background_question_option.query.filter_by(background_question_idbackground_question=bg_question_id).all() - - for o in range(len(options)): - - question_string = str(question_string) + str("; ") + str(options[o].option) - - form = EditBackgroundQuestionForm(request.form) - form.bg_questions_and_options.data = question_string - - #After user chooses to update the question and options lets replace the old question and options with the ones from the form - if request.method == 'POST' and form.validate(): - - - #Explode the string with new values from the form - form_values = form.new_values.data - form_values_list = form_values.split(';') - - #Check and remove possible whitespaces from string beginnings with lstrip - for x in range(len(form_values_list)): - - form_values_list[x] = form_values_list[x].lstrip() - - #Cycle through strings and update db - for x in range(len(form_values_list)): - - #Replace question and update the object to database - if x == 0: - - - #flash("delete kys:") - #flash(current_bg_question.background_question) - #flash("insert kys:") - current_bg_question.background_question = form_values_list[x] - #flash(current_bg_question.background_question) - #flash(current_bg_question.idbackground_question) - #flash(current_bg_question.experiment_idexperiment) - - db.session.commit() - - #Delete old options from db - for o in options: - - db.session.delete(o) - db.session.commit() - - #Insert new options to db - else: - - - #flash("insert opt:") - #flash(form_values_list[x]) - new_option = background_question_option(background_question_idbackground_question=current_bg_question.idbackground_question, option=form_values_list[x]) - db.session.add(new_option) - db.session.commit() - - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - - return render_template('edit_bg_question.html', form=form, exp_id=exp_id) - - - -@app.route('/edit_question', methods=['GET', 'POST']) -@login_required -def edit_question(): - - - question_id = request.args.get('idquestion', None) - - current_question = question.query.filter_by(idquestion=question_id).first() - - form = EditQuestionForm(request.form, obj=current_question) - - - if request.method == 'POST' and form.validate(): - - form.populate_obj(current_question) - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=current_question.experiment_idexperiment)) - - return render_template('edit_question.html', form=form) - - - -@app.route('/add_bg_question', methods=['GET', 'POST']) -@login_required -def add_bg_question(): - - exp_id = request.args.get('exp_id', None) - exp_status = experiment.query.filter_by(idexperiment=exp_id).first() - - if exp_status.status != 'Hidden': - - flash("Experiment is public. Cannot modify structure.") - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - else: - - - - form = CreateBackgroundQuestionForm(request.form) - - if request.method == 'POST' and form.validate(): - - str = form.bg_questions_and_options.data - - #Split the form data into a list that separates questions followed by the corresponding options - str_list = str.split('/n') - - #Iterate through the questions and options list - for a in range(len(str_list)): - - #Split the list cells further into questions and options - list = str_list[a].split(';') - - #Input the first item of the list as a question in db and the items followed by that as options for that question - for x in range(len(list)): - - if x == 0: - #flash("Kysymys") - #flash(list[x]) - add_bgquestion = background_question(background_question=list[x], experiment_idexperiment=exp_id) - db.session.add(add_bgquestion) - db.session.commit() - - else: - #flash("optio") - #flash(list[x]) - add_bgq_option = background_question_option(background_question_idbackground_question=add_bgquestion.idbackground_question, option=list[x]) - db.session.add(add_bgq_option) - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - return render_template('add_bg_question.html', form=form) - - - -@app.route('/add_questions', methods=['GET', 'POST']) -@login_required -def add_questions(): - - exp_id = request.args.get('exp_id', None) - exp_status = experiment.query.filter_by(idexperiment=exp_id).first() - - if exp_status.status != 'Hidden': - - flash("Experiment is public. Cannot modify structure.") - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - else: - - form = CreateQuestionForm(request.form) - - if request.method == 'POST' and form.validate(): - - str = form.questions_and_options.data - str_list = str.split('/n') - - for a in range(len(str_list)): - - list = str_list[a].split(';') - - #If there are the right amount of values for the slider input values - if len(list) == 3: - - #flash("Question:") - #flash(list[0]) - #flash("Left:") - #flash(list[1]) - #flash("Right:") - #flash(list[2]) - - add_question = question(experiment_idexperiment=exp_id, question=list[0], left=list[1], right=list[2]) - db.session.add(add_question) - db.session.commit() - - #If slider has too many or too litlle parameters give an error and redirect back to input form - else: - flash("Error Each slider must have 3 parameters separated by ; Some slider has:") - flash(len(list)) - - return redirect(url_for('create_experiment_questions', exp_id=exp_id)) - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - return render_template('add_questions.html', form=form) - - - -#Remove functions - - -@app.route('/remove_bg_question') -@login_required -def remove_bg_question(): - - exp_id = request.args.get('exp_id', None) - - - exp_status = experiment.query.filter_by(idexperiment=exp_id).first() - - if exp_status.status != 'Hidden': - - flash("Experiment is public. Cannot modify structure.") - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - else: - - - remove_id = request.args.get('idbackground_question', None) - - remove_options = background_question_option.query.filter_by(background_question_idbackground_question=remove_id).all() - - for a in range(len(remove_options)): - - #flash(remove_options[a].idbackground_question_option) - - db.session.delete(remove_options[a]) - db.session.commit() - - - remove_question = background_question.query.filter_by(idbackground_question=remove_id).first() - - db.session.delete(remove_question) - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - - - -@app.route('/remove_question') -@login_required -def remove_question(): - - exp_id = request.args.get('exp_id', None) - exp_status = experiment.query.filter_by(idexperiment=exp_id).first() - - if exp_status.status != 'Hidden': - - flash("Experiment is public. Cannot modify structure.") - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - else: - - - remove_id = request.args.get('idquestion', None) - remove_question = question.query.filter_by(idquestion=remove_id).first() - - db.session.delete(remove_question) - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - -@app.route('/remove_experiment', methods=['GET', 'POST']) -@login_required -def remove_experiment(): - - exp_id = request.args.get('exp_id', None) - exp_status = experiment.query.filter_by(idexperiment=exp_id).first() - - if exp_status.status != 'Hidden': - - flash("Experiment is public. Cannot modify structure.") - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - else: - - - - form = RemoveExperimentForm(request.form) - - if request.method == 'POST' and form.validate(): - - if form.remove.data == 'DELETE': - - - #This removes all experiment data from the database! - - - #Remove research bulletin if it exists - empty_filevariable = experiment.query.filter_by(idexperiment=exp_id).first() - - if empty_filevariable.research_notification_filename is not None: - - target = os.path.join(APP_ROOT, empty_filevariable.research_notification_filename) - - if os.path.exists(target): - os.remove(target) - - - - - #Tables - - - remove_forced_id = forced_id.query.filter_by(experiment_idexperiment=exp_id).all() - - for b in range(len(remove_forced_id)): - db.session.delete(remove_forced_id[b]) - db.session.commit() - - - #background_question_option & background_question & background question answers: - remove_background_question = background_question.query.filter_by(experiment_idexperiment=exp_id).all() - - #cycle through all bg questions and delete their options - for a in range(len(remove_background_question)): - - remove_background_question_option = background_question_option.query.filter_by(background_question_idbackground_question=remove_background_question[a].idbackground_question).all() - - for b in range(len(remove_background_question_option)): - - db.session.delete(remove_background_question_option[b]) - db.session.commit() - - - #Remove all background questions and all answers given to each bg question - for a in range(len(remove_background_question)): - - remove_background_question_answers = background_question_answer.query.filter_by(background_question_idbackground_question=remove_background_question[a].idbackground_question).all() - - for b in range(len(remove_background_question_answers)): - - db.session.delete(remove_background_question_answers[b]) - db.session.commit() - - db.session.delete(remove_background_question[a]) - db.session.commit() - - - - #Remove all questions and answers - remove_question = question.query.filter_by(experiment_idexperiment=exp_id).all() - - for a in range(len(remove_question)): - - remove_question_answers = answer.query.filter_by(question_idquestion=remove_question[a].idquestion).all() - - for b in range(len(remove_question_answers)): - - db.session.delete(remove_question_answers[b]) - db.session.commit() - - db.session.delete(remove_question[a]) - db.session.commit() - - - #Remove all pages and datafiles - remove_pages = page.query.filter_by(experiment_idexperiment=exp_id).all() - - for a in range(len(remove_pages)): - - if remove_pages[a].type == 'text': - - db.session.delete(remove_pages[a]) - db.session.commit() - - else: - - target = os.path.join(APP_ROOT, remove_pages[a].media) - - if os.path.exists(target): - os.remove(target) - - #Now that the files are removed we can delete the page - db.session.delete(remove_pages[a]) - db.session.commit() - - - #Remove all answer_sets and trial_randomization orders - remove_answer_set = answer_set.query.filter_by(experiment_idexperiment=exp_id).all() - - for a in range(len(remove_answer_set)): - - remove_trial_randomizations = trial_randomization.query.filter_by(answer_set_idanswer_set=remove_answer_set[a].idanswer_set).all() - - for b in range(len(remove_trial_randomizations)): - - db.session.delete(remove_trial_randomizations[b]) - db.session.commit() - - db.session.delete(remove_answer_set[a]) - db.session.commit() - - - #Remove experiment table - remove_experiment = experiment.query.filter_by(idexperiment=exp_id).first() - db.session.delete(remove_experiment) - db.session.commit() - - - flash("Experiment was removed from database!") - - return redirect(url_for('index')) - - else: - - flash("Experiment was not removed!") - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - - - return render_template('remove_experiment.html', form=form, exp_id=exp_id) - - - -@app.route('/remove_page') -@login_required -def remove_page(): - - exp_id = request.args.get('exp_id', None) - exp_status = experiment.query.filter_by(idexperiment=exp_id).first() - - if exp_status.status != 'Hidden': - - flash("Experiment is public. Cannot modify structure.") - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - else: - - - remove_id = request.args.get('idpage', None) - remove_page = page.query.filter_by(idpage=remove_id).first() - experiment_pages = page.query.filter_by(experiment_idexperiment=exp_id).all() - - #if stimulustype is text, the stimulus itself is text on the database, other stimulus types are real files - #on the server and need to be deleted - if remove_page.type != 'text': - - #helper variable - do_not_delete_file = 'False' - - #if the file to be deleted is in duplicate use of another page then we won't delete the file - for a in range(len(experiment_pages)): - - #flash("in da for") - - if experiment_pages[a].media == remove_page.media and experiment_pages[a].idpage != remove_page.idpage: - - #flash("in da if") - do_not_delete_file = 'True' - - #If no other page is using the file then lets remove it - if do_not_delete_file == 'False': - #remove old file - target = os.path.join(APP_ROOT, remove_page.media) - #flash("Remove:") - #flash(target) - os.remove(target) - - db.session.delete(remove_page) - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - if remove_page.type == 'text': - - db.session.delete(remove_page) - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - -@app.route('/publish_experiment') -@login_required -def publish_experiment(): - - exp_id = request.args.get('exp_id', None) - - publish_experiment = experiment.query.filter_by(idexperiment = exp_id).first() - - publish_experiment.status = 'Public' - - flash("Changed status to Public") - - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - -@app.route('/hide_experiment') -@login_required -def hide_experiment(): - - exp_id = request.args.get('exp_id', None) - - hide_experiment = experiment.query.filter_by(idexperiment = exp_id).first() - - hide_experiment.status = 'Hidden' - - flash("Changed status to Hidden") - - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - -@app.route('/private_experiment') -@login_required -def private_experiment(): - - exp_id = request.args.get('exp_id', None) - - private_experiment = experiment.query.filter_by(idexperiment = exp_id).first() - - private_experiment.status = 'Private' - - flash("Changed status to Private") - - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - - -@app.route('/enable_randomization') -@login_required -def enable_randomization(): - - exp_id = request.args.get('exp_id', None) - - enable_randomization = experiment.query.filter_by(idexperiment = exp_id).first() - - enable_randomization.randomization = 'On' - - flash("Enabled trial randomization") - - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - -@app.route('/disable_randomization') -@login_required -def disable_randomization(): - - exp_id = request.args.get('exp_id', None) - - disable_randomization = experiment.query.filter_by(idexperiment = exp_id).first() - - disable_randomization.randomization = 'Off' - - flash("Disabled trial randomization") - - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - -@app.route('/enable_forced_id') -@login_required -def enable_forced_id(): - - exp_id = request.args.get('exp_id', None) - - enable_forced_id = experiment.query.filter_by(idexperiment = exp_id).first() - - enable_forced_id.use_forced_id = 'On' - - flash("Enabled forced ID login") - - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - -@app.route('/disable_forced_id') -@login_required -def disable_forced_id(): - - exp_id = request.args.get('exp_id', None) - - disable_forced_id = experiment.query.filter_by(idexperiment = exp_id).first() - - disable_forced_id.use_forced_id = 'Off' - - flash("Disabled forced ID login") - - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - -@app.route('/view_forced_id_list', methods=['GET', 'POST']) -@login_required -def view_forced_id_list(): - - exp_id = request.args.get('exp_id', None) - - id_list = forced_id.query.filter_by(experiment_idexperiment=exp_id).all() - - - form = GenerateIdForm(request.form) - - if request.method == 'POST' and form.validate(): - - - for i in range(int(request.form['number'])): - - random_id = str(request.form['string']) + str(secrets.token_hex(3)) - check_answer_set = answer_set.query.filter_by(session=random_id).first() - check_forced_id = forced_id.query.filter_by(pregenerated_id=random_id).first() - - - #here we check if the generated id is found from given answers from the whole database in answer_set table - #or from forced_id table. If so another id is generated instead to avoid a duplicate - if check_answer_set is not None or check_forced_id is not None: - - #flash("ID already existed; generated a new one") - random_id = secrets.token_hex(3) - check_answer_set = answer_set.query.filter_by(session=random_id).first() - check_forced_id = forced_id.query.filter_by(pregenerated_id=random_id).first() - - input_id = forced_id(experiment_idexperiment=exp_id, pregenerated_id=random_id) - db.session.add(input_id) - db.session.commit() - - - - - return redirect(url_for('view_forced_id_list', exp_id=exp_id)) - - - - - - - - return render_template('view_forced_id_list.html', exp_id=exp_id, id_list=id_list) - - - - - - - - - - -@app.route('/edit_stimuli', methods=['GET', 'POST']) -@login_required -def edit_stimuli(): - - exp_id = request.args.get('exp_id', None) - page_id = request.args.get('idpage', None) - edit_page = page.query.filter_by(idpage=page_id).first() - - - - form = EditPageForm(request.form, obj=edit_page) - - if request.method == 'POST' and form.validate(): - - - #If the stimulus type is not text, then the old stimulus file is deleted from os and replaced - if edit_page.type != 'text': - - #remove old file - target = os.path.join(APP_ROOT, edit_page.media) - #flash("Remove:") - #flash(target) - os.remove(target) - - #upload new file - - path = 'static/experiment_stimuli/' + str(exp_id) - - target = os.path.join(APP_ROOT, path) - #flash(target) - - if not os.path.isdir(target): - os.mkdir(target) - #flash("make dir") - - - #This returns a list of filenames: request.files.getlist("file") - - for file in request.files.getlist("file"): - - #save files in the correct folder - #flash(file.filename) - filename = file.filename - destination = "/".join([target, filename]) - #flash("destination") - #flash(destination) - file.save(destination) - - #update db object - db_path = path + str('/') + str(filename) - - #flash("db path") - #flash(db_path) - - edit_page.media=db_path - - db.session.commit() - - #flash("Succes!") - - - #If editing text stimulus no need for filehandling - else: - - form.populate_obj(edit_page) - db.session.commit() - - - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - return render_template('edit_stimuli.html', form=form, edit_page=edit_page) - - -@app.route('/add_stimuli', methods=['GET', 'POST']) -@login_required -def add_stimuli(): - - exp_id = request.args.get('exp_id', None) - - - exp_status = experiment.query.filter_by(idexperiment=exp_id).first() - - - if exp_status.status != 'Hidden': - - flash("Experiment is public. Cannot modify structure.") - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - else: - - - #If there are no pages set for the experiment lets reroute user to create experiment stimuli upload instead - - is_there_any_stimuli = page.query.filter_by(experiment_idexperiment = exp_id).first() - - if is_there_any_stimuli is None: - - return redirect(url_for('create_experiment_upload_stimuli', exp_id=exp_id)) - - - - stimulus_type = request.args.get('stimulus_type', None) - - form = UploadStimuliForm(request.form) - - if request.method == 'POST': - - - if stimulus_type == 'text': - - #flash("db insert text") - - string = form.text.data - str_list = string.split('/n') - - for a in range(len(str_list)): - - #flash("lisättiin:") - #flash(str_list[a]) - add_text_stimulus = page(experiment_idexperiment=exp_id, type='text', text=str_list[a], media='none') - db.session.add(add_text_stimulus) - db.session.commit() - - #flash("Succes!") - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - - - else: - - #Upload stimuli into /static/experiment_stimuli/exp_id folder - #Create the pages for the stimuli by inserting experiment_id, stimulus type, text and names of the stimulus files (as a path to the folder) - path = 'static/experiment_stimuli/' + str(exp_id) - - target = os.path.join(APP_ROOT, path) - #flash(target) - - if not os.path.isdir(target): - os.mkdir(target) - #flash("make dir") - - - #This returns a list of filenames: request.files.getlist("file") - - for file in request.files.getlist("file"): - - #save files in the correct folder - #flash(file.filename) - filename = file.filename - destination = "/".join([target, filename]) - #flash("destination") - #flash(destination) - file.save(destination) - - #add pages to the db - db_path = path + str('/') + str(filename) - - #flash("db path") - #flash(db_path) - - new_page = page(experiment_idexperiment=exp_id, type=form.type.data, media=db_path) - - db.session.add(new_page) - db.session.commit() - - #flash("Succes!") - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - - return render_template('add_stimuli.html', form=form, stimulus_type=stimulus_type) - - - -@app.route('/upload_research_notification', methods=['GET', 'POST']) -@login_required -def upload_research_notification(): - - exp_id = request.args.get('exp_id', None) - - form = UploadResearchBulletinForm(request.form) - - if request.method == 'POST': - - path = 'static/experiment_stimuli/' + str(exp_id) - - target = os.path.join(APP_ROOT, path) - #flash(target) - - if not os.path.isdir(target): - os.mkdir(target) - #flash("make dir") - - - #This returns a list of filenames: request.files.getlist("file") - - for file in request.files.getlist("file"): - - #save files in the correct folder - #flash(file.filename) - filename = file.filename - destination = "/".join([target, filename]) - #flash("destination") - #flash(destination) - file.save(destination) - - #add pages to the db - db_path = path + str('/') + str(filename) - - #flash("db path") - #flash(db_path) - - - bulletin = experiment.query.filter_by(idexperiment=exp_id).first() - - bulletin.research_notification_filename = db_path - db.session.commit() - - #flash("Succes!") - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - - return render_template('upload_research_notification.html', exp_id=exp_id, form=form) - - -@app.route('/remove_research_notification') -@login_required -def remove_research_notification(): - - exp_id = request.args.get('exp_id', None) - - empty_filevariable = experiment.query.filter_by(idexperiment=exp_id).first() - - target = os.path.join(APP_ROOT, empty_filevariable.research_notification_filename) - - if os.path.exists(target): - os.remove(target) - - - empty_filevariable.research_notification_filename = None - - db.session.commit() - - return redirect(url_for('view_experiment', exp_id=exp_id)) - - - -@app.route('/view_research_notification') -def view_research_notification(): - - exp_id = request.args.get('exp_id', None) - image = experiment.query.filter_by(idexperiment=exp_id).first() - - research_notification_filename = image.research_notification_filename - - 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) - - """ - with open('export_new.csv', 'w', newline='') as f: - - thewriter = csv.writer(f) - - thewriter.writerow(['1','2','3','4']) - thewriter.writerow(['a','b','c','d']) - """ - - - - return redirect(url_for('experiment_statistics', exp_id=exp_id)) - - -@app.route('/researcher_info') -@login_required -def researcher_info(): - - - return render_template('researcher_info.html') - - - - - -#TEST PAGE - -@app.route('/test_page') -def test_page(): - - flash('Please log in to access this page.') - - session.clear() - - return render_template('test_page.html') +# EOF \ No newline at end of file diff --git a/app/static/img/dummyG.png b/app/static/img/dummyG.png new file mode 100644 index 0000000000000000000000000000000000000000..9e6ed7c1092e2f8db50c3ba03b923f8798bdf6c4 GIT binary patch literal 82796 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%0DQJ*WVqT&sv<@!VgdUNPL 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 HB>4XTo9=jY literal 0 HcmV?d00001 diff --git a/app/task/__init__.py b/app/task/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/templates/continue_task.html b/app/task/templates/continue_task.html similarity index 100% rename from app/templates/continue_task.html rename to app/task/templates/continue_task.html diff --git a/app/templates/quit_task.html b/app/task/templates/quit_task.html similarity index 100% rename from app/templates/quit_task.html rename to app/task/templates/quit_task.html diff --git a/app/templates/task.html b/app/task/templates/task.html similarity index 94% rename from app/templates/task.html rename to app/task/templates/task.html index 8eeaf5c..77cdddd 100644 --- a/app/templates/task.html +++ b/app/task/templates/task.html @@ -1,11 +1,13 @@ {% extends "base.html" %} {% block content %} - <br> {% if session['randomization']=='Off' %} - + +<!-- TODO change session['type'] TO stimulus['type'] + because session should have multiple type of stimuli!!! + --> {% if session['type']=='text' %} <div class="container text-center mt-5 pt-5"> @@ -16,7 +18,6 @@ <br><br> {% endif %} - {% if session['type']=='picture' %} <div class="container stimulus col-{{stimulus_size}} mt-5 pt-5"> {% for page in pages.items %} @@ -25,13 +26,8 @@ </div> {% endif %} - {% if session['type']=='video' %} <div class="col-{{stimulus_size}} container stimulus mt-5 pt-5"> - - - - {% for page in pages.items %} <div class="embed-responsive embed-responsive-16by9"> <iframe class="embed-responsive-item" src="/{{ page.media }}" allowfullscreen></iframe> @@ -40,14 +36,8 @@ </div> {% endif %} - {% if session['type']=='audio' %} - <div class="col-{{stimulus_size}} container stimulus mt-5 pt-5"> - - - - {% for page in pages.items %} <div class="embed-responsive embed-responsive-16by9"> <iframe class="embed-responsive-item" src="/{{ page.media }}" allowfullscreen></iframe> @@ -56,11 +46,8 @@ </div> {% endif %} - {% else %} - - {% if session['type']=='text' %} <div class="container text-center mt-5 pt-5"> {% for page in pages.items %} @@ -70,7 +57,6 @@ <br><br> {% endif %} - {% if session['type']=='picture' %} <div class="container stimulus col-{{stimulus_size}} mt-5 pt-5"> {% for page in pages.items %} @@ -79,13 +65,8 @@ </div> {% endif %} - {% if session['type']=='video' %} <div class="col-{{stimulus_size}} container stimulus mt-5 pt-5"> - - - - {% for page in pages.items %} <div class="embed-responsive embed-responsive-16by9"> <iframe class="embed-responsive-item" src="/{{ randomized_stimulus.media }}" allowfullscreen></iframe> @@ -94,12 +75,8 @@ </div> {% endif %} - {% if session['type']=='audio' %} <div class="col-{{stimulus_size}} container stimulus mt-5 pt-5"> - - - {% for page in pages.items %} <div class="embed-responsive embed-responsive-16by9"> <iframe class="embed-responsive-item" src="/{{ randomized_stimulus.media }}" allowfullscreen></iframe> @@ -108,12 +85,9 @@ </div> {% endif %} - {% endif %} - - - <br> +<br> @@ -140,7 +114,7 @@ {% endfor %} <div class="form-row text-center"> <div class="col-12"> - <a class="btn btn-primary" href={{ url_for('quit_task') }} role="button">{{ _('Quit task') }}</a> + <a class="btn btn-primary" href={{ url_for('task.quit') }} role="button">{{ _('Quit task') }}</a> <button type="submit" class="btn btn-primary">{{ _('Next page') }}</button> </div> <div class="col-12"> diff --git a/app/templates/task_completed.html b/app/task/templates/task_completed.html similarity index 100% rename from app/templates/task_completed.html rename to app/task/templates/task_completed.html diff --git a/app/task/views.py b/app/task/views.py new file mode 100644 index 0000000..0c0ccf2 --- /dev/null +++ b/app/task/views.py @@ -0,0 +1,220 @@ + + + +import math +from datetime import datetime + +from flask import ( + Flask, + render_template, + request, + session, + flash, + redirect, + url_for, + Blueprint +) + +from sqlalchemy import and_ +from flask_babel import _, lazy_gettext as _l + +from app import db +from app.models import experiment +from app.models import page, question +from app.models import answer_set, answer +from app.models import user, trial_randomization +from app.forms import Answers, TaskForm, ContinueTaskForm + +task_blueprint = Blueprint("task", __name__, + template_folder='templates', + static_folder='static', + url_prefix='/task') + +@task_blueprint.route('/<int:page_num>', methods=['GET', 'POST']) +def task(page_num): + + experiment_info = experiment.query.filter_by(idexperiment=session['exp_id']).first() + rating_instruction = experiment_info.single_sentence_instruction + stimulus_size = experiment_info.stimulus_size + + #for text stimuli the size needs to be calculated since the template element utilises h1-h6 tags. + #A value of stimulus size 12 gives h1 and value of 1 gives h6 + stimulus_size_text = 7-math.ceil((int(stimulus_size)/2)) + + print(stimulus_size_text) + + pages = page.query.filter_by(experiment_idexperiment=session['exp_id']).paginate(per_page=1, page=page_num, error_out=True) + progress_bar_percentage = round((pages.page/pages.pages)*100) + + #this variable is feeded to the template as empty if trial randomization is set to "off" + randomized_stimulus = "" + + #if trial randomization is on we will still use the same functionality that is used otherwise + #but we will pass the randomized pair of the page_id from trial randomization table to the task.html + if session['randomization'] == 'On': + randomized_page_id = trial_randomization.query.filter(and_(trial_randomization.answer_set_idanswer_set==session['answer_set'], trial_randomization.page_idpage==pages.items[0].idpage)).first() + #answer.query.filter(and_(answer.answer_set_idanswer_set==session['answer_set'], answer.page_idpage==session['current_idpage'])).first() + #flash("randomized page:") + #flash(randomized_page_id.randomized_idpage) + #set the stimulus to be shown if randomization is on + randomized_stimulus = page.query.filter_by(idpage=randomized_page_id.randomized_idpage).first() + + for p in pages.items: + session['current_idpage'] = p.idpage + + #slider set + form = TaskForm(request.form) + categories_and_scales = {} + categories = question.query.filter_by(experiment_idexperiment=session['exp_id']).all() + + for cat in categories: + scale_list = [(cat.left, cat.right)] + categories_and_scales[cat.idquestion, cat.question] = scale_list + + form.categories1 = categories_and_scales + + #slider set form handling + if request.method == 'POST'and form.validate(): + #Lets check if there are answers in database already for this page_id (eg. if user returned to previous page and tried to answer again) + #If so flash ("Page has been answered already. Answers discarded"), else insert values in to db + #this has to be done separately for trial randomization "on" and "off" situations + + if session['randomization'] == 'On': + check_answer = answer.query.filter(and_(answer.answer_set_idanswer_set==session['answer_set'], answer.page_idpage==randomized_page_id.randomized_idpage)).first() + + if session['randomization'] == 'Off': + check_answer = answer.query.filter(and_(answer.answer_set_idanswer_set==session['answer_set'], answer.page_idpage==session['current_idpage'])).first() + + if check_answer is None: + + the_time = datetime.now() + the_time = the_time.replace(microsecond=0) + + update_answer_counter = answer_set.query.filter_by(idanswer_set=session['answer_set']).first() + update_answer_counter.answer_counter = int(update_answer_counter.answer_counter) + 1 + update_answer_counter.last_answer_time = the_time + + #flash("vastauksia:") + #flash(update_answer_counter.answer_counter) + db.session.commit() + + data = request.form.to_dict() + for key, value in data.items(): + #flash(key) + #flash(value) + #flash(session['current_idpage']) + + #Insert slider values to database + + #If trial randomization is set to 'Off' the values are inputted for session['current_idpage'] + #Otherwise the values are set for the corresponding id found in the trial randomization table + + if session['randomization'] == 'Off': + participant_answer = answer(question_idquestion=key, answer_set_idanswer_set=session['answer_set'], answer=value, page_idpage=session['current_idpage']) + db.session.add(participant_answer) + db.session.commit() + else: + participant_answer = answer(question_idquestion=key, answer_set_idanswer_set=session['answer_set'], answer=value, page_idpage=randomized_page_id.randomized_idpage) + db.session.add(participant_answer) + db.session.commit() + + else: + flash("Page has been answered already. Answers discarded") + + page_num=pages.next_num + + if pages.has_next: + return redirect( url_for('task.task', page_num=pages.next_num)) + + return redirect ( url_for('task.completed')) + + return render_template('task.html', pages=pages, progress_bar_percentage=progress_bar_percentage, form=form, randomized_stimulus=randomized_stimulus, rating_instruction=rating_instruction, stimulus_size=stimulus_size, stimulus_size_text=stimulus_size_text) + + +@task_blueprint.route('/completed') +def completed(): + + session.pop('user', None) + session.pop('exp_id', None) + session.pop('agree', None) + session.pop('answer_set', None) + session.pop('type', None) + session.pop('randomization', None) + + return render_template('task_completed.html') + + +@task_blueprint.route('/continue', methods=['GET', 'POST']) +def continue_task(): + + exp_id = request.args.get('exp_id', None) + form = ContinueTaskForm() + + if form.validate_on_submit(): + + #check if participant ID is found from db and that the answer set is linked to the correct experiment + participant = answer_set.query.filter(and_(answer_set.session==form.participant_id.data, answer_set.experiment_idexperiment==exp_id)).first() + if participant is None: + flash('Invalid ID') + return redirect(url_for('task.continue', exp_id=exp_id)) + + #flash('Login requested for participant {}'.format(form.participant_id.data)) + + #if correct participant_id is found with the correct experiment ID; start session for that user + session['exp_id'] = exp_id + session['user'] = form.participant_id.data + session['answer_set'] = participant.idanswer_set + mediatype = page.query.filter_by(experiment_idexperiment=session['exp_id']).first() + + rand = experiment.query.filter_by(idexperiment=session['exp_id']).first() + + session['randomization'] = rand.randomization + + if mediatype: + session['type'] = mediatype.type + else: + flash('No pages or mediatype set for experiment') + return redirect('/') + + #If participant has done just the registration redirect to the first page of the experiment + if participant.answer_counter == 0: + #flash("Ei vastauksia ohjataan ekalle sivulle") + return redirect( url_for('task', page_num=1)) + + + redirect_to_page = participant.answer_counter + 1 + + #flash("redirect to page:") + #flash(redirect_to_page) + + experiment_page_count = db.session.query(page).filter_by(experiment_idexperiment=session['exp_id']).count() + + #If participant has ansvered all pages allready redirect to task completed page + if experiment_page_count == participant.answer_counter: + + return redirect( url_for('task.completed')) + + return redirect( url_for('task.task', page_num=redirect_to_page)) + + return render_template('continue_task.html', exp_id=exp_id, form=form) + + +@task_blueprint.route('/quit') +def quit(): + + user_id = session['user'] + session.pop('user', None) + session.pop('exp_id', None) + session.pop('agree', None) + session.pop('answer_set', None) + session.pop('type', None) + + return render_template('quit_task.html', user_id=user_id) + + +# TODO: removable? +@task_blueprint.route('/create') +def create(): + return render_template('create_task.html') + + diff --git a/app/templates/base.html b/app/templates/base.html index c8b4c59..b6f0350 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -75,7 +75,7 @@ <a class="nav-item" href="{{ url_for('researcher_info') }}" class="nav-link">Info |</a> - <a class="nav-item" href="{{ url_for('create_experiment') }}" class="nav-link">Create |</a> + <a class="nav-item" href="{{ url_for('create.create_experiment') }}" class="nav-link">Create |</a> <a class="nav-item" href="" class="nav-link">Archives |</a> <a class="nav-item" href="{{ url_for('logout') }}" class="nav-link">Logout |</a> diff --git a/app/templates/index.html b/app/templates/index.html index d034db3..e8480f8 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -54,13 +54,13 @@ <a class="btn btn-outline-primary" href="{{ url_for('consent', exp_id=exp.idexperiment) }}" role="button">{{ _('Begin task') }}</a> - <a class="btn btn-outline-primary" href="{{ url_for('continue_task', exp_id=exp.idexperiment) }}" role="button">{{ _('Continue task') }}</a> + <a class="btn btn-outline-primary" href="{{ url_for('task.continue_task', exp_id=exp.idexperiment) }}" role="button">{{ _('Continue task') }}</a> {% if current_user.is_authenticated %} <span class="text-right"> <a class="btn btn-outline-info" href="{{ url_for('admin_dryrun', exp_id=exp.idexperiment) }}" role="button">{{ _('AdminRun') }}</a> - <a class="btn btn-outline-info" href="{{ url_for('experiment_statistics', exp_id=exp.idexperiment) }}" role="button">{{ _('Statistics') }}</a> - <a class="btn btn-outline-info" href="{{ url_for('view_experiment', exp_id=exp.idexperiment) }}" role="button">{{ _('View / Edit') }}</a> + <a class="btn btn-outline-info" href="{{ url_for('experiment.statistics', exp_id=exp.idexperiment) }}" role="button">{{ _('Statistics') }}</a> + <a class="btn btn-outline-info" href="{{ url_for('experiment.view', exp_id=exp.idexperiment) }}" role="button">{{ _('View / Edit') }}</a> {% endif %} @@ -97,13 +97,13 @@ <li class="list-group-item"> <a class="btn btn-outline-primary" href="{{ url_for('consent', exp_id=exp.idexperiment) }}" role="button">{{ _('Begin task') }}</a> - <a class="btn btn-outline-primary" href="{{ url_for('continue_task', exp_id=exp.idexperiment) }}" role="button">{{ _('Continue task') }}</a + <a class="btn btn-outline-primary" href="{{ url_for('task.continue_task', exp_id=exp.idexperiment) }}" role="button">{{ _('Continue task') }}</a {% if current_user.is_authenticated %} <span class="text-right"> <a class="btn btn-outline-info" href="{{ url_for('admin_dryrun', exp_id=exp.idexperiment) }}" role="button">{{ _('AdminRun') }}</a> - <a class="btn btn-outline-info" href="{{ url_for('experiment_statistics', exp_id=exp.idexperiment) }}" role="button">{{ _('Statistics') }}</a> - <a class="btn btn-outline-info" href="{{ url_for('view_experiment', exp_id=exp.idexperiment) }}" role="button">{{ _('View / Edit') }}</a> + <a class="btn btn-outline-info" href="{{ url_for('experiment.statistics', exp_id=exp.idexperiment) }}" role="button">{{ _('Statistics') }}</a> + <a class="btn btn-outline-info" href="{{ url_for('experiment.view', exp_id=exp.idexperiment) }}" role="button">{{ _('View / Edit') }}</a> {% endif %} @@ -147,12 +147,12 @@ <li class="list-group-item"> <a class="btn btn-outline-primary" href="{{ url_for('consent', exp_id=exp.idexperiment) }}" role="button">Begin task</a> - <a class="btn btn-outline-primary" href="{{ url_for('continue_task', exp_id=exp.idexperiment) }}" role="button">Continue task</a> + <a class="btn btn-outline-primary" href="{{ url_for('task.continue_task', exp_id=exp.idexperiment) }}" role="button">Continue task</a> {% if current_user.is_authenticated %} <a class="btn btn-outline-info" href="{{ url_for('admin_dryrun', exp_id=exp.idexperiment) }}" role="button">AdminRun</a> - <a class="btn btn-outline-info" href="{{ url_for('experiment_statistics', exp_id=exp.idexperiment) }}" role="button">Statistics</a> - <a class="btn btn-outline-info" href="{{ url_for('view_experiment', exp_id=exp.idexperiment) }}" role="button">View / Edit</a> + <a class="btn btn-outline-info" href="{{ url_for('experiment.statistics', exp_id=exp.idexperiment) }}" role="button">Statistics</a> + <a class="btn btn-outline-info" href="{{ url_for('experiment.view', exp_id=exp.idexperiment) }}" role="button">View / Edit</a> {% endif %} @@ -187,12 +187,12 @@ <li class="list-group-item"> <a class="btn btn-outline-primary" href="{{ url_for('consent', exp_id=exp.idexperiment) }}" role="button">Begin task</a> - <a class="btn btn-outline-primary" href="{{ url_for('continue_task', exp_id=exp.idexperiment) }}" role="button">Continue task</a> + <a class="btn btn-outline-primary" href="{{ url_for('task.continue_task', exp_id=exp.idexperiment) }}" role="button">Continue task</a> {% if current_user.is_authenticated %} <a class="btn btn-outline-info" href="{{ url_for('admin_dryrun', exp_id=exp.idexperiment) }}" role="button">AdminRun</a> - <a class="btn btn-outline-info" href="{{ url_for('experiment_statistics', exp_id=exp.idexperiment) }}" role="button">Statistics</a> - <a class="btn btn-outline-info" href="{{ url_for('view_experiment', exp_id=exp.idexperiment) }}" role="button">View / Edit</a> + <a class="btn btn-outline-info" href="{{ url_for('experiment.statistics', exp_id=exp.idexperiment) }}" role="button">Statistics</a> + <a class="btn btn-outline-info" href="{{ url_for('experiment.view', exp_id=exp.idexperiment) }}" role="button">View / Edit</a> {% endif %} @@ -249,12 +249,12 @@ <li class="list-group-item"> <a class="btn btn-outline-primary" href="{{ url_for('consent', exp_id=exp.idexperiment) }}" role="button">Begin task</a> - <a class="btn btn-outline-primary" href="{{ url_for('continue_task', exp_id=exp.idexperiment) }}" role="button">Continue task</a> + <a class="btn btn-outline-primary" href="{{ url_for('task.continue_task', exp_id=exp.idexperiment) }}" role="button">Continue task</a> {% if current_user.is_authenticated %} <a class="btn btn-outline-info" href="{{ url_for('admin_dryrun', exp_id=exp.idexperiment) }}" role="button">AdminRun</a> - <a class="btn btn-outline-info" href="{{ url_for('experiment_statistics', exp_id=exp.idexperiment) }}" role="button">Statistics</a> - <a class="btn btn-outline-info" href="{{ url_for('view_experiment', exp_id=exp.idexperiment) }}" role="button">View / Edit</a> + <a class="btn btn-outline-info" href="{{ url_for('experiment.statistics', exp_id=exp.idexperiment) }}" role="button">Statistics</a> + <a class="btn btn-outline-info" href="{{ url_for('experiment.view', exp_id=exp.idexperiment) }}" role="button">View / Edit</a> {% endif %} @@ -289,12 +289,12 @@ <li class="list-group-item"> <a class="btn btn-outline-primary" href="{{ url_for('consent', exp_id=exp.idexperiment) }}" role="button">Begin task</a> - <a class="btn btn-outline-primary" href="{{ url_for('continue_task', exp_id=exp.idexperiment) }}" role="button">Continue task</a> + <a class="btn btn-outline-primary" href="{{ url_for('task.continue_task', exp_id=exp.idexperiment) }}" role="button">Continue task</a> {% if current_user.is_authenticated %} <a class="btn btn-outline-info" href="{{ url_for('admin_dryrun', exp_id=exp.idexperiment) }}" role="button">AdminRun</a> - <a class="btn btn-outline-info" href="{{ url_for('experiment_statistics', exp_id=exp.idexperiment) }}" role="button">Statistics</a> - <a class="btn btn-outline-info" href="{{ url_for('view_experiment', exp_id=exp.idexperiment) }}" role="button">View / Edit</a> + <a class="btn btn-outline-info" href="{{ url_for('experiment.statistics', exp_id=exp.idexperiment) }}" role="button">Statistics</a> + <a class="btn btn-outline-info" href="{{ url_for('experiment.view', exp_id=exp.idexperiment) }}" role="button">View / Edit</a> {% endif %} diff --git a/app/templates/test_page.html b/app/templates/test_page.html deleted file mode 100644 index f204c39..0000000 --- a/app/templates/test_page.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "base.html" %} -{% block content %} - -<h1 class="container mt-5">moro</h1> - - -<h1 class="container mt-5">{{ _('File Not Found') }}</h1> - -{% endblock %} - - - diff --git a/config.py b/config.py index f5a8ed4..542e701 100644 --- a/config.py +++ b/config.py @@ -18,14 +18,13 @@ class Config(object): """ #MariaDB mysql database settings - MYSQL_USER = 'rating' - MYSQL_PASSWORD = 'timotimo' + MYSQL_PASSWORD = 'rating_passwd' MYSQL_SERVER = 'localhost' - MYSQL_DB = 'rating_tool_db' + MYSQL_DB = 'rating_db' SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://'+MYSQL_USER+':'+MYSQL_PASSWORD+'@'+MYSQL_SERVER+'/'+MYSQL_DB+'?charset=utf8mb4' SQLALCHEMY_TRACK_MODIFICATIONS = False - \ No newline at end of file + diff --git a/create_rating_db.txt b/create_rating_db.txt index 1eaafe7..7d52248 100644 --- a/create_rating_db.txt +++ b/create_rating_db.txt @@ -7,19 +7,19 @@ CREATE TABLE background_question ( CREATE TABLE experiment ( idexperiment INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(120), - instruction TEXT, + instruction MEDIUMTEXT, directoryname VARCHAR(120), language VARCHAR(120), status VARCHAR(120), randomization VARCHAR(120), - short_instruction TEXT, - single_sentence_instruction TEXT, + short_instruction MEDIUMTEXT, + single_sentence_instruction MEDIUMTEXT, is_archived VARCHAR(120), creator_name VARCHAR(120), research_notification_filename VARCHAR(120), creation_time DATETIME, stimulus_size VARCHAR(120), - consent_text TEXT, + consent_text MEDIUMTEXT, use_forced_id VARCHAR(120), PRIMARY KEY (idexperiment) ); -- GitLab