question: Add the Question.multimedia attribute
[quizzer.git] / quizzer / quiz.py
1 # Copyright (C) 2013 W. Trevor King <wking@tremily.us>
2 #
3 # This file is part of quizzer.
4 #
5 # quizzer is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 3 of the License, or (at your option) any later
8 # version.
9 #
10 # quizzer is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along with
15 # quizzer.  If not, see <http://www.gnu.org/licenses/>.
16
17 import codecs as _codecs
18 import json as _json
19 import os.path as _os_path
20
21 from . import __version__
22 from . import question as _question
23
24
25 class Quiz (list):
26     def __init__(self, questions=None, path=None, encoding=None,
27                  copyright=None, introduction=None):
28         if questions is None:
29             questions = []
30         super(Quiz, self).__init__(questions)
31         self.path = path
32         self.encoding = encoding
33         self.copyright = copyright
34         self.introduction = introduction
35
36     def _open(self, mode='r', path=None, encoding=None):
37         if path:
38             self.path = path
39         if encoding:
40             self.encoding = encoding
41         return _codecs.open(self.path, mode, self.encoding)
42
43     def load(self, **kwargs):
44         with self._open(mode='r', **kwargs) as f:
45             data = _json.load(f)
46         version = data.get('version', None)
47         if version != __version__:
48             try:
49                 upgrader = getattr(
50                     self, '_upgrade_from_{}'.format(version.replace('.', '_')))
51             except AttributeError as e:
52                 raise NotImplementedError('upgrade from {} to {}'.format(
53                         version, __version__)) from e
54             data = upgrader(data)
55         self.copyright = data.get('copyright', None)
56         self.introduction = data.get('introduction', None)
57         for state in data['questions']:
58             question_class_name = state.pop('class', 'Question')
59             question_class = _question.QUESTION_CLASS[question_class_name]
60             q = question_class()
61             q.__setstate__(state)
62             self.append(q)
63
64     def save(self, **kwargs):
65         questions = []
66         for question in self:
67             state = question.__getstate__()
68             state['class'] = type(question).__name__
69         data = {
70             'version': __version__,
71             'copyright': self.copyright,
72             'introduction': self.introduction,
73             'questions': questions,
74             }
75         with self._open(mode='w', **kwargs) as f:
76             _json.dump(
77                 data, f, indent=2, separators=(',', ': '), sort_keys=True)
78             f.write('\n')
79
80     def leaf_questions(self):
81         "Questions that are not dependencies of other question"
82         dependents = set()
83         for question in self:
84             dependents.update(question.dependencies)
85         return [q for q in self if q.id not in dependents]
86
87     def get(self, id=None):
88         matches = [q for q in self if q.id == id]
89         if len(matches) == 1:
90             return matches[0]
91         elif len(matches) == 0:
92             raise KeyError(id)
93         raise NotImplementedError(
94             'multiple questions with one ID: {}'.format(matches))
95
96     def multimedia_path(self, multimedia):
97         if 'path' in multimedia:
98             basedir = _os_path.dirname(self.path)
99             path = multimedia['path']
100             if _os_path.sep != '/':  # convert to native separators
101                 path = path.replace('/', _os_path.sep)
102             return _os_path.join(basedir, multimedia['path'])
103         else:
104             raise NotImplementedError(question.multimedia)
105
106     def _upgrade_from_0_1(self, data):
107         data['version'] = __version__
108         return data
109
110     _upgrade_from_0_2 = _upgrade_from_0_1
111     _upgrade_from_0_3 = _upgrade_from_0_1