Commit e7246628 authored by Ossi Laine's avatar Ossi Laine
Browse files

Added mock version of a new question type (embody coloring tool)

parent 4ec55731
......@@ -84,5 +84,6 @@ app.register_blueprint(create_blueprint)
app.secret_key = 'random string'
"""app.secret_key = os.urandom(24)"""
app.jinja_env.auto_reload = True
from app import routes, models
......@@ -9,9 +9,8 @@
<table class="table">
<tbody>
{% for exp in experiment_info %}
<!-- Why there is a for loop her ?! -->
<tr>
<td nowrap>Name:</td>
......@@ -215,53 +214,70 @@
<h1 class="container mt-5 display-4 text-left"><br>Rating set:</h1>
<hr>
TODO: add embody tool
<table class="table">
<colgroup>
<col style="width:33%;%">
<col style="width:33%;%">
<col style="width:33%;%">
</colgroup>
<tr>
<td>Add embody tool:</td>
<td>
{% if experiment_info[0].embody_enabled %}
Enabled
{% else %}
Disabled
{% endif %}
</td>
<td><a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.set_embody', exp_id=exp_id) }}" role="button">Enable embody</a></td>
</tr>
</table>
<hr>
<br>
{% for category in categories1 %}
{% for scale in categories1[category] %}
<div class="row form-group">
<div class="col-2 text-center">
<p>{{ scale[0] }}</p>
</div>
<div class="col text-center">
<label for="customRange">{{ category[1] }}</label>
<input type="range" class="custom-range" id="customRange" name={{ category[0] }}>
</div>
<div class="col-2 text-center">
<p>{{ scale[1] }}</p>
</div>
<div class="col-2 text-center">
<button type="button" class="btn btn-primary btn-sm btn-dark" data-toggle="modal" data-target="#mymodal{{category[0]}}">Remove</button>
<!-- Modal -->
<div class="modal fade" id="mymodal{{category[0]}}" role="dialog">
<div class="modal-dialog modal-dialog-centered" id="{{category[0]}}">
<!-- Modal content-->
<div class="modal-content modal-dialog-centered">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Notice!</h4>
</div>
<div class="modal-body">
<p>Are you sure you want to remove this?</p>
</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('experiment.remove_question', idquestion=category[0], exp_id=exp_id) }}" role="button">Yes, remove</a>
</div>
</div>
{% for category in categories1 %}
{% for scale in categories1[category] %}
<div class="row form-group">
<div class="col-2 text-center">
<p>{{ scale[0] }}</p>
</div>
<div class="col text-center">
<label for="customRange">{{ category[1] }}</label>
<input type="range" class="custom-range" id="customRange" name={{ category[0] }}>
</div>
<div class="col-2 text-center">
<p>{{ scale[1] }}</p>
</div>
<div class="col-2 text-center">
<button type="button" class="btn btn-primary btn-sm btn-dark" data-toggle="modal" data-target="#mymodal{{category[0]}}">Remove</button>
<!-- Modal -->
<div class="modal fade" id="mymodal{{category[0]}}" role="dialog">
<div class="modal-dialog modal-dialog-centered" id="{{category[0]}}">
<!-- Modal content-->
<div class="modal-content modal-dialog-centered">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Notice!</h4>
</div>
<div class="modal-body">
<p>Are you sure you want to remove this?</p>
</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('experiment.remove_question', idquestion=category[0], exp_id=exp_id) }}" role="button">Yes, remove</a>
</div>
</div>
<a class="btn btn-primary btn-sm btn-info" href="{{ url_for('experiment.edit_question', idquestion=category[0]) }}" role="button">Edit</a>
</div>
</div>
</div>
<a class="btn btn-primary btn-sm btn-info" href="{{ url_for('experiment.edit_question', idquestion=category[0]) }}" role="button">Edit</a>
</div>
{% endfor %}
{% endfor %}
</div>
{% endfor %}
{% endfor %}
</table>
<table class="table">
......
......@@ -79,15 +79,8 @@ def view():
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)
return render_template('view_experiment.html', exp_id=exp_id, media=media, mtype=mtype, experiment_info=experiment_info, categories1=categories1, questions1=questions1)
# Experiment info:
......@@ -556,6 +549,20 @@ def remove_bg_question():
# Rating set:
@experiment_blueprint.route('/set_embody')
@login_required
def set_embody():
'''Enable/disable embody tool'''
exp_id = request.args.get('exp_id', None)
exp = experiment.query.filter_by(idexperiment = exp_id).first()
exp.embody_enabled = (True if exp.embody_enabled == False else False)
db.session.commit()
return redirect(url_for('experiment.view', exp_id=exp_id))
@experiment_blueprint.route('/add_questions', methods=['GET', 'POST'])
@login_required
def add_questions():
......@@ -856,6 +863,8 @@ def remove_stimuli():
@experiment_blueprint.route('/statistics')
@login_required
def statistics():
# TODO: Answers are in normal order although questions might be in randomized order
exp_id = request.args.get('exp_id', None)
......@@ -879,51 +888,6 @@ def statistics():
#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:
......
......@@ -25,11 +25,17 @@ class RegisterForm(Form):
class TaskForm(Form):
__name__= 'slider'
categories1 = FieldList(SelectField([validators.InputRequired()]))
submit = SubmitField("Send")
class StringForm(Form):
__name__= 'embody'
text = StringField()
submit = SubmitField("Send")
class ContinueTaskForm(FlaskForm):
participant_id = StringField('participant_id', validators=[DataRequired()])
......
from app import db
from sqlalchemy import Column, Integer, String, Text
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, Integer, String, Text, Boolean
from flask_wtf import FlaskForm
from wtforms_sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
from flask_bootstrap import Bootstrap
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
from app import login
......@@ -53,9 +51,10 @@ class experiment (db.Model):
stimulus_size = db.Column(db.String(120))
consent_text = db.Column(db.Text, index=True)
use_forced_id = db.Column(db.String(120))
embody_enabled = db.Column(db.Boolean, unique=False, default=True)
def __repr__(self):
return "<idexperiment = '%s', name='%s', instruction='%s', directoryname='%s', language='%s', status='%s', randomization='%s', short_instruction='%s', single_sentence_instruction='%s', is_archived='%s', creator_name='%s', research_notification_filename='%s', creation_time='%s', stimulus_size='%s', consent_text='%s', use_forced_id='%s'>" % (self.idexperiment, self.name, self.instruction, self.directoryname, self.language, self.status, self.randomization, self.short_instruction, self.single_sentence_instruction, self.is_archived, self.creator_name, self.research_notification_filename, self.creation_time, self.stimulus_size, self.consent_text, self.use_forced_id)
return "<idexperiment = '%s', name='%s', instruction='%s', directoryname='%s', language='%s', status='%s', randomization='%s', short_instruction='%s', single_sentence_instruction='%s', is_archived='%s', creator_name='%s', research_notification_filename='%s', creation_time='%s', stimulus_size='%s', consent_text='%s', use_forced_id='%s', embody_enabled='%s'>" % (self.idexperiment, self.name, self.instruction, self.directoryname, self.language, self.status, self.randomization, self.short_instruction, self.single_sentence_instruction, self.is_archived, self.creator_name, self.research_notification_filename, self.creation_time, self.stimulus_size, self.consent_text, self.use_forced_id, self.embody_enabled)
class answer_set (db.Model):
......
......@@ -83,8 +83,7 @@ def remove_language():
@app.route('/session')
def participant_session():
# TODO: too long method?
'''Set up session variables'''
#start session
session['exp_id'] = request.args.get('exp_id', None)
......@@ -100,12 +99,11 @@ def participant_session():
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)
......@@ -115,30 +113,22 @@ def participant_session():
answer_counter = '0',
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
exp_status = experiment.query.filter_by(idexperiment=session['exp_id']).first()
if exp_status.randomization == 'On':
session['randomization'] = 'On'
#flash("answer_set_id")
#flash(participant_answer_set.idanswer_set)
#create a list of page id:s for the experiment
experiment_pages = page.query.filter_by(experiment_idexperiment=session['exp_id']).all()
original_id_order_list = [(int(o.idpage)) for o in experiment_pages]
#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
helper_list = original_id_order_list
randomized_order_list = []
......@@ -159,29 +149,33 @@ def participant_session():
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
#session_id_for_participant = answer_set.query.filter_by(session=session['user']).first()
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
mediatype = page.query.filter_by(experiment_idexperiment=session['exp_id']).first()
if mediatype:
session['type'] = mediatype.type
else:
flash('No pages or mediatype set for experiment')
return redirect('/')
# Redirect user to register page
if 'user' in session:
user = session['user']
#flash('Session started for user {}'.format(user))
return redirect('/register')
return "Session start failed return <a href = '/login'></b>" + "Home</b></a>"
......
body {
background-color: darkgrey;
font-family: Helvetica, Arial, sans-serif;
}
container {
text-decoration-color: #4EB1BA;
margin-left: auto;
margin-right: auto;
}
#header{
background-color: darkslategrey;
color: white;
......@@ -19,10 +9,13 @@ container {
}
#nav{
.hidden {
display: none;
}
#main{
#embody-canvas {
display: block;
margin: 0 auto;
margin-bottom: 30px;
}
\ No newline at end of file
$(document).ready(function() {
$("#canvas-data").val("hello world");
var canvas = $("#embody-canvas")
var context = document.getElementById("embody-canvas").getContext("2d");
canvas.mousedown(function(e){
var mouseX = e.pageX - this.offsetLeft;
var mouseY = e.pageY - this.offsetTop;
paint = true;
addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop);
redraw();
});
canvas.mousemove(function(e){
if(paint){
addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop, true);
redraw();
}
});
canvas.mouseup(function(e){
paint = false;
});
canvas.mouseleave(function(e){
paint = false;
});
var clickX = new Array();
var clickY = new Array();
var clickDrag = new Array();
var paint;
function addClick(x, y, dragging)
{
clickX.push(x);
clickY.push(y);
clickDrag.push(dragging);
}
function redraw(){
context.clearRect(0, 0, context.canvas.width, context.canvas.height); // Clears the canvas
context.strokeStyle = "#df4b26";
context.lineJoin = "round";
context.lineWidth = 5;
for(var i=0; i < clickX.length; i++) {
context.beginPath();
if (clickDrag[i] && i) {
context.moveTo(clickX[i-1], clickY[i-1]);
} else {
context.moveTo(clickX[i]-1, clickY[i]);
}
context.lineTo(clickX[i], clickY[i]);
context.closePath();
context.stroke();
}
}
function drawBaseImage()
{
var img = document.getElementById("baseImage");
var width = img.clientWidth;
var height = img.clientHeight;
context.canvas.height = height
context.canvas.width = width
context.drawImage(img, 0, 0);
img.classList.add("hidden")
}
drawBaseImage()
});
\ No newline at end of file
......@@ -8,6 +8,7 @@
<!-- 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">
......@@ -89,12 +90,36 @@
<br>
<h4 class="text-center">{{ rating_instruction }}</h4>
<form class="form-group mt-5" action="" method="post">
<!-- Select form type -->
{{page_num}}
{% if form.__name__ == 'embody' %}
<canvas id="embody-canvas" width="200" height="100" style="border: 1px solid blue;" ></canvas>
<img id="baseImage" class="" src={{ url_for('static', filename='img/dummy_600.png') }} />
<form class="form-group mt-5" action="/task/embody/{{ page_num }}" method="post">
<input id="canvas-data" type="hidden" value="" name="text">
<div class="form-row text-center">
<div class="col-12">
<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">
<br>
<p>{{ _('You can zoom in/out the page view by pressing ctrl+/ctrl- (Windows) or ⌘+/⌘- (Mac)') }} </p>
</div>
</div>
</form>
{% elif form.__name__ == 'slider' %}
<form class="form-group mt-5" action="/task/question/{{ page_num }}" method="post">
{% for category in form.categories1 %}
{% for scale in form.categories1[category] %}
<div class="row form-group mt-0 mb-0">
......@@ -104,7 +129,6 @@
<h6 class="col text-center mt-0 mb-0">
<label for="customRange">{{ category[1] }}</label>
<input type="range" class="custom-range" id="customRange" name={{ category[0] }}>
</h6>
<h6 class="col-3 text-left mt-0 mb-0">
{{ scale[1] }}
......@@ -112,16 +136,20 @@
</div>
{% endfor %}
{% endfor %}
<div class="form-row text-center">
<div class="col-12">
<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">
<br>
<p>{{ _('You can zoom in/out the page view by pressing ctrl+/ctrl- (Windows) or ⌘+/⌘- (Mac)') }} </p>
</div>
</div>
<div class="form-row text-center">
<div class="col-12">
<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">
<br>
<p>{{ _('You can zoom in/out the page view by pressing ctrl+/ctrl- (Windows) or ⌘+/⌘- (Mac)') }} </p>
</div>
</div>
</form>
{% endif %}
<script src="{{ url_for('static', filename='js/canvas.js') }}" ></script>
{% endblock %}
\ No newline at end of file
......@@ -23,7 +23,7 @@ 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
from app.forms import Answers, TaskForm, ContinueTaskForm, StringForm
task_blueprint = Blueprint("task", __name__,
template_folder='templates',
......@@ -31,19 +31,20 @@ task_blueprint = Blueprint("task", __name__,
url_prefix='/task')
def get_randomized_page(pages):
def get_randomized_page(page_id):
#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_id = trial_randomization.query.filter(and_(
randomized_page = trial_randomization.query.filter(and_(
trial_randomization.answer_set_idanswer_set==session['answer_set'],
trial_randomization.page_idpage==pages.items[0].idpage
trial_randomization.page_idpage==page_id
#trial_randomization.page_idpage==pages.items[0].idpage
)).first()
return randomized_page_id
return randomized_page
def add_slider_answer(key, value, randomized_page_id):
......@@ -51,19 +52,71 @@ def add_slider_answer(key, value, randomized_page_id):
the values are inputted for session['current_idpage']. Otherwise the values
are set for the corresponding id found in the trial randomization table'''
page_idpage = session['current_idpage'] if session['randomization'] == 'Off' else randomized_page_id.randomized_idpage