export/import question banks

refs #3396

Change-Id: I4199264bf07e145be2b3949bc4a56a37cda715b7
Reviewed-on: https://gerrit.instructure.com/3223
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Zach Wily <zach@instructure.com>
This commit is contained in:
Bracken Mosbacker 2011-04-22 19:12:39 -06:00 committed by Zach Wily
parent 090eaf6d96
commit 31374465fe
10 changed files with 156 additions and 53 deletions

View File

@ -391,7 +391,7 @@ class AssessmentQuestion < ActiveRecord::Base
end
def self.process_migration(data, migration)
question_data = {}
question_data = {:aq_data=>{}, :qq_data=>{}}
questions = data['assessment_questions'] ? data['assessment_questions']['assessment_questions'] : []
questions ||= []
to_import = migration.to_import 'quizzes'
@ -425,6 +425,10 @@ class AssessmentQuestion < ActiveRecord::Base
banks = {}
questions.each do |question|
if question[:assessment_question_migration_id]
question_data[:qq_data][question['migration_id']] = question
next
end
question[:question_bank_name] = nil if question[:question_bank_name] == ''
question[:question_bank_name] ||= bank_map[question[:migration_id]]
question[:question_bank_name] ||= migration.question_bank_name
@ -441,7 +445,7 @@ class AssessmentQuestion < ActiveRecord::Base
end
question = AssessmentQuestion.import_from_migration(question, migration.context, banks[hash_id])
question_data[question['migration_id']] = question
question_data[:aq_data][question['migration_id']] = question
end
end
@ -461,8 +465,7 @@ class AssessmentQuestion < ActiveRecord::Base
end
end
context.imported_migration_items << bank if context.imported_migration_items && !context.imported_migration_items.include?(bank)
hash[:question_text] = ImportedHtmlConverter.convert(hash[:question_text], context, true) if hash[:question_text]
hash[:answers].each{ |answer| answer[:html] = ImportedHtmlConverter.convert(answer[:html], context, true) unless answer[:html].blank? } if hash[:answers]
prep_for_import(hash, context)
question_data = ActiveRecord::Base.connection.quote hash.to_yaml
question_name = ActiveRecord::Base.connection.quote hash[:question_name]
query = "INSERT INTO assessment_questions (name, question_data, context_id, context_type, workflow_state, created_at, updated_at, assessment_question_bank_id, migration_id)"
@ -472,6 +475,11 @@ class AssessmentQuestion < ActiveRecord::Base
hash
end
def self.prep_for_import(hash, context)
hash[:question_text] = ImportedHtmlConverter.convert(hash[:question_text], context, true) if hash[:question_text]
hash[:answers].each{ |answer| answer[:html] = ImportedHtmlConverter.convert(answer[:html], context, true) unless answer[:html].blank? } if hash[:answers]
end
named_scope :active, lambda {
{:conditions => ['assessment_questions.workflow_state != ?', 'deleted'] }
}

View File

@ -1129,11 +1129,20 @@ class Quiz < ActiveRecord::Base
hash[:questions].each do |question|
case question[:question_type]
when "question_reference"
if aq = question_data[question[:migration_id]]
if qq = question_data[:qq_data][question[:migration_id]]
if qq[:assessment_question_migration_id]
if aq = question_data[:aq_data][qq[:assessment_question_migration_id]]
qq['assessment_question_id'] = aq['assessment_question_id']
AssessmentQuestion.prep_for_import(qq, context)
QuizQuestion.import_from_migration(qq, context, item)
else
AssessmentQuestion.import_from_migration(qq, context)
QuizQuestion.import_from_migration(qq, context, item)
end
end
elsif aq = question_data[:aq_data][question[:migration_id]]
aq[:points_possible] = question[:points_possible] if question[:points_possible]
QuizQuestion.import_from_migration(aq, context, item)
else
#TODO: no assessment question was imported for this question...
end
when "question_group"
QuizGroup.import_from_migration(question, context, item, question_data)

View File

@ -83,10 +83,20 @@ class QuizGroup < ActiveRecord::Base
end
item.save!
hash[:questions].each do |question|
if aq = question_data[question[:migration_id]]
if qq = question_data[:qq_data][question[:migration_id]]
if qq[:assessment_question_migration_id]
if aq = question_data[:aq_data][qq[:assessment_question_migration_id]]
qq['assessment_question_id'] = aq['assessment_question_id']
AssessmentQuestion.prep_for_import(qq, context)
QuizQuestion.import_from_migration(qq, context, quiz, item)
else
AssessmentQuestion.import_from_migration(qq, context)
QuizQuestion.import_from_migration(qq, context, quiz, item)
end
end
elsif aq = question_data[:aq_data][question[:migration_id]]
aq[:points_possible] = question[:points_possible] if question[:points_possible]
QuizQuestion.import_from_migration(aq, context, quiz, item)
else
#TODO: no assessment question was imported for this question...
end
end

View File

@ -127,7 +127,10 @@ class QuizQuestion < ActiveRecord::Base
def self.import_from_migration(hash, context, quiz=nil, quiz_group=nil)
question_data = ActiveRecord::Base.connection.quote hash.to_yaml
query = "INSERT INTO quiz_questions (quiz_id, quiz_group_id, assessment_question_id, question_data, created_at, updated_at, migration_id)"
query += " VALUES (#{quiz ? quiz.id : 'NULL'}, #{quiz_group ? quiz_group.id : 'NULL'}, #{hash['assessment_question_id']},#{question_data},'#{Time.now.to_s(:db)}', '#{Time.now.to_s(:db)}', '#{hash[:migration_id]}')"
aq_id = hash['assessment_question_id'] ? hash['assessment_question_id'] : 'NULL'
g_id = quiz_group ? quiz_group.id : 'NULL'
q_id = quiz ? quiz.id : 'NULL'
query += " VALUES (#{q_id}, #{g_id}, #{aq_id},#{question_data},'#{Time.now.to_s(:db)}', '#{Time.now.to_s(:db)}', '#{hash[:migration_id]}')"
id = ActiveRecord::Base.connection.insert(query)
hash[:quiz_question_id] = id
hash

View File

@ -41,6 +41,17 @@ module CC
non_cc_folder = File.join(@export_dir, ASSESSMENT_NON_CC_FOLDER)
FileUtils::mkdir_p non_cc_folder
@course.assessment_question_banks.active.each do |bank|
bank_mig_id = create_key(bank)
rel_path = File.join(ASSESSMENT_NON_CC_FOLDER, bank_mig_id + ".xml")
full_path = File.join(@export_dir, rel_path)
File.open(full_path, 'w') do |file|
doc = Builder::XmlMarkup.new(:target=>file, :indent=>2)
generate_bank(doc, bank, bank_mig_id)
end
end
@course.quizzes.active.each do |quiz|
cc_qti_migration_id = create_key(quiz)
resource_dir = File.join(@export_dir, cc_qti_migration_id)
@ -160,7 +171,7 @@ module CC
if for_cc
add_cc_question(section_node, item)
else
add_question(section_node, item)
add_quiz_question(section_node, item)
end
elsif item[:questions] # It's a QuizGroup
if for_cc
@ -175,6 +186,28 @@ module CC
end # qti node
end
def generate_bank(doc, bank, migration_id)
doc.instruct!
doc.questestinterop("xmlns" => "http://www.imsglobal.org/xsd/ims_qtiasiv1p2",
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
"xsi:schemaLocation"=> "http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd"
) do |qti_node|
qti_node.objectbank(
:ident => migration_id
) do |bank_node|
bank_node.qtimetadata do |meta_node|
meta_field(meta_node, 'bank_title', bank.title)
end # meta_node
bank.assessment_questions.each do |aq|
add_question(bank_node, aq.data.with_indifferent_access)
end
end # bank_node
end # qti node
end
def meta_field(node, label, entry)
node.qtimetadatafield do |meta_node|
meta_node.fieldlabel label
@ -228,7 +261,7 @@ module CC
unless group[:assessment_question_bank_id]
group[:questions].each do |question|
add_question(section_node, question)
add_quiz_question(section_node, question)
end
end
end # section node

View File

@ -38,6 +38,28 @@ module CC
MULTI_ANSWER_TYPES = ['matching_question',
'multiple_dropdowns_question',
'fill_in_multiple_blanks_question']
def add_ref_or_question(node, question)
aq = nil
unless question[:assessment_question_id].blank?
if aq = AssessmentQuestion.find_by_id(question[:assessment_question_id])
if aq.deleted? ||
!aq.assessment_question_bank ||
aq.assessment_question_bank.deleted? ||
aq.assessment_question_bank.context_id != @course.id ||
aq.assessment_question_bank.context_type != @course.class.to_s
aq = nil
end
end
end
if aq
ref = CC::CCHelper::create_key(aq)
node.itemref(:linkrefid => ref)
else
add_question(node, question)
end
end
# if the question is a supported CC type it will be added
# it it's not supported it's just skipped
@ -48,8 +70,16 @@ module CC
true
end
def add_quiz_question(node, question)
question[:is_quiz_question] = true
add_question(node, question)
end
def add_question(node, question, for_cc=false)
question['migration_id'] = create_key("assessment_question_#{question['assessment_question_id']}")
aq_mig_id = create_key("assessment_question_#{question['assessment_question_id']}")
qq_mig_id = create_key("assessment_question_#{question['id']}")
question['migration_id'] = question[:is_quiz_question] ? qq_mig_id : aq_mig_id
if question['question_type'] == 'missing_word_question'
change_missing_word(question)
end
@ -67,6 +97,9 @@ module CC
else
meta_field(qm_node, 'question_type', question['question_type'])
meta_field(qm_node, 'points_possible', question['points_possible'])
if question[:is_quiz_question]
meta_field(qm_node, 'assessment_question_identifierref', aq_mig_id)
end
end
end
end # meta data

View File

@ -50,12 +50,12 @@ def get_import_data(sub_folder, hash_name)
end
def import_example_questions(context)
question_data = {}
question_data = {:aq_data=>{}, :qq_data=>{}}
QUESTIONS.each do |question|
if import_data_exists?(['vista', 'quiz'], question[0])
q = get_import_data ['vista', 'quiz'], question[0]
q = AssessmentQuestion.import_from_migration(q, context)
question_data[q['migration_id']] = q
question_data[:aq_data][q['migration_id']] = q
end
end
question_data

View File

@ -85,42 +85,47 @@ class AssessmentItemConverter
end
def parse_instructure_metadata
if bank = get_node_att(@doc, 'instructureMetadata instructureField[name=question_bank]', 'value')
@question[:question_bank_name] = bank
end
if bank = get_node_att(@doc, 'instructureMetadata instructureField[name=question_bank_iden]', 'value')
@question[:question_bank_id] = bank
end
if score = get_node_att(@doc, 'instructureMetadata instructureField[name=max_score]', 'value')
@question[:points_possible] = score.to_f
end
if score = get_node_att(@doc, 'instructureMetadata instructureField[name=points_possible]', 'value')
@question[:points_possible] = score.to_f
end
if type = get_node_att(@doc, 'instructureMetadata instructureField[name=bb_question_type]', 'value')
@migration_type = type
case @migration_type
when 'True/False'
@question[:question_type] = 'true_false_question'
when 'Short Response'
@question[:question_type] = 'essay_question'
when 'Fill in the Blank Plus'
@question[:question_type] = 'fill_in_multiple_blanks_question'
when 'WCT_FillInTheBlank'
@question[:question_type] = 'fill_in_multiple_blanks_question'
@question[:is_vista_fib] = true
when 'Jumbled Sentence'
@question[:question_type] = 'multiple_dropdowns_question'
when 'Essay'
@question[:question_type] = 'essay_question'
if meta = @doc.at_css('instructureMetadata')
if bank = get_node_att(meta, 'instructureField[name=question_bank]', 'value')
@question[:question_bank_name] = bank
end
end
if type = get_node_att(@doc, 'instructureMetadata instructureField[name=question_type]', 'value')
@migration_type = type
if AssessmentQuestion::ALL_QUESTION_TYPES.member?(@migration_type)
@question[:question_type] = @migration_type
elsif @migration_type =~ /matching/i
@question[:question_type] = 'matching_question'
if bank = get_node_att(meta, 'instructureField[name=question_bank_iden]', 'value')
@question[:question_bank_id] = bank
end
if score = get_node_att(meta, 'instructureField[name=max_score]', 'value')
@question[:points_possible] = score.to_f
end
if score = get_node_att(meta, 'instructureField[name=points_possible]', 'value')
@question[:points_possible] = score.to_f
end
if ref = get_node_att(meta, 'instructureField[name=assessment_question_identifierref]', 'value')
@question[:assessment_question_migration_id] = ref
end
if type = get_node_att(meta, 'instructureField[name=bb_question_type]', 'value')
@migration_type = type
case @migration_type
when 'True/False'
@question[:question_type] = 'true_false_question'
when 'Short Response'
@question[:question_type] = 'essay_question'
when 'Fill in the Blank Plus'
@question[:question_type] = 'fill_in_multiple_blanks_question'
when 'WCT_FillInTheBlank'
@question[:question_type] = 'fill_in_multiple_blanks_question'
@question[:is_vista_fib] = true
when 'Jumbled Sentence'
@question[:question_type] = 'multiple_dropdowns_question'
when 'Essay'
@question[:question_type] = 'essay_question'
end
end
if type = get_node_att(meta, 'instructureField[name=question_type]', 'value')
@migration_type = type
if AssessmentQuestion::ALL_QUESTION_TYPES.member?(@migration_type)
@question[:question_type] = @migration_type
elsif @migration_type =~ /matching/i
@question[:question_type] = 'matching_question'
end
end
end
end

View File

@ -19,13 +19,14 @@ Unknown text type: ignored mattext with texttype="text" treated as text/plain
<assessmentItem
xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p1 http://www.imsglobal.org/xsd/imsqti_v2p1.xsd" identifier="i7ee7c77592c6cd4ac58509c3e41dace8"
xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p1 http://www.imsglobal.org/xsd/imsqti_v2p1.xsd" identifier="i27a2844e09afc2eb6e4a6bf0599bf010"
title="Match"
adaptive="false"
timeDependent="false">
<instructureMetadata>
<instructureField name="points_possible" value="1" />
<instructureField name="question_type" value="matching_question" />
<instructureField name="assessment_question_identifierref" value="i7ee7c77592c6cd4ac58509c3e41dace8" />
</instructureMetadata>
<responseDeclaration identifier="response_1971" cardinality="single" baseType="identifier"/>
<responseDeclaration identifier="response_338" cardinality="single" baseType="identifier"/>

View File

@ -208,7 +208,8 @@ module CanvasExpected
:match_id=>2840,
:text=>"4"}],
:question_text=>"Make it stop! Please!",
:migration_id=>"i7ee7c77592c6cd4ac58509c3e41dace8",
:migration_id=>"i27a2844e09afc2eb6e4a6bf0599bf010",
:assessment_question_migration_id=>"i7ee7c77592c6cd4ac58509c3e41dace8",
:question_type=>"matching_question",
:incorrect_comments=>"How could you get this wrong?"}