Commit 9e3600ef49e63cb0db817c25fe76d2bbd52e0a70
1 parent
3d24dd0f
Exists in
master
and in
1 other branch
- internal changes in how a test is configured.
Showing
5 changed files
with
64 additions
and
38 deletions
Show diff stats
BUGS.md
@@ -6,6 +6,7 @@ | @@ -6,6 +6,7 @@ | ||
6 | - hints nao funciona | 6 | - hints nao funciona |
7 | - uniformizar question.py com a de aprendizations... | 7 | - uniformizar question.py com a de aprendizations... |
8 | - permitir eliminar teste a decorrer | 8 | - permitir eliminar teste a decorrer |
9 | +- eventos unfocus? | ||
9 | - servidor nao esta a lidar com eventos scroll/resize. ignorar? | 10 | - servidor nao esta a lidar com eventos scroll/resize. ignorar? |
10 | 11 | ||
11 | # TODO | 12 | # TODO |
@@ -18,7 +19,7 @@ | @@ -18,7 +19,7 @@ | ||
18 | node_modules/mathjax-node-cli/bin/tex2svg '\sqrt{x}' | 19 | node_modules/mathjax-node-cli/bin/tex2svg '\sqrt{x}' |
19 | usar isto para gerar svg que passa a fazer parte do texto da pergunta (markdown suporta tags svg?) | 20 | usar isto para gerar svg que passa a fazer parte do texto da pergunta (markdown suporta tags svg?) |
20 | fazer funçao tex() que recebe formula e converte para svg. exemplo: | 21 | fazer funçao tex() que recebe formula e converte para svg. exemplo: |
21 | - fr'''A formula é {tex(\sqrt{x]})}''' | 22 | + fr'''A formula é {tex("\sqrt{x]}")}''' |
22 | 23 | ||
23 | - Gerar pdf's com todos os testes no final (pdfkit). | 24 | - Gerar pdf's com todos os testes no final (pdfkit). |
24 | - manter registo dos unfocus durante o teste e de qual a pergunta visivel nesse momento | 25 | - manter registo dos unfocus durante o teste e de qual a pergunta visivel nesse momento |
app.py
@@ -12,6 +12,7 @@ from sqlalchemy.orm import sessionmaker, scoped_session | @@ -12,6 +12,7 @@ from sqlalchemy.orm import sessionmaker, scoped_session | ||
12 | # this project | 12 | # this project |
13 | from models import Student, Test, Question | 13 | from models import Student, Test, Question |
14 | import test | 14 | import test |
15 | +from tools import load_yaml | ||
15 | 16 | ||
16 | logger = logging.getLogger(__name__) | 17 | logger = logging.getLogger(__name__) |
17 | 18 | ||
@@ -23,7 +24,7 @@ class AppException(Exception): | @@ -23,7 +24,7 @@ class AppException(Exception): | ||
23 | # Application | 24 | # Application |
24 | # ============================================================================ | 25 | # ============================================================================ |
25 | class App(object): | 26 | class App(object): |
26 | - def __init__(self, filename, conf): | 27 | + def __init__(self, conf={}): |
27 | # online = { | 28 | # online = { |
28 | # uid1: { | 29 | # uid1: { |
29 | # 'student': {'number': 123, 'name': john, ...}, | 30 | # 'student': {'number': 123, 'name': john, ...}, |
@@ -35,8 +36,17 @@ class App(object): | @@ -35,8 +36,17 @@ class App(object): | ||
35 | self.online = dict() # {uid: {'student':{}}} | 36 | self.online = dict() # {uid: {'student':{}}} |
36 | self.allowed = set([]) # '0' is hardcoded to allowed elsewhere | 37 | self.allowed = set([]) # '0' is hardcoded to allowed elsewhere |
37 | 38 | ||
39 | + # build test configuration dictionary | ||
40 | + testconf = {} | ||
41 | + if conf['filename']: | ||
42 | + logger.info(f'Loading test configuration "{conf["filename"]}".') | ||
43 | + testconf.update(load_yaml(conf['filename'])) | ||
44 | + | ||
45 | + testconf.update(conf) # configuration overrides | ||
46 | + | ||
47 | + # start test factory | ||
38 | try: | 48 | try: |
39 | - self.testfactory = test.TestFactory(filename, conf=conf) | 49 | + self.testfactory = test.TestFactory(testconf) |
40 | except test.TestFactoryException: | 50 | except test.TestFactoryException: |
41 | logger.critical('Can\'t create test factory.') | 51 | logger.critical('Can\'t create test factory.') |
42 | raise AppException() | 52 | raise AppException() |
serve.py
@@ -8,6 +8,8 @@ import logging.config | @@ -8,6 +8,8 @@ import logging.config | ||
8 | import json | 8 | import json |
9 | import base64 | 9 | import base64 |
10 | import uuid | 10 | import uuid |
11 | +# from mimetypes import guess_type | ||
12 | + | ||
11 | 13 | ||
12 | # packages | 14 | # packages |
13 | import tornado.ioloop | 15 | import tornado.ioloop |
@@ -28,7 +30,7 @@ class WebApplication(tornado.web.Application): | @@ -28,7 +30,7 @@ class WebApplication(tornado.web.Application): | ||
28 | (r'/test', TestHandler), | 30 | (r'/test', TestHandler), |
29 | (r'/review', ReviewHandler), | 31 | (r'/review', ReviewHandler), |
30 | (r'/admin', AdminHandler), | 32 | (r'/admin', AdminHandler), |
31 | - (r'/static/(.+)', FileHandler), # FIXME | 33 | + (r'/file/(.+)', FileHandler), # FIXME |
32 | (r'/', RootHandler), # TODO multiple tests | 34 | (r'/', RootHandler), # TODO multiple tests |
33 | ] | 35 | ] |
34 | 36 | ||
@@ -93,17 +95,33 @@ class LogoutHandler(BaseHandler): | @@ -93,17 +95,33 @@ class LogoutHandler(BaseHandler): | ||
93 | # FIXME checkit | 95 | # FIXME checkit |
94 | class FileHandler(BaseHandler): | 96 | class FileHandler(BaseHandler): |
95 | @tornado.web.authenticated | 97 | @tornado.web.authenticated |
96 | - def get(self, filename): | 98 | + def get(self): |
97 | uid = self.current_user | 99 | uid = self.current_user |
98 | - public_dir = self.learn.get_current_public_dir(uid) # FIXME!!! | ||
99 | - filepath = path.expanduser(path.join(public_dir, filename)) | ||
100 | - try: | ||
101 | - f = open(filepath, 'rb') | ||
102 | - except FileNotFoundError: | ||
103 | - raise tornado.web.HTTPError(404) | ||
104 | - else: | ||
105 | - self.write(f.read()) | ||
106 | - f.close() | 100 | + qref = self.get_query_argument('ref') |
101 | + qfile = self.get_query_argument('filename') | ||
102 | + self.write(self.testapp.get_file(ref, filename)) | ||
103 | + | ||
104 | + | ||
105 | + # if not os.path.isfile(file_location): | ||
106 | + # raise tornado.web.HTTPError(status_code=404) | ||
107 | + | ||
108 | + # content_type, _ = guess_type(file_location) | ||
109 | + # self.add_header('Content-Type', content_type) | ||
110 | + # with open(file_location) as source_file: | ||
111 | + # self.write(source_file.read()) | ||
112 | + | ||
113 | + | ||
114 | + | ||
115 | + | ||
116 | + # public_dir = self.learn.get_current_public_dir(uid) # FIXME!!! | ||
117 | + # filepath = path.expanduser(path.join(public_dir, filename)) | ||
118 | + # try: | ||
119 | + # f = open(filepath, 'rb') | ||
120 | + # except FileNotFoundError: | ||
121 | + # raise tornado.web.HTTPError(404) | ||
122 | + # else: | ||
123 | + # self.write(f.read()) | ||
124 | + # f.close() | ||
107 | 125 | ||
108 | 126 | ||
109 | # ------------------------------------------------------------------------- | 127 | # ------------------------------------------------------------------------- |
@@ -309,7 +327,7 @@ def main(): | @@ -309,7 +327,7 @@ def main(): | ||
309 | logger_file = 'logger-debug.yaml' if arg.debug else 'logger.yaml' | 327 | logger_file = 'logger-debug.yaml' if arg.debug else 'logger.yaml' |
310 | SERVER_PATH = path.dirname(path.realpath(__file__)) | 328 | SERVER_PATH = path.dirname(path.realpath(__file__)) |
311 | LOGGER_CONF = path.join(SERVER_PATH, f'config/{logger_file}') | 329 | LOGGER_CONF = path.join(SERVER_PATH, f'config/{logger_file}') |
312 | - | 330 | + |
313 | try: | 331 | try: |
314 | logging.config.dictConfig(load_yaml(LOGGER_CONF)) | 332 | logging.config.dictConfig(load_yaml(LOGGER_CONF)) |
315 | except: | 333 | except: |
@@ -318,10 +336,14 @@ def main(): | @@ -318,10 +336,14 @@ def main(): | ||
318 | logging.info('===============================================') | 336 | logging.info('===============================================') |
319 | 337 | ||
320 | # --- start application | 338 | # --- start application |
321 | - filename = path.abspath(path.expanduser(arg.testfile[0])) | 339 | + config = { |
340 | + 'filename': arg.testfile[0] or '', | ||
341 | + 'debug': arg.debug, | ||
342 | + 'allow_all': arg.allow_all, | ||
343 | + } | ||
322 | 344 | ||
323 | try: | 345 | try: |
324 | - app = App(filename, vars(arg)) | 346 | + app = App(config) |
325 | except AppException: | 347 | except AppException: |
326 | logging.critical('Failed to start application.') | 348 | logging.critical('Failed to start application.') |
327 | sys.exit(1) | 349 | sys.exit(1) |
templates/admin.html
@@ -66,10 +66,10 @@ | @@ -66,10 +66,10 @@ | ||
66 | <table class="table table-sm" id="students_table"> | 66 | <table class="table table-sm" id="students_table"> |
67 | <thead class="thead thead-light"> | 67 | <thead class="thead thead-light"> |
68 | <tr> | 68 | <tr> |
69 | - <th class="col-md-2">Número</th> | ||
70 | - <th class="col-md-6">Nome</th> | ||
71 | - <th class="col-md-2">Estado</th> | ||
72 | - <th class="col-md-2">Nota</th> | 69 | + <th>Número</th> |
70 | + <th>Nome</th> | ||
71 | + <th>Estado</th> | ||
72 | + <th>Nota</th> | ||
73 | </tr> | 73 | </tr> |
74 | </thead> | 74 | </thead> |
75 | </table> | 75 | </table> |
test.py
@@ -9,7 +9,7 @@ import logging | @@ -9,7 +9,7 @@ import logging | ||
9 | 9 | ||
10 | # project | 10 | # project |
11 | import questions | 11 | import questions |
12 | -from tools import load_yaml | 12 | +# from tools import load_yaml |
13 | 13 | ||
14 | # Logger configuration | 14 | # Logger configuration |
15 | logger = logging.getLogger(__name__) | 15 | logger = logging.getLogger(__name__) |
@@ -29,17 +29,11 @@ class TestFactory(dict): | @@ -29,17 +29,11 @@ class TestFactory(dict): | ||
29 | # some configurations using the conf argument. | 29 | # some configurations using the conf argument. |
30 | # base questions are loaded from files into a pool. | 30 | # base questions are loaded from files into a pool. |
31 | # ----------------------------------------------------------------------- | 31 | # ----------------------------------------------------------------------- |
32 | - def __init__(self, filename=None, conf={}): | ||
33 | - super().__init__({}) | 32 | + def __init__(self, conf): |
33 | + super().__init__(conf) | ||
34 | 34 | ||
35 | - if filename is not None: | ||
36 | - self.update(load_yaml(filename)) | ||
37 | - self['filename'] = filename | ||
38 | - else: | ||
39 | - self['filename'] = '' | ||
40 | - | ||
41 | - self.update(conf) # overrides configuration | ||
42 | - self.sanity_checks() # defaults and sanity checks | 35 | + # set defaults and sanity checks |
36 | + self.sanity_checks() | ||
43 | 37 | ||
44 | # loads question_factory | 38 | # loads question_factory |
45 | self.question_factory = questions.QuestionFactory() | 39 | self.question_factory = questions.QuestionFactory() |
@@ -66,7 +60,7 @@ class TestFactory(dict): | @@ -66,7 +60,7 @@ class TestFactory(dict): | ||
66 | 60 | ||
67 | # --- database | 61 | # --- database |
68 | if 'database' not in self: | 62 | if 'database' not in self: |
69 | - logger.critical('Missing "database" key in configuration.') | 63 | + logger.critical('Missing "database" in configuration.') |
70 | raise TestFactoryException() | 64 | raise TestFactoryException() |
71 | elif not path.isfile(path.expanduser(self['database'])): | 65 | elif not path.isfile(path.expanduser(self['database'])): |
72 | logger.critical(f'Can\'t find database {self["database"]}.') | 66 | logger.critical(f'Can\'t find database {self["database"]}.') |
@@ -74,7 +68,7 @@ class TestFactory(dict): | @@ -74,7 +68,7 @@ class TestFactory(dict): | ||
74 | 68 | ||
75 | # --- answers_dir | 69 | # --- answers_dir |
76 | if 'answers_dir' not in self: | 70 | if 'answers_dir' not in self: |
77 | - logger.warning('Missing "answers_dir".') | 71 | + logger.critical('Missing "answers_dir".') |
78 | raise TestFactoryException() | 72 | raise TestFactoryException() |
79 | try: # check if answers_dir is a writable directory | 73 | try: # check if answers_dir is a writable directory |
80 | f = open(path.join(path.expanduser(self['answers_dir']),'REMOVE-ME'), 'w') | 74 | f = open(path.join(path.expanduser(self['answers_dir']),'REMOVE-ME'), 'w') |
@@ -127,7 +121,7 @@ class TestFactory(dict): | @@ -127,7 +121,7 @@ class TestFactory(dict): | ||
127 | 121 | ||
128 | # --- defaults for optional keys | 122 | # --- defaults for optional keys |
129 | self.setdefault('title', '') | 123 | self.setdefault('title', '') |
130 | - self.setdefault('show_hints', False) | 124 | + self.setdefault('show_hints', False) # FIXME not implemented yet |
131 | self.setdefault('show_points', False) | 125 | self.setdefault('show_points', False) |
132 | self.setdefault('scale_points', True) | 126 | self.setdefault('scale_points', True) |
133 | self.setdefault('scale_max', 20.0) | 127 | self.setdefault('scale_max', 20.0) |
@@ -178,7 +172,6 @@ class TestFactory(dict): | @@ -178,7 +172,6 @@ class TestFactory(dict): | ||
178 | 'student': student, # student id | 172 | 'student': student, # student id |
179 | 'questions': test, # list of questions | 173 | 'questions': test, # list of questions |
180 | 'answers_dir': self['answers_dir'], | 174 | 'answers_dir': self['answers_dir'], |
181 | - # 'total_points': total_points, | ||
182 | 175 | ||
183 | # FIXME which ones are required? | 176 | # FIXME which ones are required? |
184 | 'show_hints': self['show_hints'], | 177 | 'show_hints': self['show_hints'], |
@@ -191,8 +184,8 @@ class TestFactory(dict): | @@ -191,8 +184,8 @@ class TestFactory(dict): | ||
191 | }) | 184 | }) |
192 | 185 | ||
193 | # ----------------------------------------------------------------------- | 186 | # ----------------------------------------------------------------------- |
194 | - def __repr__(self): | ||
195 | - return '{\n' + '\n'.join(' {0:14s}: {1}'.format(k, v) for k,v in self.items()) + '\n}' | 187 | + # def __repr__(self): |
188 | + # return '{\n' + '\n'.join(' {0:14s}: {1}'.format(k, v) for k,v in self.items()) + '\n}' | ||
196 | 189 | ||
197 | 190 | ||
198 | # =========================================================================== | 191 | # =========================================================================== |