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 | - hints nao funciona |
| 7 | 7 | - uniformizar question.py com a de aprendizations... |
| 8 | 8 | - permitir eliminar teste a decorrer |
| 9 | +- eventos unfocus? | |
| 9 | 10 | - servidor nao esta a lidar com eventos scroll/resize. ignorar? |
| 10 | 11 | |
| 11 | 12 | # TODO |
| ... | ... | @@ -18,7 +19,7 @@ |
| 18 | 19 | node_modules/mathjax-node-cli/bin/tex2svg '\sqrt{x}' |
| 19 | 20 | usar isto para gerar svg que passa a fazer parte do texto da pergunta (markdown suporta tags svg?) |
| 20 | 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 | 24 | - Gerar pdf's com todos os testes no final (pdfkit). |
| 24 | 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 | 12 | # this project |
| 13 | 13 | from models import Student, Test, Question |
| 14 | 14 | import test |
| 15 | +from tools import load_yaml | |
| 15 | 16 | |
| 16 | 17 | logger = logging.getLogger(__name__) |
| 17 | 18 | |
| ... | ... | @@ -23,7 +24,7 @@ class AppException(Exception): |
| 23 | 24 | # Application |
| 24 | 25 | # ============================================================================ |
| 25 | 26 | class App(object): |
| 26 | - def __init__(self, filename, conf): | |
| 27 | + def __init__(self, conf={}): | |
| 27 | 28 | # online = { |
| 28 | 29 | # uid1: { |
| 29 | 30 | # 'student': {'number': 123, 'name': john, ...}, |
| ... | ... | @@ -35,8 +36,17 @@ class App(object): |
| 35 | 36 | self.online = dict() # {uid: {'student':{}}} |
| 36 | 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 | 48 | try: |
| 39 | - self.testfactory = test.TestFactory(filename, conf=conf) | |
| 49 | + self.testfactory = test.TestFactory(testconf) | |
| 40 | 50 | except test.TestFactoryException: |
| 41 | 51 | logger.critical('Can\'t create test factory.') |
| 42 | 52 | raise AppException() | ... | ... |
serve.py
| ... | ... | @@ -8,6 +8,8 @@ import logging.config |
| 8 | 8 | import json |
| 9 | 9 | import base64 |
| 10 | 10 | import uuid |
| 11 | +# from mimetypes import guess_type | |
| 12 | + | |
| 11 | 13 | |
| 12 | 14 | # packages |
| 13 | 15 | import tornado.ioloop |
| ... | ... | @@ -28,7 +30,7 @@ class WebApplication(tornado.web.Application): |
| 28 | 30 | (r'/test', TestHandler), |
| 29 | 31 | (r'/review', ReviewHandler), |
| 30 | 32 | (r'/admin', AdminHandler), |
| 31 | - (r'/static/(.+)', FileHandler), # FIXME | |
| 33 | + (r'/file/(.+)', FileHandler), # FIXME | |
| 32 | 34 | (r'/', RootHandler), # TODO multiple tests |
| 33 | 35 | ] |
| 34 | 36 | |
| ... | ... | @@ -93,17 +95,33 @@ class LogoutHandler(BaseHandler): |
| 93 | 95 | # FIXME checkit |
| 94 | 96 | class FileHandler(BaseHandler): |
| 95 | 97 | @tornado.web.authenticated |
| 96 | - def get(self, filename): | |
| 98 | + def get(self): | |
| 97 | 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 | 327 | logger_file = 'logger-debug.yaml' if arg.debug else 'logger.yaml' |
| 310 | 328 | SERVER_PATH = path.dirname(path.realpath(__file__)) |
| 311 | 329 | LOGGER_CONF = path.join(SERVER_PATH, f'config/{logger_file}') |
| 312 | - | |
| 330 | + | |
| 313 | 331 | try: |
| 314 | 332 | logging.config.dictConfig(load_yaml(LOGGER_CONF)) |
| 315 | 333 | except: |
| ... | ... | @@ -318,10 +336,14 @@ def main(): |
| 318 | 336 | logging.info('===============================================') |
| 319 | 337 | |
| 320 | 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 | 345 | try: |
| 324 | - app = App(filename, vars(arg)) | |
| 346 | + app = App(config) | |
| 325 | 347 | except AppException: |
| 326 | 348 | logging.critical('Failed to start application.') |
| 327 | 349 | sys.exit(1) | ... | ... |
templates/admin.html
| ... | ... | @@ -66,10 +66,10 @@ |
| 66 | 66 | <table class="table table-sm" id="students_table"> |
| 67 | 67 | <thead class="thead thead-light"> |
| 68 | 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 | 73 | </tr> |
| 74 | 74 | </thead> |
| 75 | 75 | </table> | ... | ... |
test.py
| ... | ... | @@ -9,7 +9,7 @@ import logging |
| 9 | 9 | |
| 10 | 10 | # project |
| 11 | 11 | import questions |
| 12 | -from tools import load_yaml | |
| 12 | +# from tools import load_yaml | |
| 13 | 13 | |
| 14 | 14 | # Logger configuration |
| 15 | 15 | logger = logging.getLogger(__name__) |
| ... | ... | @@ -29,17 +29,11 @@ class TestFactory(dict): |
| 29 | 29 | # some configurations using the conf argument. |
| 30 | 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 | 38 | # loads question_factory |
| 45 | 39 | self.question_factory = questions.QuestionFactory() |
| ... | ... | @@ -66,7 +60,7 @@ class TestFactory(dict): |
| 66 | 60 | |
| 67 | 61 | # --- database |
| 68 | 62 | if 'database' not in self: |
| 69 | - logger.critical('Missing "database" key in configuration.') | |
| 63 | + logger.critical('Missing "database" in configuration.') | |
| 70 | 64 | raise TestFactoryException() |
| 71 | 65 | elif not path.isfile(path.expanduser(self['database'])): |
| 72 | 66 | logger.critical(f'Can\'t find database {self["database"]}.') |
| ... | ... | @@ -74,7 +68,7 @@ class TestFactory(dict): |
| 74 | 68 | |
| 75 | 69 | # --- answers_dir |
| 76 | 70 | if 'answers_dir' not in self: |
| 77 | - logger.warning('Missing "answers_dir".') | |
| 71 | + logger.critical('Missing "answers_dir".') | |
| 78 | 72 | raise TestFactoryException() |
| 79 | 73 | try: # check if answers_dir is a writable directory |
| 80 | 74 | f = open(path.join(path.expanduser(self['answers_dir']),'REMOVE-ME'), 'w') |
| ... | ... | @@ -127,7 +121,7 @@ class TestFactory(dict): |
| 127 | 121 | |
| 128 | 122 | # --- defaults for optional keys |
| 129 | 123 | self.setdefault('title', '') |
| 130 | - self.setdefault('show_hints', False) | |
| 124 | + self.setdefault('show_hints', False) # FIXME not implemented yet | |
| 131 | 125 | self.setdefault('show_points', False) |
| 132 | 126 | self.setdefault('scale_points', True) |
| 133 | 127 | self.setdefault('scale_max', 20.0) |
| ... | ... | @@ -178,7 +172,6 @@ class TestFactory(dict): |
| 178 | 172 | 'student': student, # student id |
| 179 | 173 | 'questions': test, # list of questions |
| 180 | 174 | 'answers_dir': self['answers_dir'], |
| 181 | - # 'total_points': total_points, | |
| 182 | 175 | |
| 183 | 176 | # FIXME which ones are required? |
| 184 | 177 | 'show_hints': self['show_hints'], |
| ... | ... | @@ -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 | # =========================================================================== | ... | ... |