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