views.py 12.1 KB
Newer Older
1
import math
2
import json
3
4
5
from datetime import datetime

from flask import (
6
7
8
9
10
11
12
    Flask,
    render_template,
    request,
    session,
    flash,
    redirect,
    url_for,
13
14
    Blueprint
)
15
from flask_cors import CORS, cross_origin
16

17
from sqlalchemy import and_
18
from sqlalchemy import exc
19
20
21
22
23
from flask_babel import _, lazy_gettext as _l

from app import db
from app.models import experiment
from app.models import page, question
Ossi Laine's avatar
Ossi Laine committed
24
from app.models import answer_set, answer, embody_answer, embody_question
25
from app.models import user, trial_randomization
26
from app.forms import Answers, TaskForm, ContinueTaskForm, StringForm
27

28
29
30
31
task_blueprint = Blueprint("task", __name__,
                           template_folder='templates',
                           static_folder='static',
                           url_prefix='/task')
32
33


34
def get_randomized_page(page_id):
35
36
    """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"""
37
    return trial_randomization.get_randomized_page(page_id)
38
39


40
def add_slider_answer(key, value, page_id=None):
Ossi Laine's avatar
Ossi Laine committed
41
    """Insert slider value to database. If trial randomization is set to 'Off' 
42
    the values are inputted for session['current_idpage']. Otherwise the values 
Ossi Laine's avatar
Ossi Laine committed
43
    are set for the corresponding id found in the trial randomization table"""
44

45
46
    participant_answer = answer(
        question_idquestion=key, answer_set_idanswer_set=session['answer_set'], answer=value, page_idpage=page_id)
47
48
49
50
    db.session.add(participant_answer)
    db.session.commit()


51
52
def update_answer_set_page():
    """Increment the page number by one in answer_set when user goes to next page"""
53
54
55
    the_time = datetime.now()
    the_time = the_time.replace(microsecond=0)

56
57
58
59
    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
60
61
62
63
    update_answer_counter.last_answer_time = the_time
    db.session.commit()


64
65
66
67
68
69
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)

70
71
    updated_answer_set = answer_set.query.filter_by(
        idanswer_set=session['answer_set']).first()
72
73
74
75
76
77
78
79
80
    updated_answer_set.answer_type = answer_type
    updated_answer_set.last_answer_time = the_time
    db.session.commit()


def select_form_type():
    """Select form type based on the value in answer_set->answer_type"""

    form = None
81
82
    answer_set_type = answer_set.query.filter_by(
        idanswer_set=session['answer_set']).first().answer_type
83
84
85
86
87

    if answer_set_type == 'slider':
        form = TaskForm()

        # Get sliders from this experiment
88
89
        categories = question.query.filter_by(
            experiment_idexperiment=session['exp_id']).all()
90
        categories_and_scales = {}
91
        for cat in categories:
92
            scale_list = [(cat.left, cat.right)]
93
            categories_and_scales[cat.idquestion, cat.question] = scale_list
94
95
96
97
98
99
        form.categories1 = categories_and_scales
    else:
        form = StringForm()

    return form

100

101
102
103
104
105
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':
106
107
        check_answer = embody_answer.query.filter(and_(
            embody_answer.answer_set_idanswer_set == session['answer_set'], embody_answer.page_idpage == page_id)).first()
108
    elif answer_type == 'slider':
109
110
        check_answer = answer.query.filter(and_(
            answer.answer_set_idanswer_set == session['answer_set'], answer.page_idpage == page_id)).first()
111

112
    return check_answer
113
114


115
116
117
def next_page(pages):
    # If no more pages left -> complete task
    if not pages.has_next:
118
        return redirect(url_for('task.completed'))
119

120
    # If user has answered to all questions, then move to next page
121
    return redirect(url_for('task.task', page_num=pages.next_num))
122
123
124
125
126
127


def get_experiment_info():
    """Return experiment information from current session"""
    try:
        return experiment.query.filter_by(idexperiment=session['exp_id']).first()
Ossi Laine's avatar
Ossi Laine committed
128
    except KeyError as err:
129
130
131
        flash("No valid session found")
        return redirect('/')

132

133
134
135
136
137
138
139
def embody_on():
    """Check from session[exp_id] if embody questions are enabled"""
    experiment_info = get_experiment_info()
    if experiment_info.embody_enabled:
        return True
    else:
        return False
140
141


142
143
144
def slider_on():
    """Check from session[exp_id] if there are slider questions in this session"""
    experiment_info = get_experiment_info()
145
146
    questions = question.query.filter_by(
        experiment_idexperiment=experiment_info.idexperiment).all()
147
148
149
150
151

    if len(questions) == 0:
        return False
    return True

152

153
@task_blueprint.route('/embody/<int:page_num>', methods=['POST'])
Ossi Laine's avatar
Ossi Laine committed
154
@cross_origin()
155
def task_embody(page_num):
Ossi Laine's avatar
Ossi Laine committed
156
    '''Save embody drawing to database.'''
157
158

    form = StringForm(request.form)
159
160
    pages = page.query.filter_by(experiment_idexperiment=session['exp_id']).paginate(
        per_page=1, page=page_num, error_out=True)
161
162
163
164
    page_id = pages.items[0].idpage

    if form.validate():
        data = request.form.to_dict()
165
166
        coordinates = json.loads(data['coordinates'])

167
168
        # Check if randomization ON and if user has already answered to embody question
        if session['randomization'] == 'On':
169
170
            page_id = get_randomized_page(page_id).randomized_idpage

171
        check_answer = check_if_answer_exists('embody', page_id)
172

173
174
        # Add answer to DB
        if check_answer is None:
Ossi Laine's avatar
Ossi Laine committed
175
            for coordinate_data in coordinates:
176
                save_coordinates(coordinate_data, page_id)
177
178
        else:
            flash("Page has been answered already. Answers discarded")
179

180
181
    # Check if there are unanswered slider questions -> if true redirect to same page
    if slider_on():
182
        update_answer_set_type('slider')
183
        return redirect(url_for('task.task', page_num=page_num))
184

185
186
    update_answer_set_page()
    return next_page(pages)
187
188


189
190
191
192
193
194
195
196
197
198
199
200
def save_coordinates(coordinate_data, page_id):
    """All of the embody results from one page/stimulant is saved in this method"""
    idembody = int(coordinate_data['id'].split('-')[1])
    del coordinate_data['id']
    del coordinate_data['r']

    participant_answer = embody_answer(
        answer_set_idanswer_set=session['answer_set'], coordinates=json.dumps(coordinate_data), page_idpage=page_id, embody_question_idembody=idembody)
    db.session.add(participant_answer)
    db.session.commit()


201
@task_blueprint.route('/question/<int:page_num>', methods=['POST'])
202
def task_answer(page_num):
203
    '''Save slider answers to database'''
204

205
    form = TaskForm(request.form)
206
207
    pages = page.query.filter_by(experiment_idexperiment=session['exp_id']).paginate(
        per_page=1, page=page_num, error_out=True)
208
    page_id = pages.items[0].idpage
209

210
211
    if form.validate():

212
        # Check if randomization ON and if user has already answered to slider question
213
        if session['randomization'] == 'On':
214
            page_id = get_randomized_page(page_id).randomized_idpage
215
216

        check_answer = check_if_answer_exists('slider', page_id)
217
218
219
220

        if check_answer is None:
            data = request.form.to_dict()
            for key, value in data.items():
221
                add_slider_answer(key, value, page_id)
222
223
        else:
            flash("Page has been answered already. Answers discarded")
224

225
    if embody_on():
226
227
        update_answer_set_type('embody')

Ossi Laine's avatar
Ossi Laine committed
228
    # Always redirect to next page(stimulus) from sliders
229
230
    update_answer_set_page()
    return next_page(pages)
231
232
233
234


@task_blueprint.route('/<int:page_num>', methods=['GET'])
def task(page_num):
235
    """Get selected task page"""
236
    randomized_stimulus = ""
237
    experiment_info = get_experiment_info()
238
239
240

    # 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
241
    stimulus_size = experiment_info.stimulus_size
242
243
    stimulus_size_text = 7-math.ceil((int(stimulus_size)/2))

244
245
    pages = page.query.filter_by(experiment_idexperiment=session['exp_id']).paginate(
        per_page=1, page=page_num, error_out=True)
246
    page_id = pages.items[0].idpage
247
    progress_bar_percentage = round((pages.page/pages.pages)*100)
248
    session['current_idpage'] = page_id
249
250
251
252

    # 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':
253
        randomized_page_id = get_randomized_page(page_id).randomized_idpage
254
255
        randomized_stimulus = page.query.filter_by(
            idpage=randomized_page_id).first()
256

257
258
259
    # get all embody questions
    embody_questions = embody_question.query.filter_by(
        experiment_idexperiment=session['exp_id']).all()
Ossi Laine's avatar
Ossi Laine committed
260

261
    return render_template(
262
263
        'task.html',
        pages=pages,
264
        page_num=page_num,
265
266
267
268
269
        progress_bar_percentage=progress_bar_percentage,
        form=select_form_type(),
        randomized_stimulus=randomized_stimulus,
        rating_instruction=experiment_info.single_sentence_instruction,
        stimulus_size=stimulus_size,
270
        stimulus_size_text=stimulus_size_text,
Ossi Laine's avatar
Ossi Laine committed
271
272
        experiment_info=experiment_info,
        embody_questions=embody_questions
273
    )
274
275
276
277
278


@task_blueprint.route('/completed')
def completed():
    session.pop('user', None)
279
    session.pop('exp_id', None)
280
281
282
283
284
285
286
287
288
289
    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():
290
291
292
    """Continue unfinished task"""

    # Set experience if to session
293
    exp_id = request.args.get('exp_id', None)
294
295
    session['exp_id'] = exp_id

296
    form = ContinueTaskForm()
297

298
    if form.validate_on_submit():
299
300
301
        # 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()
302
303
        if participant is None:
            flash('Invalid ID')
304
305
306
            return redirect(url_for('task.continue_task', exp_id=exp_id))

        # if correct participant_id is found with the correct experiment ID; start session for that user
307
        exp = get_experiment_info()
308
        session['user'] = form.participant_id.data
309
        session['answer_set'] = participant.idanswer_set
310
        session['randomization'] = exp.randomization
311
312

        update_answer_set_type(participant.answer_type)
313
314
315

        mediatype = page.query.filter_by(
            experiment_idexperiment=session['exp_id']).first()
316
317
318
319
320
321
        if mediatype:
            session['type'] = mediatype.type
        else:
            flash('No pages or mediatype set for experiment')
            return redirect('/')

322
        # If participant has done just the registration redirect to the first page of the experiment
323
        if participant.answer_counter == 0:
324
325
            return redirect(url_for('task.task', page_num=1))

326
        redirect_to_page = participant.answer_counter + 1
327
328
329
330
        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
331
        if experiment_page_count == participant.answer_counter:
332
333
334
335
            return redirect(url_for('task.completed'))

        return redirect(url_for('task.task', page_num=redirect_to_page))

336
337
338
339
340
    return render_template('continue_task.html', exp_id=exp_id, form=form)


@task_blueprint.route('/quit')
def quit():
341

342
343
    user_id = session['user']
    session.pop('user', None)
344
    session.pop('exp_id', None)
345
346
347
    session.pop('agree', None)
    session.pop('answer_set', None)
    session.pop('type', None)
348

349
350
351
    return render_template('quit_task.html', user_id=user_id)


352
# EOF