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:
parent
7646eff6d8
commit
ff85faf46a
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue