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 | # =========================================================================== |