Matching-style quiz questions
authorCharles Connell <charles@connells.org>
Wed, 14 May 2014 15:05:04 +0000 (11:05 -0400)
committerCharles Connell <charles@connells.org>
Wed, 14 May 2014 15:05:04 +0000 (11:05 -0400)
karmaworld/apps/notes/templatetags/__init__.py [new file with mode: 0644]
karmaworld/apps/notes/templatetags/notes.py [new file with mode: 0644]
karmaworld/apps/quizzes/create_quiz.py
karmaworld/apps/users/templatetags/__init__.py
karmaworld/assets/css/note_course_pages.css
karmaworld/assets/js/note-detail.js
karmaworld/templates/notes/note_quiz.html

diff --git a/karmaworld/apps/notes/templatetags/__init__.py b/karmaworld/apps/notes/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/karmaworld/apps/notes/templatetags/notes.py b/karmaworld/apps/notes/templatetags/notes.py
new file mode 100644 (file)
index 0000000..cba5016
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+# -*- coding:utf8 -*-
+# Copyright (C) 2014  FinalsClub Foundation
+import string
+from django import template
+
+register = template.Library()
+
+@register.filter()
+def ordinal_letter(value):
+    try:
+        num = int(value)
+        result = num
+        if num >= 0 and num < 26:
+            result = string.ascii_uppercase[num]
+        return result
+    except ValueError:
+        return value
+
+
+@register.filter
+def keyvalue(dict, key):
+    return dict[key]
+
index 076a368bf25a82c569f0c19ad8a78f2b419cf2d4..d7fd8e0941b81f3f48f565c2fb3643efe190b4af 100644 (file)
@@ -54,13 +54,35 @@ class TrueFalseQuestion(BaseQuizQuestion):
         return str(self)
 
 
+class MatchingQuestion(BaseQuizQuestion):
+    def __init__(self, question_text, left_column, right_column, left_right_mapping):
+        self.question_text = question_text
+        self.left_column = left_column
+        self.right_column = right_column
+        self.left_right_mapping = left_right_mapping
+
+    def rows(self):
+        return zip(self.left_column, self.right_column)
+
+    def __unicode__(self):
+        return u"Matching question: {0} / {1}".format(self.left_column, self.right_column)
+
+    def __str__(self):
+        return unicode(self)
+
+    def __repr__(self):
+        return str(self)
+
+
 KEYWORD_MULTIPLE_CHOICE = 1
 DEFINITION_MULTIPLE_CHOICE = 2
 KEYWORD_DEFINITION_TRUE_FALSE = 3
+KEYWORD_DEFINITION_MATCHING = 4
 GENERATED_QUESTION_TYPE = (
     KEYWORD_MULTIPLE_CHOICE,
     DEFINITION_MULTIPLE_CHOICE,
     KEYWORD_DEFINITION_TRUE_FALSE,
+    KEYWORD_DEFINITION_MATCHING,
 )
 
 MULTIPLE_CHOICE_CHOICES = 4
@@ -107,6 +129,22 @@ def _create_keyword_definition_true_false(keyword, keywords):
     return TrueFalseQuestion(question_text, true)
 
 
+def _create_keyword_definition_matching(keyword, keywords):
+    question_keywords = [keyword]
+    question_keywords.extend(random.sample(keywords.exclude(id=keyword.id), MULTIPLE_CHOICE_CHOICES - 1))
+
+    answer_mapping = {k.word: k.definition for k in question_keywords}
+    word_column = [k.word for k in question_keywords]
+    random.shuffle(word_column)
+    definition_column = [k.definition for k in question_keywords]
+    random.shuffle(definition_column)
+
+    question_text = u'Match the words with their definitions'
+
+    return MatchingQuestion(question_text, left_column=word_column,
+                            right_column=definition_column, left_right_mapping=answer_mapping)
+
+
 def quiz_from_keywords(note):
     keywords = Keyword.objects.filter(note=note).exclude(word__iexact='').exclude(definition__iexact='')
     questions = []
@@ -127,6 +165,9 @@ def quiz_from_keywords(note):
             elif question_type is KEYWORD_DEFINITION_TRUE_FALSE:
                 questions.append(_create_keyword_definition_true_false(keyword, keywords))
 
+            elif question_type is KEYWORD_DEFINITION_MATCHING:
+                questions.append(_create_keyword_definition_matching(keyword, keywords))
+
     return questions
 
 
index 8be65774812b9141088b806cb961ef67dc47a103..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
@@ -1 +0,0 @@
-__author__ = 'charles'
index 1dec4fda1849d75b9d167a32e5e14f697fa0f911..e5096c2a525725c48413db511fcc09cf3241cc74 100644 (file)
@@ -193,3 +193,30 @@ button.add-note-btn {
   background-color: #740b00;
 }
 
+.matching-select-wrapper {
+  width: 40px;
+  margin: 0;
+  display: inline-block;
+}
+
+.matching-select {
+  width: 80px;
+  font-weight: bold;
+  margin: 0;
+}
+
+.matching-select option {
+  font-weight: bold;
+}
+
+.matching-select-label {
+  display: inline-block;
+  margin: 0 0 0 5px;
+  padding: 0;
+  vertical-align: top;
+  font-weight: bold;
+}
+
+.matching-choice {
+  margin: 0 0 15px 0;
+}
\ No newline at end of file
index 1718ee0699aea42df5bf913bb6fe5359ca26e6fe..3f83db2c6f7cd2d3126ef0ff26403d59a8d1a6f3 100644 (file)
@@ -311,13 +311,29 @@ function initQuizPage() {
     $('.quiz-question-wrapper').each(function() {
       var choice = $(this).find('input:checked');
       if (choice.length) {
-        console.log(choice);
         if (choice.data('correct') == true) {
           markQuestionCorrect($(this));
         } else {
           markQuestionIncorrect($(this));
         }
       }
+
+      var matching_correct = false;
+      var options_selected = $(this).find('option:selected');
+      if (options_selected.length > 0) {
+        matching_correct = true
+      }
+      options_selected.each(function() {
+        if ($(this).data('correct') == false) {
+          matching_correct = false;
+        }
+      });
+      if (matching_correct) {
+        markQuestionCorrect($(this));
+      } else {
+        markQuestionIncorrect($(this));
+      }
+
     });
   });
 }
index 9126d2439da5e50b94107d51cdbaf041265b370e..b248b705d54df37813169340e9e11ba686be8661 100644 (file)
@@ -1,3 +1,5 @@
+{% load notes %}
+
 <div id="quiz" class="content">
   <div class="row">
     <div class="small-12 columns">
                 <label for="choice_{{ forloop.counter0 }}_false">False</label>
               </div>
             {% endif %}
+            {% if question.question_type == 'MatchingQuestion' %}
+              {% for row in question.rows %}
+                <div class="question-choices row">
+                  <div class="small-6 columns">
+                    {% with left_row=row.0 %}
+                      <div class="matching-choice">
+                        <div class="select-wrapper matching-select-wrapper inline">
+                          <select class="inline matching-select" id="group_{ forloop.parentloop.counter0 }}_{{ forloop.counter0 }}">
+                            {% for right_row in question.right_column %}
+                              <option {% if question.left_right_mapping|keyvalue:left_row == right_row %}
+                                        data-correct="true"
+                                      {% else %}
+                                        data-correct="false"
+                                      {% endif %}>
+                                {{ forloop.counter0|ordinal_letter }}
+                              </option>
+                            {% endfor %}
+                          </select>
+                        </div>
+                        <label class="matching-select-label" for="group_{ forloop.parentloop.counter0 }}_{{ forloop.counter0 }}">{{ left_row }}</label>
+                      </div>
+                    {% endwith %}
+                  </div>
+                  <div class="small-6 columns">
+                    {% with right_row=row.1 %}
+                      <div class="matching-choice">
+                        <strong>{{ forloop.counter0|ordinal_letter }}.</strong> {{ right_row }}
+                      </div>
+                    {% endwith %}
+                  </div>
+                </div>
+              {% endfor %}
+            {% endif %}
           </div>
         </div>
       </div>