basic support for file upload quiz question type

note: this currently just adds all the necessary hooks for a new question type,
and is currently disabled while the specific file upload functionality is
implemented.

closes CNVS-1150

test plan:
- should see no change with adding quiz questions, taking a quiz, or viewing
  statistics

Change-Id: I3360f00055d1d0c4f9c9703d70590e888cd636d7
Reviewed-on: https://gerrit.instructure.com/19560
QA-Review: Myller de Araujo <myller@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
Product-Review: Simon Williams <simon@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
This commit is contained in:
Simon Williams 2013-04-12 11:49:59 -06:00
parent 7646eff6d8
commit ff85faf46a
7 changed files with 113 additions and 38 deletions

View File

@ -184,6 +184,14 @@ module QuizzesHelper
false,
false
),
"file_upload_question" => QuestionType.new(
"file_upload_question",
"file",
"single",
"file_answer",
false,
false
),
"matching_question" => QuestionType.new(
"matching_question",
"matching",

View File

@ -35,7 +35,7 @@ class AssessmentQuestion < ActiveRecord::Base
"multiple_choice_question", "numerical_question",
"text_only_question", "short_answer_question",
"multiple_dropdowns_question", "calculated_question",
"essay_question", "true_false_question"]
"essay_question", "true_false_question", "file_upload_question"]
serialize :question_data
@ -394,15 +394,6 @@ class AssessmentQuestion < ActiveRecord::Base
return question
end
def self.update_question(assessment_question, qdata)
question = parse_question(qdata, assessment_question)
assessment_question.question_data = question
assessment_question.name = question[:question_name]
assessment_question.save
question[:assessment_question_id] = assessment_question.id
question
end
def self.variable_id(variable)
Digest::MD5.hexdigest(["dropdown", variable, "instructure-key"].join(","))
end

View File

@ -0,0 +1,37 @@
#
# Copyright (C) 2012 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
class QuizQuestion::FileUploadQuestion < QuizQuestion::Base
def requires_manual_scoring?(user_answer)
true
end
def stats(responses)
stats = {:file_upload_responses => []}
responses.each do |response|
stats[:file_upload_responses] << {
# TODO: what does file upload quiz stats need?
# :user_id => response[:user_id],
# :filename => response[:text].strip
}
end
@question_data.merge stats
end
end

View File

@ -10,19 +10,20 @@
<div class="question">
<div class="header" style="background-color: transparent;">
<input type="text" style="width: 120px;" name="question_name" aria-label="<%= t "label.question.name", "Question name" %>"/>
<select class="question_type" name="question_type" style="width: 150px;" aria-label="<%= t "label.question.type", "Question type" %>">
<option value="multiple_choice_question" style="padding: 2px;"><%= t('question_type.multiple_choice', "Multiple Choice") %></option>
<option value="true_false_question" style="padding: 2px;"><%= t('question_type.true_false', "True/False") %></option>
<option value="short_answer_question" style="padding: 2px;"><%= t('question_type.short_answer', "Fill In the Blank") %></option>
<option value="fill_in_multiple_blanks_question" style="padding: 2px;"><%= t('question_type.fill_in_multiple_blanks', "Fill In Multiple Blanks") %></option>
<option value="multiple_answers_question" style="padding: 2px;"><%= t('question_type.multiple_answers', "Multiple Answers") %></option>
<option value="multiple_dropdowns_question" style="padding: 2px;"><%= t('question_type.multiple_dropdowns', "Multiple Dropdowns") %></option>
<option value="matching_question" style="padding: 2px;"><%= t('question_type.matching', "Matching") %></option>
<option value="numerical_question" style="padding: 2px;"><%= t('question_type.numerical', "Numerical Answer") %></option>
<option value="calculated_question" style="padding: 2px;"><%= t('question_type.calculated', "Formula Question") %></option>
<option value="missing_word_question" style="padding: 2px;" class="missing_word"><%= t('question_type.missing_word', "Missing Word") %></option>
<option value="essay_question" style="padding: 2px;"><%= t('question_type.essay', "Essay Question") %></option>
<option value="text_only_question" style="padding: 2px;"><%= t('question_type.text_only', "Text (no question)") %></option>
<select class="question_type" name="question_type" aria-label="<%= t "label.question.type", "Question type" %>">
<option value="multiple_choice_question"><%= t('question_type.multiple_choice', "Multiple Choice") %></option>
<option value="true_false_question"><%= t('question_type.true_false', "True/False") %></option>
<option value="short_answer_question"><%= t('question_type.short_answer', "Fill In the Blank") %></option>
<option value="fill_in_multiple_blanks_question"><%= t('question_type.fill_in_multiple_blanks', "Fill In Multiple Blanks") %></option>
<option value="multiple_answers_question"><%= t('question_type.multiple_answers', "Multiple Answers") %></option>
<option value="multiple_dropdowns_question"><%= t('question_type.multiple_dropdowns', "Multiple Dropdowns") %></option>
<option value="matching_question"><%= t('question_type.matching', "Matching") %></option>
<option value="numerical_question"><%= t('question_type.numerical', "Numerical Answer") %></option>
<option value="calculated_question"><%= t('question_type.calculated', "Formula Question") %></option>
<option value="missing_word_question" class="missing_word"><%= t('question_type.missing_word', "Missing Word") %></option>
<option value="essay_question"><%= t('question_type.essay', "Essay Question") %></option>
<%# TODO: add file upload option here %>
<option value="text_only_question"><%= t('question_type.text_only', "Text (no question)") %></option>
</select>
<span class="question_points_holder">
<span style="padding-left: 20px;"><%= before_label :points, 'pts' %></span>
@ -66,6 +67,9 @@
<div tabindex="0" class="essay_question_explanation explanation subheader">
<%= t('explanations.essay', "Students will be given a text field to compose their answer.") %>
</div>
<div tabindex="0" class="file_upload_question_explanation explanation subheader">
<%= t('explanations.file_upload', "Students will be able to upload a file for their answer.") %>
</div>
<div tabindex="0" class="text_only_question_explanation explanation subheader">
<%= t('explanations.text_only', 'This "question" will not be scored, but can be useful for introducing a set of related questions.') %>
</div>

View File

@ -105,17 +105,38 @@
<td>
<div class="essay_answers">
<% question[:essay_responses].reverse.each do |response| %>
<div class="essay_answer" style="padding-bottom: 5px;">
<div class="user_content">
<%= user_content(response[:text]) %>
</div>
<% if @quiz.graded? && !@quiz.anonymous_submissions? %>
<div class="user_name" style="text-align: right; font-style: italic;">
<%= @users[response[:user_id]].short_name %>
</div>
<% end %>
<div class="essay_answer" style="padding-bottom: 5px;">
<div class="user_content">
<%= user_content(response[:text]) %>
</div>
<% if @quiz.graded? && !@quiz.anonymous_submissions? %>
<div class="user_name" style="text-align: right; font-style: italic;">
<%= @users[response[:user_id]].short_name %>
</div>
<% end %>
</div>
<% end %>
</div>
</td>
</tr>
</table>
<% elsif question[:file_upload_responses] %>
<table class="results">
<tr>
<td>
<div class="file_upload_answers">
<% question[:file_upload_responses].reverse.each do |response| %>
<div class="file_upload_answer" style="padding-bottom: 5px;">
<div>
<%# TODO: what should be displayed for file upload quiz stats? %>
<%= response[:filename] %>
</div>
<% if @quiz.graded? && !@quiz.anonymous_submissions? %>
<div class="user_name" style="text-align: right; font-style: italic;">
<%= @users[response[:user_id]].short_name %>
</div>
<% end %>
</div>
<% end %>
</div>
</td>

View File

@ -9,5 +9,7 @@
<input type="text" name="question_<%= hash_get(question, :id) %>" value="<%= value %>" class="question_input numerical_question_input" autocomplete='off' <%= 'readonly="readonly"' unless editable %>/>
<% elsif question_type.entry_type == "textarea" %>
<textarea name="question_<%= hash_get(question, :id) %>" class="question_input" style="width: 90%;" autocomplete='off' <%= 'readonly="readonly"' unless editable %>><%= value %></textarea>
<% elsif question_type.entry_type == "file" %>
<input type="file" name="question_<%= hash_get(question, :id) %>" value="<%= value %>" class="question_input" <%= 'readonly="readonly"' unless editable %>/>
<% end %>
</div>

View File

@ -128,7 +128,7 @@ define([
// Determines whether or not to show the "show question details" link.
checkShowDetails: function() {
var hasQuestions = this.$questions.find('div.display_question:not(.essay_question, .text_only_question)').length;
var hasQuestions = this.$questions.find('div.display_question:not(.essay_question, .file_upload_question, .text_only_question)').length;
this.$showDetailsWrap[hasQuestions ? 'show' : 'hide'](200);
},
@ -211,7 +211,7 @@ define([
$answer.find(".comment_focus").attr('title', I18n.t('titles.click_to_enter_comments_on_answer', 'Click to enter comments for the student if they choose this answer'));
if (question_type == "essay_question") {
if (question_type == "essay_question" || question_type == "file_upload_question") {
templateData.comments_header = I18n.beforeLabel('comments_on_question', "Comments for this question");
} else if (question_type == "matching_question") {
templateData.answer_match_left_html = answer.answer_match_left_html;
@ -310,6 +310,10 @@ define([
answer_type = "comment";
question_type = "essay_question";
n_correct = "none";
} else if (qt == 'file_upload_question') {
answer_type = "comment";
question_type = "file_upload_question";
n_correct = "none";
} else if (qt == 'matching_question') {
answer_type = "matching_answer";
question_type = "matching_question";
@ -348,6 +352,8 @@ define([
result = "any_answer";
} else if (question_type == 'essay_question') {
result = "none";
} else if (question_type == 'file_upload_question') {
result = "none";
} else if (question_type == 'matching_question') {
result = "matching";
} else if (question_type == 'missing_word_question') {
@ -643,7 +649,7 @@ define([
} else if (question_type == 'short_answer_question') {
$formQuestion.removeClass('selectable');
result.answer_type = "short_answer";
} else if (question_type == 'essay_question') {
} else if (question_type == 'essay_question' || question_type == 'file_upload_question') {
$formQuestion.find(".answer").remove();
$formQuestion.removeClass('selectable');
$formQuestion.find(".answers_header").hide().end()
@ -1530,7 +1536,7 @@ define([
$form.find(".form_answers").append($answer);
});
}
if ($question.hasClass('essay_question')) {
if ($question.hasClass('essay_question') || $question.hasClass('file_upload')) {
$formQuestion.find(".comments_header").text(I18n.beforeLabel('comments_on_question', "Comments for this question"));
}
$question.hide().after($form);
@ -2026,6 +2032,12 @@ define([
}];
answer_type = "comment";
question_type = "essay_question";
} else if ($question.hasClass('file_upload_question')) {
var answers = [{
comments: I18n.t('default_response_to_file_upload', "Response to show student after they submit an answer")
}];
answer_type = "comment";
question_type = "file_upload_question";
} else if ($question.hasClass('matching_question')) {
var answers = [{
comments: I18n.t('default_comments_on_wrong_match', "Response if the user misses this match")
@ -2117,7 +2129,7 @@ define([
error_text = I18n.t('errors.no_possible_solution', "Please generate at least one possible solution");
}
} else if ($answers.length === 0 || $answers.filter(".correct_answer").length === 0) {
if ($answers.length === 0 && questionData.question_type != "essay_question" && questionData.question_type != "text_only_question") {
if ($answers.length === 0 && !_.contains(["essay_question", "file_upload_question", "text_only_question"], questionData.question_type)) {
error_text = I18n.t('errors.no_answer', "Please add at least one answer");
} else if ($answers.filter(".correct_answer").length === 0 && (questionData.question_type == "multiple_choice_question" || questionData.question_type == "true_false_question" || questionData.question_tyep == "missing_word_question")) {
error_text = I18n.t('errors.no_correct_answer', "Please choose a correct answer");