lms.model.quizzes
1import os 2import typing 3 4import edq.util.dirent 5import edq.util.json 6import quizcomp.constants 7import quizcomp.group 8import quizcomp.question.base 9import quizcomp.quiz 10 11import lms.model.assignments 12import lms.model.base 13import lms.model.query 14 15QUESTIONS_DIRNAME: str = 'questions' 16 17class QuestionQuery(lms.model.query.BaseQuery): 18 """ 19 A class for the different ways one can attempt to reference an LMS quiz question. 20 In general, a quiz question can be queried by: 21 - LMS Question ID (`id`) 22 - Full Name (`name`) 23 - f"{name} ({id})" 24 """ 25 26 _include_email = False 27 28class ResolvedQuestionQuery(lms.model.query.ResolvedBaseQuery, QuestionQuery): 29 """ 30 A QuestionQuery that has been resolved (verified) from a real quiz question instance. 31 """ 32 33 _include_email = False 34 35 def __init__(self, 36 question: 'Question', 37 **kwargs: typing.Any) -> None: 38 super().__init__(id = question.id, name = question.name, **kwargs) 39 40class Question(lms.model.base.BaseType): 41 """ 42 A question within a quiz. 43 """ 44 45 CORE_FIELDS = [ 46 'id', 47 'question_type', 48 'name', 49 'prompt', 50 'points', 51 'answers', 52 ] 53 54 def __init__(self, 55 id: typing.Union[str, int, None] = None, 56 question_type: typing.Union[quizcomp.question.base.QuestionType, None] = None, 57 name: typing.Union[str, None] = None, 58 prompt: typing.Union[str, None] = None, 59 points: typing.Union[float, None] = None, 60 answers: typing.Union[typing.List[typing.Any], typing.Dict[str, typing.Any], None] = None, 61 group_id: typing.Union[str, None] = None, 62 resources: typing.Union[typing.List[str], None] = None, 63 **kwargs: typing.Any) -> None: 64 super().__init__(**kwargs) 65 66 if (id is None): 67 raise ValueError("Quiz questions must have an id.") 68 69 self.id: str = str(id) 70 """ The LMS's identifier for this question. """ 71 72 if (question_type is None): 73 raise ValueError("Quiz questions must have a type.") 74 75 self.question_type: quizcomp.question.base.QuestionType = question_type 76 """ The type of this question (multiple choice, essay, etc). """ 77 78 self.name: typing.Union[str, None] = name 79 """ The display name of this question. """ 80 81 self.prompt: typing.Union[str, None] = prompt 82 """ The prompt of this question. """ 83 84 self.points: typing.Union[float, None] = points 85 """ The number of points possible for this question. """ 86 87 if (answers is None): 88 answers = [] 89 90 self.answers: typing.Union[typing.List[typing.Any], typing.Dict[str, typing.Any]] = answers 91 """ Possible answers to this question. """ 92 93 self.group_id: typing.Union[str, None] = group_id 94 """ The id of the group this question belongs to (in context of the chosen quiz). """ 95 96 if (resources is None): 97 resources = [] 98 99 self.resources: typing.List[str] = resources 100 """ Paths to additional resources (e.g., images) associated with this question. """ 101 102 def to_query(self) -> ResolvedQuestionQuery: 103 """ Get a query representation of this question. """ 104 105 return ResolvedQuestionQuery(self) 106 107 def get_label(self) -> str: 108 """ Get the label for this question. """ 109 110 return f"{self.name} ({self.id})" 111 112 def write(self, base_dir: str, force: bool = False) -> str: 113 """ 114 Write this question to the given directory in Quiz Composer format and return the new directory for this question. 115 If `force` is true, then any existing directory with the same name will be overwritten, 116 otherwise an error will be raised. 117 """ 118 119 base_dir = os.path.abspath(base_dir) 120 121 dirname = self.get_label() 122 out_dir = os.path.join(base_dir, dirname) 123 124 if (os.path.exists(out_dir)): 125 if (not force): 126 raise ValueError(f"Path to write quiz question ('{dirname}') already exists: '{out_dir}'.") 127 128 edq.util.dirent.remove(out_dir) 129 130 edq.util.dirent.mkdir(out_dir) 131 132 question = self._to_quizcomp_data() 133 question.to_path(os.path.join(out_dir, quizcomp.constants.QUESTION_FILENAME)) 134 135 # Write resources to the same dir. 136 for resource_path in self.resources: 137 edq.util.dirent.move(resource_path, out_dir) 138 139 return out_dir 140 141 def _to_quizcomp_data(self) -> quizcomp.question.base.Question: 142 """ Get a QuizComp representation of this question. """ 143 144 data = { 145 'question_type': str(self.question_type), 146 'name': self.name, 147 'prompt': self.prompt, 148 'points': self.points, 149 'ids': {'lms': self.id}, 150 'answers': self.answers, 151 } 152 153 question = quizcomp.question.base.Question.from_dict(data) 154 question.validate() 155 156 return question 157 158class QuestionGroupQuery(lms.model.query.BaseQuery): 159 """ 160 A class for the different ways one can attempt to reference an LMS quiz question group. 161 In general, a quiz question group can be queried by: 162 - LMS Question Group ID (`id`) 163 - Full Name (`name`) 164 - f"{name} ({id})" 165 """ 166 167 _include_email = False 168 169class ResolvedQuestionGroupQuery(lms.model.query.ResolvedBaseQuery, QuestionGroupQuery): 170 """ 171 A QuestionGroupQuery that has been resolved (verified) from a real quiz question question instance. 172 """ 173 174 _include_email = False 175 176 def __init__(self, 177 group: 'QuestionGroup', 178 **kwargs: typing.Any) -> None: 179 super().__init__(id = group.id, name = group.name, **kwargs) 180 181class QuestionGroup(lms.model.base.BaseType): 182 """ 183 A question group within a quiz. 184 This allows a quiz to choose a specific number of questions for each part of the quiz, 185 e.g., the group may have 10 questions, and 3 are chosen when the quiz is given/generated. 186 """ 187 188 CORE_FIELDS = [ 189 'id', 190 'name', 191 'pick_count', 192 'points', 193 ] 194 195 def __init__(self, 196 id: typing.Union[str, int, None] = None, 197 name: typing.Union[str, None] = None, 198 pick_count: typing.Union[int, None] = None, 199 points: typing.Union[float, None] = None, 200 **kwargs: typing.Any) -> None: 201 super().__init__(**kwargs) 202 203 if (id is None): 204 raise ValueError("Quiz question groups must have an id.") 205 206 self.id: str = str(id) 207 """ The LMS's identifier for this group. """ 208 209 self.name: typing.Union[str, None] = name 210 """ The display name of this group. """ 211 212 self.pick_count: typing.Union[int, None] = pick_count 213 """ The number of questions to choose for this group. """ 214 215 self.points: typing.Union[float, None] = points 216 """ The number of points possible for this queston. """ 217 218 def to_query(self) -> ResolvedQuestionGroupQuery: 219 """ Get a query representation of this question group. """ 220 221 return ResolvedQuestionGroupQuery(self) 222 223 def get_label(self) -> str: 224 """ Get the label for this group. """ 225 226 return f"{self.name} ({self.id})" 227 228 def _to_quizcomp_data(self, 229 all_questions: typing.List[Question], 230 questions_rel_dir: str = QUESTIONS_DIRNAME, 231 ) -> typing.Dict[str, typing.Any]: 232 """ 233 Get a QuizComp representation of this group. 234 The path to each question will be based on the base dir and the question's label. 235 """ 236 237 group_question_paths = [] 238 for question in all_questions: 239 if (self.id == question.group_id): 240 path = os.path.join(questions_rel_dir, question.get_label()) 241 group_question_paths.append(path) 242 243 return { 244 'name': self.name, 245 'pick_count': self.pick_count, 246 'points': self.points, 247 'questions': group_question_paths, 248 'ids': {'lms': self.id}, 249 } 250 251class QuizQuery(lms.model.query.BaseQuery): 252 """ 253 A class for the different ways one can attempt to reference an LMS quiz. 254 In general, a quiz can be queried by: 255 - LMS Quiz ID (`id`) 256 - Full Name (`name`) 257 - f"{name} ({id})" 258 """ 259 260 _include_email = False 261 262class ResolvedQuizQuery(lms.model.query.ResolvedBaseQuery, QuizQuery): 263 """ 264 A QuizQuery that has been resolved (verified) from a real quiz instance. 265 """ 266 267 _include_email = False 268 269 def __init__(self, 270 quiz: 'Quiz', 271 **kwargs: typing.Any) -> None: 272 super().__init__(id = quiz.id, name = quiz.name, **kwargs) 273 274class Quiz(lms.model.assignments.Assignment): 275 """ 276 A quiz within a course. 277 """ 278 279 def __init__(self, 280 resources: typing.Union[typing.List[str], None] = None, 281 **kwargs: typing.Any) -> None: 282 super().__init__(**kwargs) 283 284 if (resources is None): 285 resources = [] 286 287 self.resources: typing.List[str] = resources 288 """ Paths to additional resources (e.g., images) associated with this quiz. """ 289 290 def to_query(self) -> ResolvedQuizQuery: # type: ignore[override] 291 """ Get a query representation of this quiz. """ 292 293 return ResolvedQuizQuery(self) 294 295 def write(self, base_dir: str, groups: typing.List[QuestionGroup], questions: typing.List[Question], force: bool = False) -> str: 296 """ 297 Write this quiz to the given directory in Quiz Composer format and return the new directory for this quiz. 298 This will also write out all the questions to "questions" dir in the returned directory. 299 If `force` is true, then any existing directory with the same name will be overwritten, 300 otherwise an error will be raised. 301 """ 302 303 base_dir = os.path.abspath(base_dir) 304 305 dirname = f"{self.name} ({self.id})" 306 out_dir = os.path.join(base_dir, dirname) 307 308 if (os.path.exists(out_dir)): 309 if (not force): 310 raise ValueError(f"Path to write quiz ('{dirname}') already exists: '{out_dir}'.") 311 312 edq.util.dirent.remove(out_dir) 313 314 edq.util.dirent.mkdir(out_dir) 315 316 quiz = self._to_quizcomp_data(groups, questions) 317 edq.util.json.dump_path(quiz, os.path.join(out_dir, quizcomp.constants.QUIZ_FILENAME), sort_keys = False, indent = 4) 318 319 questions_dir = os.path.join(out_dir, QUESTIONS_DIRNAME) 320 for question in questions: 321 question.write(questions_dir, force = force) 322 323 return out_dir 324 325 def _to_quizcomp_data(self, groups: typing.List[QuestionGroup], questions: typing.List[Question]) -> quizcomp.quiz.Quiz: 326 """ Get a QuizComp representation of this quiz. """ 327 328 quizcomp_groups = [group._to_quizcomp_data(questions) for group in groups] 329 330 return { 331 'title': self.name, 332 'description': self.description, 333 'groups': quizcomp_groups, 334 'ids': {'lms': self.id}, 335 }
18class QuestionQuery(lms.model.query.BaseQuery): 19 """ 20 A class for the different ways one can attempt to reference an LMS quiz question. 21 In general, a quiz question can be queried by: 22 - LMS Question ID (`id`) 23 - Full Name (`name`) 24 - f"{name} ({id})" 25 """ 26 27 _include_email = False
A class for the different ways one can attempt to reference an LMS quiz question. In general, a quiz question can be queried by:
29class ResolvedQuestionQuery(lms.model.query.ResolvedBaseQuery, QuestionQuery): 30 """ 31 A QuestionQuery that has been resolved (verified) from a real quiz question instance. 32 """ 33 34 _include_email = False 35 36 def __init__(self, 37 question: 'Question', 38 **kwargs: typing.Any) -> None: 39 super().__init__(id = question.id, name = question.name, **kwargs)
A QuestionQuery that has been resolved (verified) from a real quiz question instance.
Inherited Members
41class Question(lms.model.base.BaseType): 42 """ 43 A question within a quiz. 44 """ 45 46 CORE_FIELDS = [ 47 'id', 48 'question_type', 49 'name', 50 'prompt', 51 'points', 52 'answers', 53 ] 54 55 def __init__(self, 56 id: typing.Union[str, int, None] = None, 57 question_type: typing.Union[quizcomp.question.base.QuestionType, None] = None, 58 name: typing.Union[str, None] = None, 59 prompt: typing.Union[str, None] = None, 60 points: typing.Union[float, None] = None, 61 answers: typing.Union[typing.List[typing.Any], typing.Dict[str, typing.Any], None] = None, 62 group_id: typing.Union[str, None] = None, 63 resources: typing.Union[typing.List[str], None] = None, 64 **kwargs: typing.Any) -> None: 65 super().__init__(**kwargs) 66 67 if (id is None): 68 raise ValueError("Quiz questions must have an id.") 69 70 self.id: str = str(id) 71 """ The LMS's identifier for this question. """ 72 73 if (question_type is None): 74 raise ValueError("Quiz questions must have a type.") 75 76 self.question_type: quizcomp.question.base.QuestionType = question_type 77 """ The type of this question (multiple choice, essay, etc). """ 78 79 self.name: typing.Union[str, None] = name 80 """ The display name of this question. """ 81 82 self.prompt: typing.Union[str, None] = prompt 83 """ The prompt of this question. """ 84 85 self.points: typing.Union[float, None] = points 86 """ The number of points possible for this question. """ 87 88 if (answers is None): 89 answers = [] 90 91 self.answers: typing.Union[typing.List[typing.Any], typing.Dict[str, typing.Any]] = answers 92 """ Possible answers to this question. """ 93 94 self.group_id: typing.Union[str, None] = group_id 95 """ The id of the group this question belongs to (in context of the chosen quiz). """ 96 97 if (resources is None): 98 resources = [] 99 100 self.resources: typing.List[str] = resources 101 """ Paths to additional resources (e.g., images) associated with this question. """ 102 103 def to_query(self) -> ResolvedQuestionQuery: 104 """ Get a query representation of this question. """ 105 106 return ResolvedQuestionQuery(self) 107 108 def get_label(self) -> str: 109 """ Get the label for this question. """ 110 111 return f"{self.name} ({self.id})" 112 113 def write(self, base_dir: str, force: bool = False) -> str: 114 """ 115 Write this question to the given directory in Quiz Composer format and return the new directory for this question. 116 If `force` is true, then any existing directory with the same name will be overwritten, 117 otherwise an error will be raised. 118 """ 119 120 base_dir = os.path.abspath(base_dir) 121 122 dirname = self.get_label() 123 out_dir = os.path.join(base_dir, dirname) 124 125 if (os.path.exists(out_dir)): 126 if (not force): 127 raise ValueError(f"Path to write quiz question ('{dirname}') already exists: '{out_dir}'.") 128 129 edq.util.dirent.remove(out_dir) 130 131 edq.util.dirent.mkdir(out_dir) 132 133 question = self._to_quizcomp_data() 134 question.to_path(os.path.join(out_dir, quizcomp.constants.QUESTION_FILENAME)) 135 136 # Write resources to the same dir. 137 for resource_path in self.resources: 138 edq.util.dirent.move(resource_path, out_dir) 139 140 return out_dir 141 142 def _to_quizcomp_data(self) -> quizcomp.question.base.Question: 143 """ Get a QuizComp representation of this question. """ 144 145 data = { 146 'question_type': str(self.question_type), 147 'name': self.name, 148 'prompt': self.prompt, 149 'points': self.points, 150 'ids': {'lms': self.id}, 151 'answers': self.answers, 152 } 153 154 question = quizcomp.question.base.Question.from_dict(data) 155 question.validate() 156 157 return question
A question within a quiz.
55 def __init__(self, 56 id: typing.Union[str, int, None] = None, 57 question_type: typing.Union[quizcomp.question.base.QuestionType, None] = None, 58 name: typing.Union[str, None] = None, 59 prompt: typing.Union[str, None] = None, 60 points: typing.Union[float, None] = None, 61 answers: typing.Union[typing.List[typing.Any], typing.Dict[str, typing.Any], None] = None, 62 group_id: typing.Union[str, None] = None, 63 resources: typing.Union[typing.List[str], None] = None, 64 **kwargs: typing.Any) -> None: 65 super().__init__(**kwargs) 66 67 if (id is None): 68 raise ValueError("Quiz questions must have an id.") 69 70 self.id: str = str(id) 71 """ The LMS's identifier for this question. """ 72 73 if (question_type is None): 74 raise ValueError("Quiz questions must have a type.") 75 76 self.question_type: quizcomp.question.base.QuestionType = question_type 77 """ The type of this question (multiple choice, essay, etc). """ 78 79 self.name: typing.Union[str, None] = name 80 """ The display name of this question. """ 81 82 self.prompt: typing.Union[str, None] = prompt 83 """ The prompt of this question. """ 84 85 self.points: typing.Union[float, None] = points 86 """ The number of points possible for this question. """ 87 88 if (answers is None): 89 answers = [] 90 91 self.answers: typing.Union[typing.List[typing.Any], typing.Dict[str, typing.Any]] = answers 92 """ Possible answers to this question. """ 93 94 self.group_id: typing.Union[str, None] = group_id 95 """ The id of the group this question belongs to (in context of the chosen quiz). """ 96 97 if (resources is None): 98 resources = [] 99 100 self.resources: typing.List[str] = resources 101 """ Paths to additional resources (e.g., images) associated with this question. """
The common fields shared across backends for this type that are used for comparison and other operations. Child classes should set this to define how comparisons are made.
The type of this question (multiple choice, essay, etc).
The id of the group this question belongs to (in context of the chosen quiz).
103 def to_query(self) -> ResolvedQuestionQuery: 104 """ Get a query representation of this question. """ 105 106 return ResolvedQuestionQuery(self)
Get a query representation of this question.
108 def get_label(self) -> str: 109 """ Get the label for this question. """ 110 111 return f"{self.name} ({self.id})"
Get the label for this question.
113 def write(self, base_dir: str, force: bool = False) -> str: 114 """ 115 Write this question to the given directory in Quiz Composer format and return the new directory for this question. 116 If `force` is true, then any existing directory with the same name will be overwritten, 117 otherwise an error will be raised. 118 """ 119 120 base_dir = os.path.abspath(base_dir) 121 122 dirname = self.get_label() 123 out_dir = os.path.join(base_dir, dirname) 124 125 if (os.path.exists(out_dir)): 126 if (not force): 127 raise ValueError(f"Path to write quiz question ('{dirname}') already exists: '{out_dir}'.") 128 129 edq.util.dirent.remove(out_dir) 130 131 edq.util.dirent.mkdir(out_dir) 132 133 question = self._to_quizcomp_data() 134 question.to_path(os.path.join(out_dir, quizcomp.constants.QUESTION_FILENAME)) 135 136 # Write resources to the same dir. 137 for resource_path in self.resources: 138 edq.util.dirent.move(resource_path, out_dir) 139 140 return out_dir
Write this question to the given directory in Quiz Composer format and return the new directory for this question.
If force is true, then any existing directory with the same name will be overwritten,
otherwise an error will be raised.
159class QuestionGroupQuery(lms.model.query.BaseQuery): 160 """ 161 A class for the different ways one can attempt to reference an LMS quiz question group. 162 In general, a quiz question group can be queried by: 163 - LMS Question Group ID (`id`) 164 - Full Name (`name`) 165 - f"{name} ({id})" 166 """ 167 168 _include_email = False
A class for the different ways one can attempt to reference an LMS quiz question group. In general, a quiz question group can be queried by:
170class ResolvedQuestionGroupQuery(lms.model.query.ResolvedBaseQuery, QuestionGroupQuery): 171 """ 172 A QuestionGroupQuery that has been resolved (verified) from a real quiz question question instance. 173 """ 174 175 _include_email = False 176 177 def __init__(self, 178 group: 'QuestionGroup', 179 **kwargs: typing.Any) -> None: 180 super().__init__(id = group.id, name = group.name, **kwargs)
A QuestionGroupQuery that has been resolved (verified) from a real quiz question question instance.
Inherited Members
182class QuestionGroup(lms.model.base.BaseType): 183 """ 184 A question group within a quiz. 185 This allows a quiz to choose a specific number of questions for each part of the quiz, 186 e.g., the group may have 10 questions, and 3 are chosen when the quiz is given/generated. 187 """ 188 189 CORE_FIELDS = [ 190 'id', 191 'name', 192 'pick_count', 193 'points', 194 ] 195 196 def __init__(self, 197 id: typing.Union[str, int, None] = None, 198 name: typing.Union[str, None] = None, 199 pick_count: typing.Union[int, None] = None, 200 points: typing.Union[float, None] = None, 201 **kwargs: typing.Any) -> None: 202 super().__init__(**kwargs) 203 204 if (id is None): 205 raise ValueError("Quiz question groups must have an id.") 206 207 self.id: str = str(id) 208 """ The LMS's identifier for this group. """ 209 210 self.name: typing.Union[str, None] = name 211 """ The display name of this group. """ 212 213 self.pick_count: typing.Union[int, None] = pick_count 214 """ The number of questions to choose for this group. """ 215 216 self.points: typing.Union[float, None] = points 217 """ The number of points possible for this queston. """ 218 219 def to_query(self) -> ResolvedQuestionGroupQuery: 220 """ Get a query representation of this question group. """ 221 222 return ResolvedQuestionGroupQuery(self) 223 224 def get_label(self) -> str: 225 """ Get the label for this group. """ 226 227 return f"{self.name} ({self.id})" 228 229 def _to_quizcomp_data(self, 230 all_questions: typing.List[Question], 231 questions_rel_dir: str = QUESTIONS_DIRNAME, 232 ) -> typing.Dict[str, typing.Any]: 233 """ 234 Get a QuizComp representation of this group. 235 The path to each question will be based on the base dir and the question's label. 236 """ 237 238 group_question_paths = [] 239 for question in all_questions: 240 if (self.id == question.group_id): 241 path = os.path.join(questions_rel_dir, question.get_label()) 242 group_question_paths.append(path) 243 244 return { 245 'name': self.name, 246 'pick_count': self.pick_count, 247 'points': self.points, 248 'questions': group_question_paths, 249 'ids': {'lms': self.id}, 250 }
A question group within a quiz. This allows a quiz to choose a specific number of questions for each part of the quiz, e.g., the group may have 10 questions, and 3 are chosen when the quiz is given/generated.
196 def __init__(self, 197 id: typing.Union[str, int, None] = None, 198 name: typing.Union[str, None] = None, 199 pick_count: typing.Union[int, None] = None, 200 points: typing.Union[float, None] = None, 201 **kwargs: typing.Any) -> None: 202 super().__init__(**kwargs) 203 204 if (id is None): 205 raise ValueError("Quiz question groups must have an id.") 206 207 self.id: str = str(id) 208 """ The LMS's identifier for this group. """ 209 210 self.name: typing.Union[str, None] = name 211 """ The display name of this group. """ 212 213 self.pick_count: typing.Union[int, None] = pick_count 214 """ The number of questions to choose for this group. """ 215 216 self.points: typing.Union[float, None] = points 217 """ The number of points possible for this queston. """
The common fields shared across backends for this type that are used for comparison and other operations. Child classes should set this to define how comparisons are made.
219 def to_query(self) -> ResolvedQuestionGroupQuery: 220 """ Get a query representation of this question group. """ 221 222 return ResolvedQuestionGroupQuery(self)
Get a query representation of this question group.
252class QuizQuery(lms.model.query.BaseQuery): 253 """ 254 A class for the different ways one can attempt to reference an LMS quiz. 255 In general, a quiz can be queried by: 256 - LMS Quiz ID (`id`) 257 - Full Name (`name`) 258 - f"{name} ({id})" 259 """ 260 261 _include_email = False
A class for the different ways one can attempt to reference an LMS quiz. In general, a quiz can be queried by:
263class ResolvedQuizQuery(lms.model.query.ResolvedBaseQuery, QuizQuery): 264 """ 265 A QuizQuery that has been resolved (verified) from a real quiz instance. 266 """ 267 268 _include_email = False 269 270 def __init__(self, 271 quiz: 'Quiz', 272 **kwargs: typing.Any) -> None: 273 super().__init__(id = quiz.id, name = quiz.name, **kwargs)
A QuizQuery that has been resolved (verified) from a real quiz instance.
Inherited Members
275class Quiz(lms.model.assignments.Assignment): 276 """ 277 A quiz within a course. 278 """ 279 280 def __init__(self, 281 resources: typing.Union[typing.List[str], None] = None, 282 **kwargs: typing.Any) -> None: 283 super().__init__(**kwargs) 284 285 if (resources is None): 286 resources = [] 287 288 self.resources: typing.List[str] = resources 289 """ Paths to additional resources (e.g., images) associated with this quiz. """ 290 291 def to_query(self) -> ResolvedQuizQuery: # type: ignore[override] 292 """ Get a query representation of this quiz. """ 293 294 return ResolvedQuizQuery(self) 295 296 def write(self, base_dir: str, groups: typing.List[QuestionGroup], questions: typing.List[Question], force: bool = False) -> str: 297 """ 298 Write this quiz to the given directory in Quiz Composer format and return the new directory for this quiz. 299 This will also write out all the questions to "questions" dir in the returned directory. 300 If `force` is true, then any existing directory with the same name will be overwritten, 301 otherwise an error will be raised. 302 """ 303 304 base_dir = os.path.abspath(base_dir) 305 306 dirname = f"{self.name} ({self.id})" 307 out_dir = os.path.join(base_dir, dirname) 308 309 if (os.path.exists(out_dir)): 310 if (not force): 311 raise ValueError(f"Path to write quiz ('{dirname}') already exists: '{out_dir}'.") 312 313 edq.util.dirent.remove(out_dir) 314 315 edq.util.dirent.mkdir(out_dir) 316 317 quiz = self._to_quizcomp_data(groups, questions) 318 edq.util.json.dump_path(quiz, os.path.join(out_dir, quizcomp.constants.QUIZ_FILENAME), sort_keys = False, indent = 4) 319 320 questions_dir = os.path.join(out_dir, QUESTIONS_DIRNAME) 321 for question in questions: 322 question.write(questions_dir, force = force) 323 324 return out_dir 325 326 def _to_quizcomp_data(self, groups: typing.List[QuestionGroup], questions: typing.List[Question]) -> quizcomp.quiz.Quiz: 327 """ Get a QuizComp representation of this quiz. """ 328 329 quizcomp_groups = [group._to_quizcomp_data(questions) for group in groups] 330 331 return { 332 'title': self.name, 333 'description': self.description, 334 'groups': quizcomp_groups, 335 'ids': {'lms': self.id}, 336 }
A quiz within a course.
280 def __init__(self, 281 resources: typing.Union[typing.List[str], None] = None, 282 **kwargs: typing.Any) -> None: 283 super().__init__(**kwargs) 284 285 if (resources is None): 286 resources = [] 287 288 self.resources: typing.List[str] = resources 289 """ Paths to additional resources (e.g., images) associated with this quiz. """
291 def to_query(self) -> ResolvedQuizQuery: # type: ignore[override] 292 """ Get a query representation of this quiz. """ 293 294 return ResolvedQuizQuery(self)
Get a query representation of this quiz.
296 def write(self, base_dir: str, groups: typing.List[QuestionGroup], questions: typing.List[Question], force: bool = False) -> str: 297 """ 298 Write this quiz to the given directory in Quiz Composer format and return the new directory for this quiz. 299 This will also write out all the questions to "questions" dir in the returned directory. 300 If `force` is true, then any existing directory with the same name will be overwritten, 301 otherwise an error will be raised. 302 """ 303 304 base_dir = os.path.abspath(base_dir) 305 306 dirname = f"{self.name} ({self.id})" 307 out_dir = os.path.join(base_dir, dirname) 308 309 if (os.path.exists(out_dir)): 310 if (not force): 311 raise ValueError(f"Path to write quiz ('{dirname}') already exists: '{out_dir}'.") 312 313 edq.util.dirent.remove(out_dir) 314 315 edq.util.dirent.mkdir(out_dir) 316 317 quiz = self._to_quizcomp_data(groups, questions) 318 edq.util.json.dump_path(quiz, os.path.join(out_dir, quizcomp.constants.QUIZ_FILENAME), sort_keys = False, indent = 4) 319 320 questions_dir = os.path.join(out_dir, QUESTIONS_DIRNAME) 321 for question in questions: 322 question.write(questions_dir, force = force) 323 324 return out_dir
Write this quiz to the given directory in Quiz Composer format and return the new directory for this quiz.
This will also write out all the questions to "questions" dir in the returned directory.
If force is true, then any existing directory with the same name will be overwritten,
otherwise an error will be raised.