diff --git a/app/create/views.py b/app/create/views.py index bca9240ea44536d4b0f7d1e9d94e5370d293956e..3e3bb9c973af719a533c6b46e1da3d31d4ab398c 100644 --- a/app/create/views.py +++ b/app/create/views.py @@ -111,8 +111,12 @@ def experiment_questions(): exp_id = request.args.get('exp_id', None) form = CreateQuestionForm(request.form) + + print(form) + print(form.validate()) - if request.method == 'POST' and form.validate(): + if request.method == 'POST': + #if request.method == 'POST' and form.validate(): str = form.questions_and_options.data str_list = str.split('/n') @@ -140,9 +144,10 @@ def experiment_questions(): db.session.add(add_question) db.session.commit() - return redirect(url_for('create.experiment_upload_stimuli', exp_id=exp_id)) + #return redirect(url_for('create.experiment_upload_stimuli', exp_id=exp_id)) + return redirect(url_for('create.experiment_upload_stimuli', exp_id=exp_id)) - return render_template('create_experiment_questions.html', form=form) + #return render_template('create_experiment_questions.html', form=form) @create_blueprint.route('/experiment_upload_stimuli', methods=['GET', 'POST']) diff --git a/app/experiment/views.py b/app/experiment/views.py index c0283881e1c9bec3db5372282b152dc54fe62cae..55e7e9872c105610dd6bb5600a86cc10bdb40f87 100644 --- a/app/experiment/views.py +++ b/app/experiment/views.py @@ -517,13 +517,12 @@ def remove_bg_question(): 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: - + + # TODO: cannot remove background question if there are answers remove_id = request.args.get('idbackground_question', None) remove_options = background_question_option.query.filter_by(background_question_idbackground_question=remove_id).all() @@ -545,8 +544,6 @@ def remove_bg_question(): - - # Rating set: @experiment_blueprint.route('/set_embody') diff --git a/app/models.py b/app/models.py index 158039315c35d926866e803903260d4ae2f22290..9c3fe4d661adc59060414199cca7733422df4fa6 100644 --- a/app/models.py +++ b/app/models.py @@ -28,7 +28,6 @@ class background_question_option(db.Model): background_question_idbackground_question = db.Column(db.Integer, db.ForeignKey('background_question.idbackground_question')) option = db.Column(db.String(120)) - def __repr__(self): return "<idbackground_question_option = '%s', background_question_idbackground_question = '%s', option = '%s'>" % (self.idbackground_question_option, self.background_question_idbackground_question, self.option) @@ -64,6 +63,7 @@ class answer_set (db.Model): session = db.Column(db.String(120)) agreement = db.Column(db.String(120)) answer_counter = db.Column(db.Integer) + answer_type = db.Column(db.String(120)) registration_time = db.Column(db.DateTime, index=True, default=datetime.utcnow) last_answer_time = db.Column(db.DateTime, index=True, default=datetime.utcnow) @@ -78,15 +78,8 @@ class background_question_answer(db.Model): answer = db.Column(db.String(120)) background_question_idbackground_question = db.Column(db.Integer, db.ForeignKey('background_question.idbackground_question')) - def __repr__(self): return "<idbackground_question_answer = '%s', answer_set_idanswer_set = '%s', answer = '%s', background_question_idbackground_question = '%s'>" % (self.idbackground_question_answer, self.answer_set_idanswer_set, self.answer, self.background_question_idbackground_question) - """ - - def __repr__(self): - return '<answer {}>'.format(self.answer) - - """ def background_question_answer_query(): @@ -95,10 +88,7 @@ def background_question_answer_query(): """ class ChoiceForm(FlaskForm): opts = QuerySelectField(query_factory=background_question_answer_query, allow_blank=True) -""" - -""" u = background_question.query.get(1) vastaukset = u.answers.all() ## pitää sisällään kysymyksen 1 vastaukset @@ -124,13 +114,7 @@ class page (db.Model): type = db.Column(db.String(120), index=True) text = db.Column(db.Text) media = db.Column(db.String(120), index=True) - """ - def __repr__(self): - return "<idpage = '%s', experiment_idexperiment = '%s', type = '%s', text = '%s', media = '%s'>" % (self.idpage, self.experiment_idexperiment, self.type, self.text, self.media) - - def __repr__(self): - return '{}'.format(self.text) - """ + def __repr__(self): return "<idpage = '%s', experiment_idexperiment = '%s', type = '%s', text = '%s', media = '%s'>" % (self.idpage, self.experiment_idexperiment, self.type, self.text, self.media) @@ -147,6 +131,17 @@ class answer (db.Model): return "<idanswer = '%s', question_idquestion = '%s', answer_set_idanswer_set = '%s', answer = '%s', page_idpage = '%s'>" % (self.idanswer, self.question_idquestion, self.answer_set_idanswer_set, self.answer, self.page_idpage) +class embody_answer (db.Model): + __tablename__ = "embody_answer" + idanswer = db.Column(db.Integer, primary_key=True) + answer_set_idanswer_set = db.Column(db.Integer, db.ForeignKey('answer_set.idanswer_set')) + page_idpage = db.Column(db.Integer, db.ForeignKey('page.idpage')) + coordinates = db.Column(db.Text) + + def __repr__(self): + return "<idanswer = '%s', answer_set_idanswer_set = '%s', coordinates = '%s', page_idpage = '%s'>" % (self.idanswer, self.answer_set_idanswer_set, self.coordinates, self.page_idpage) + + class trial_randomization (db.Model): __tablename__ = "trial_randomization" idtrial_randomization = db.Column(db.Integer, primary_key=True) @@ -169,11 +164,6 @@ class forced_id (db.Model): return "<idforced_id = '%s', experiment_idexperiment = '%s', pregenerated_id = '%s'>" % (self.idforced_id, self.experiment_idexperiment, self.pregenerated_id) - - - - - class user(UserMixin, db.Model): __tablename__ = "user" id = db.Column(db.Integer, primary_key=True) diff --git a/app/routes.py b/app/routes.py index 30da6bcf5759bf7fe3e8f168922aa48b49b035d2..67314d65e2877eece00dd9a251d01250c9128bb9 100644 --- a/app/routes.py +++ b/app/routes.py @@ -83,7 +83,7 @@ def remove_language(): @app.route('/session') def participant_session(): - '''Set up session variables''' + '''Set up session variables and create answer_set (database level sessions)''' #start session session['exp_id'] = request.args.get('exp_id', None) @@ -104,20 +104,29 @@ def participant_session(): session['user'] = random_id + # Set session status variables + exp_status = experiment.query.filter_by(idexperiment=session['exp_id']).first() + + #create answer set for the participant in the database the_time = datetime.now() the_time = the_time.replace(microsecond=0) + + # TODO: Check which question type is the first in answer_set + answer_set_type = 'slider' + if exp_status.embody_enabled: + answer_set_type = 'embody' + participant_answer_set = answer_set(experiment_idexperiment=session['exp_id'], session=session['user'], agreement = session['agree'], answer_counter = '0', + answer_type = answer_set_type, registration_time=the_time, last_answer_time=the_time) db.session.add(participant_answer_set) db.session.commit() - # Set session status variables - exp_status = experiment.query.filter_by(idexperiment=session['exp_id']).first() #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 @@ -150,18 +159,11 @@ def participant_session(): if exp_status.randomization == "Off": session['randomization'] = "Off" - if exp_status.embody_enabled: - session['embody'] = True - else: - session['embody'] = False - - #store participants session id in session list as answer_set, based on experiment id and session id session_id_for_participant = answer_set.query.filter(and_(answer_set.session==session['user'], answer_set.experiment_idexperiment==session['exp_id'])).first() session['answer_set'] = session_id_for_participant.idanswer_set - # TODO: this is unnecessary if experiment contains multiple stimulus types #collect experiments mediatype from db to session['type']. #This is later used in task.html to determine page layout based on stimulus type @@ -172,7 +174,6 @@ def participant_session(): flash('No pages or mediatype set for experiment') return redirect('/') - # Redirect user to register page if 'user' in session: user = session['user'] diff --git a/app/static/js/canvas.js b/app/static/js/canvas.js index 7b084dd5b1051a443860b170aaf3eb24f8c3e4dc..770d7502b75981359d54b77cb956e1d839eb394a 100644 --- a/app/static/js/canvas.js +++ b/app/static/js/canvas.js @@ -40,6 +40,7 @@ $(document).ready(function() { }); canvas.mousemove(function(e){ + // TODO: if mousedown -> can draw outside of image var mouseX = e.pageX - this.offsetLeft; var mouseY = e.pageY - this.offsetTop; if(paint && pointInsideBaseImage([mouseX, mouseY])){ diff --git a/app/task/views.py b/app/task/views.py index d4204417a0b63a79d4ac76b84288f21e91fabff6..5a5ccce58ac5c443c3b61df67ab8290ec00957d4 100644 --- a/app/task/views.py +++ b/app/task/views.py @@ -22,7 +22,7 @@ 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 answer_set, answer, embody_answer from app.models import user, trial_randomization from app.forms import Answers, TaskForm, ContinueTaskForm, StringForm @@ -33,22 +33,18 @@ task_blueprint = Blueprint("task", __name__, def get_randomized_page(page_id): + """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""" - #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 randomized_page = trial_randomization.query.filter(and_( trial_randomization.answer_set_idanswer_set==session['answer_set'], trial_randomization.page_idpage==page_id - #trial_randomization.page_idpage==pages.items[0].idpage )).first() return randomized_page -def add_slider_answer(key, value, randomized_page_id): +def add_slider_answer(key, value, randomized_page_id=None): '''Insert slider value 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''' @@ -59,7 +55,8 @@ def add_slider_answer(key, value, randomized_page_id): db.session.commit() -def update_answer_set(): +def update_answer_set_page(): + """Increment the page number by one in answer_set when user goes to next page""" the_time = datetime.now() the_time = the_time.replace(microsecond=0) @@ -69,6 +66,18 @@ def update_answer_set(): db.session.commit() +def update_answer_set_type(answer_type): + """If there are multiple question types(embody,slider,...) on one page, + then update the current question type""" + the_time = datetime.now() + the_time = the_time.replace(microsecond=0) + + updated_answer_set = answer_set.query.filter_by(idanswer_set=session['answer_set']).first() + updated_answer_set.answer_type = answer_type + updated_answer_set.last_answer_time = the_time + db.session.commit() + + def slider_question_has_answers(user, page_id): '''This should return true IF there are questions from certain page and no answers''' @@ -83,9 +92,63 @@ def slider_question_has_answers(user, page_id): questions = question.query.filter_by(experiment_idexperiment=experiment_id).all() + # TODO: should return true if there are no slider questions!!!! + return (True if (len(answers) == 0 and len(questions) > 0) else False) +def select_form_type(): + """Select form type based on the value in answer_set->answer_type""" + + form = None + answer_set_type = answer_set.query.filter_by(idanswer_set=session['answer_set']).first().answer_type + + if answer_set_type == 'slider': + form = TaskForm() + + # Get sliders from this experiment + categories = question.query.filter_by(experiment_idexperiment=session['exp_id']).all() + categories_and_scales = {} + for cat in categories: + scale_list = [(cat.left, cat.right)] + categories_and_scales[cat.idquestion, cat.question] = scale_list + form.categories1 = categories_and_scales + else: + form = StringForm() + + return form + +def check_if_answer_exists(answer_type, page_id): + """Check if there is already answer on certain experiment->page""" + check_answer = None + + if answer_type == 'embody': + check_answer = embody_answer.query.filter(and_(embody_answer.answer_set_idanswer_set==session['answer_set'], embody_answer.page_idpage==session['current_idpage'])).first() + elif answer_type == 'slider': + check_answer = answer.query.filter(and_(answer.answer_set_idanswer_set==session['answer_set'], answer.page_idpage==session['current_idpage'])).first() + + return (check_answer, None) + +def check_if_randomized_answer_exists(answer_type, page_id): + """Check if there is already answer on certain experiment->page if the pages are in randomized order""" + check_answer = randomized_page_id = None + + if answer_type == 'embody': + randomized_page_id = get_randomized_page(page_id).randomized_idpage + check_answer = embody_answer.query.filter(and_(embody_answer.answer_set_idanswer_set==session['answer_set'], embody_answer.page_idpage==randomized_page_id)).first() + elif answer_type == 'slider': + randomized_page_id = get_randomized_page(page_id).randomized_idpage + check_answer = answer.query.filter(and_(answer.answer_set_idanswer_set==session['answer_set'], answer.page_idpage==randomized_page_id)).first() + + return (check_answer, randomized_page_id) + + +################################ +### ### +### ROUTES ### +### ### +################################ + @task_blueprint.route('/embody/<int:page_num>', methods=['POST']) def task_embody(page_num): '''Save embody drawing to database''' @@ -96,19 +159,34 @@ def task_embody(page_num): if form.validate(): data = request.form.to_dict() - coordinates = json.loads(data['coordinates']) - print("x:",coordinates['x']) - print("y:",coordinates['y']) + # Check if randomization ON and if user has already answered to embody question + if session['randomization'] == 'On': + check_answer, randomized_page_id = check_if_randomized_answer_exists('embody',page_id) + else: + check_answer, randomized_page_id = check_if_answer_exists('embody',page_id) + # Add answer to DB + if check_answer is None: + page_idpage = session['current_idpage'] if session['randomization'] == 'Off' else randomized_page_id + + # Add new embody answer + participant_answer = embody_answer(answer_set_idanswer_set=session['answer_set'], coordinates=data['coordinates'], page_idpage=page_idpage) + db.session.add(participant_answer) + + # Update answer set type + answer_set.query.filter_by(idanswer_set=session['answer_set']).first().answer_type = 'slider' + + db.session.commit() + else: + flash("Page has been answered already. Answers discarded") - # Test that everything OK - return json.dumps(coordinates) # Check if there are unanswered slider questions if slider_question_has_answers(session['user'], page_id): - return redirect( url_for('task.task', page_num=page_num, show_sliders=True)) + update_answer_set_type('slider') + return redirect( url_for('task.task', page_num=page_num)) if not pages.has_next: return redirect ( url_for('task.completed')) @@ -126,23 +204,17 @@ def task_answer(page_num): page_id = pages.items[0].idpage if 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 + # Check if randomization ON and if user has already answered to slider question if session['randomization'] == 'On': - randomized_page_id = get_randomized_page(page_id).randomized_idpage - check_answer = answer.query.filter(and_(answer.answer_set_idanswer_set==session['answer_set'], answer.page_idpage==randomized_page_id)).first() + check_answer, randomized_page_id = check_if_randomized_answer_exists('slider',page_id) else: - check_answer = answer.query.filter(and_(answer.answer_set_idanswer_set==session['answer_set'], answer.page_idpage==session['current_idpage'])).first() + check_answer, randomized_page_id = check_if_answer_exists('slider',page_id) if check_answer is None: - update_answer_set() - data = request.form.to_dict() for key, value in data.items(): add_slider_answer(key, value, randomized_page_id) - else: flash("Page has been answered already. Answers discarded") @@ -151,11 +223,20 @@ def task_answer(page_num): if not pages.has_next: return redirect ( url_for('task.completed')) + update_answer_set_page() + + # If embody in use -> change the answer set type + exp_status = experiment.query.filter_by(idexperiment=session['exp_id']).first() + if exp_status.embody_enabled: + update_answer_set_type('embody') + return redirect( url_for('task.task', page_num=pages.next_num)) @task_blueprint.route('/<int:page_num>', methods=['GET']) def task(page_num): + """Get selected task page""" + randomized_stimulus="" try: experiment_info = experiment.query.filter_by(idexperiment=session['exp_id']).first() @@ -163,22 +244,16 @@ def task(page_num): print(err) flash("No valid session found") return redirect('/') - - - 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 = experiment_info.stimulus_size 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) page_id = pages.items[0].idpage 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': @@ -188,39 +263,18 @@ def task(page_num): for p in pages.items: session['current_idpage'] = p.idpage - print(session) - - # Select form type (TODO: question order is now harcoded to EMBODY -> SLIDERS - # there should be more flexible solution if more question types are added...) - if request.args.get('show_sliders', False) or not session['embody']: - # Init slider form - form = TaskForm() - - # Get sliders from this experiment - categories = question.query.filter_by(experiment_idexperiment=session['exp_id']).all() - - categories_and_scales = {} - for cat in categories: - scale_list = [(cat.left, cat.right)] - categories_and_scales[cat.idquestion, cat.question] = scale_list - - form.categories1 = categories_and_scales - - else: - form = StringForm() - return render_template( 'task.html', pages=pages, page_num=page_num, progress_bar_percentage=progress_bar_percentage, - form=form, + form=select_form_type(), randomized_stimulus=randomized_stimulus, - rating_instruction=rating_instruction, + rating_instruction=experiment_info.single_sentence_instruction, stimulus_size=stimulus_size, stimulus_size_text=stimulus_size_text, experiment_info=experiment_info - ) + ) @task_blueprint.route('/completed') @@ -261,7 +315,6 @@ def continue_task(): exp = experiment.query.filter_by(idexperiment=session['exp_id']).first() session['randomization'] = exp.randomization - session['embody'] = exp.embody_enabled if mediatype: session['type'] = mediatype.type diff --git a/create_rating_db.sql b/create_rating_db.sql index 46ec6278aa2c85a647ed7364f12c8ea42f87dd4a..b454ca37d8b9b3d43bb38b1b26b89cd6068d0951 100644 --- a/create_rating_db.sql +++ b/create_rating_db.sql @@ -55,6 +55,10 @@ CREATE TABLE answer_set ( FOREIGN KEY(experiment_idexperiment) REFERENCES experiment (idexperiment) ); +ALTER TABLE answer_set ADD COLUMN (answer_type VARCHAR(120)); + +/* TODO: Update answer_set so it knows which part of the page the user is doing (embody/sliders/something else) */ + /* Background questions are asked before the experiment begins */ CREATE TABLE background_question ( idbackground_question INTEGER NOT NULL AUTO_INCREMENT,