Shuffle answers in multiple choice questions
[oweals/karmaworld.git] / karmaworld / apps / quizzes / create_quiz.py
1 from copy import copy
2 import random
3 from karmaworld.apps.quizzes.models import Keyword
4
5
6 class BaseQuizQuestion(object):
7
8     def question_type(self):
9         return self.__class__.__name__
10
11
12 class MultipleChoiceQuestion(BaseQuizQuestion):
13     def __init__(self, question_text, choices):
14         self.question_text = question_text
15         self.choices = choices
16
17     def __unicode__(self):
18         return u"Multiple choice: {0}: {1}".format(self.question_text, ", ".join(map(str, self.choices)))
19
20     def __str__(self):
21         return unicode(self)
22
23     def __repr__(self):
24         return str(self)
25
26
27 class MultipleChoiceOption(object):
28     def __init__(self, text, correct):
29         self.text = text
30         self.correct = correct
31
32     def __unicode__(self):
33         return self.text
34
35     def __str__(self):
36         return unicode(self)
37
38     def __repr__(self):
39         return str(self)
40
41
42 class TrueFalseQuestion(BaseQuizQuestion):
43     def __init__(self, question_text, true):
44         self.question_text = question_text
45         self.true = true
46
47     def __unicode__(self):
48         return u"True or false: {0}".format(self.question_text)
49
50     def __str__(self):
51         return unicode(self)
52
53     def __repr__(self):
54         return str(self)
55
56
57 class MatchingQuestion(BaseQuizQuestion):
58     def __init__(self, question_text, left_column, right_column, left_right_mapping):
59         self.question_text = question_text
60         self.left_column = left_column
61         self.right_column = right_column
62         self.left_right_mapping = left_right_mapping
63
64     def rows(self):
65         return zip(self.left_column, self.right_column)
66
67     def __unicode__(self):
68         return u"Matching question: {0} / {1}".format(self.left_column, self.right_column)
69
70     def __str__(self):
71         return unicode(self)
72
73     def __repr__(self):
74         return str(self)
75
76
77 KEYWORD_MULTIPLE_CHOICE = 1
78 DEFINITION_MULTIPLE_CHOICE = 2
79 KEYWORD_DEFINITION_TRUE_FALSE = 3
80 KEYWORD_DEFINITION_MATCHING = 4
81 GENERATED_QUESTION_TYPE = (
82     KEYWORD_MULTIPLE_CHOICE,
83     DEFINITION_MULTIPLE_CHOICE,
84     KEYWORD_DEFINITION_TRUE_FALSE,
85     KEYWORD_DEFINITION_MATCHING,
86 )
87
88 MULTIPLE_CHOICE_CHOICES = 4
89
90
91 def _create_keyword_multiple_choice(keyword, keywords):
92     choices = [MultipleChoiceOption(text=keyword.word, correct=True)]
93
94     for other_keyword in random.sample(keywords.exclude(id=keyword.id), MULTIPLE_CHOICE_CHOICES - 1):
95         choices.append(MultipleChoiceOption(
96                        text=other_keyword.word,
97                        correct=False))
98
99     question_text = u'Pick the keyword to match "{d}"'.format(d=keyword.definition)
100
101     random.shuffle(choices)
102
103     return MultipleChoiceQuestion(question_text, choices)
104
105
106 def _create_definition_multiple_choice(keyword, keywords):
107     choices = [MultipleChoiceOption(text=keyword.definition, correct=True)]
108
109     for other_keyword in random.sample(keywords.exclude(id=keyword.id), MULTIPLE_CHOICE_CHOICES - 1):
110         choices.append(MultipleChoiceOption(
111                        text=other_keyword.definition,
112                        correct=False))
113
114     question_text = u'Pick the definition to match "{w}"'.format(w=keyword.word)
115
116     random.shuffle(choices)
117
118     return MultipleChoiceQuestion(question_text, choices)
119
120
121 def _create_keyword_definition_true_false(keyword, keywords):
122     true = random.choice((True, False))
123
124     if true:
125         definition = keyword.definition
126     else:
127         other_keyword = random.choice(keywords.exclude(id=keyword.id))
128         definition = other_keyword.definition
129
130     question_text = u'The keyword "{w}" matches the definition "{d}"'. \
131         format(w=keyword.word, d=definition)
132
133     return TrueFalseQuestion(question_text, true)
134
135
136 def _create_keyword_definition_matching(keyword, keywords):
137     question_keywords = [keyword]
138     question_keywords.extend(random.sample(keywords.exclude(id=keyword.id), MULTIPLE_CHOICE_CHOICES - 1))
139
140     answer_mapping = {k.word: k.definition for k in question_keywords}
141     word_column = [k.word for k in question_keywords]
142     random.shuffle(word_column)
143     definition_column = [k.definition for k in question_keywords]
144     random.shuffle(definition_column)
145
146     question_text = u'Match the words with their definitions'
147
148     return MatchingQuestion(question_text, left_column=word_column,
149                             right_column=definition_column, left_right_mapping=answer_mapping)
150
151
152 def quiz_from_keywords(note):
153     keywords = Keyword.objects.filter(note=note).exclude(word__iexact='').exclude(definition__iexact='')
154     questions = []
155
156     if len(keywords) < 4:
157         return []
158
159     for keyword in keywords:
160         if keyword.word and keyword.definition:
161             question_type = random.choice(GENERATED_QUESTION_TYPE)
162
163             if question_type is KEYWORD_MULTIPLE_CHOICE:
164                 questions.append(_create_keyword_multiple_choice(keyword, keywords))
165
166             elif question_type is DEFINITION_MULTIPLE_CHOICE:
167                 questions.append(_create_definition_multiple_choice(keyword, keywords))
168
169             elif question_type is KEYWORD_DEFINITION_TRUE_FALSE:
170                 questions.append(_create_keyword_definition_true_false(keyword, keywords))
171
172             elif question_type is KEYWORD_DEFINITION_MATCHING:
173                 questions.append(_create_keyword_definition_matching(keyword, keywords))
174
175     return questions
176
177