Commit 704d1d69b38b507dab68a6db6f9b28b02491a77c
1 parent
4feaaba5
Exists in
master
and in
1 other branch
- all logging configurations in a config/logger.yaml file.
- new 'alert' type of question. - commandline 'debug' argument partially working (still lacks loglevel setup). - information type questions use 'title' instead of hardcoded "Atenção".
Showing
7 changed files
with
45 additions
and
75 deletions
Show diff stats
BUGS.md
| 1 | 1 | |
| 2 | 2 | # BUGS |
| 3 | 3 | |
| 4 | -- alunos online têm acesso a /correct e servidor rebenta. (não é fácil impedir...) | |
| 5 | -- configuracao dos logs cherrypy para se darem bem com os outros | |
| 6 | -- browser e ip usados gravado no test. | |
| 4 | +- argumentos da linha de comando a funcionar. | |
| 7 | 5 | - usar thread.Lock para aceder a variaveis de estado. |
| 8 | 6 | - permitir adicionar imagens nas perguntas. |
| 9 | -- argumentos da linha de comando a funcionar. | |
| 10 | 7 | |
| 11 | 8 | # TODO |
| 12 | 9 | |
| 13 | 10 | - implementar practice mode. |
| 14 | -- botões allow all/deny all. | |
| 15 | 11 | - enviar logs para web? |
| 16 | 12 | - SQLAlchemy em vez da classe database. |
| 13 | +- single page web no teste/correcçao. Página construída em javascript, obter perguntas com ajax (para practice?). | |
| 17 | 14 | - aviso na pagina principal para quem usa browser da treta |
| 18 | 15 | - permitir varios testes, aluno escolhe qual o teste que quer fazer. |
| 19 | 16 | - criar perguntas de outros tipos, e.g. associação, ordenação, varios textinput |
| 20 | 17 | - perguntas para professor corrigir mais tarde. |
| 21 | -- single page web no teste/correcçao. Página construída em javascript, obter perguntas com ajax (para practice?). | |
| 22 | 18 | - visualizar um teste ja realizado na página de administração |
| 23 | -- Menu para professor com link para /results e /students | |
| 24 | 19 | - fazer uma calculadora javascript e por no menu. surge como modal |
| 25 | 20 | - GeoIP? |
| 26 | -- mostrar botão de reset apenas no final da pagina, com edit para escrever o número. | |
| 21 | +- alunos online têm acesso a /correct e servidor rebenta. (não é fácil impedir...) | |
| 27 | 22 | |
| 28 | 23 | # FIXED |
| 29 | 24 | |
| 25 | +- configuracao dos logs cherrypy para se darem bem com os outros | |
| 26 | +- browser e ip usados gravado no test. | |
| 27 | +- botões allow all/deny all. | |
| 28 | +- mostrar botão de reset apenas no final da pagina, com edit para escrever o número. | |
| 30 | 29 | - aluno faz login, mas fecha browser, ficando no estado (online,deny). Ao tentar login com outro browser está deny e o prof não consegue pô-lo em allow pois já não está na lista. => solucao é manter todos os alunos numa tabela. |
| 31 | 30 | - pagina de login nao esta a apresentar bem. parece que precisa de autorizacao para aceder a /static... |
| 32 | 31 | - Não mostrar Professor nos activos em /admin | ... | ... |
app.py
| ... | ... | @@ -3,12 +3,14 @@ |
| 3 | 3 | import logging |
| 4 | 4 | from os import path |
| 5 | 5 | import sqlite3 |
| 6 | - | |
| 7 | 6 | import bcrypt |
| 8 | 7 | |
| 9 | 8 | import test |
| 10 | 9 | import database |
| 11 | 10 | |
| 11 | +logger = logging.getLogger(__name__) | |
| 12 | + | |
| 13 | + | |
| 12 | 14 | # ============================================================================ |
| 13 | 15 | # Application |
| 14 | 16 | # ============================================================================ |
| ... | ... | @@ -23,7 +25,7 @@ class App(object): |
| 23 | 25 | # } |
| 24 | 26 | logger.info('============= Running perguntations =============') |
| 25 | 27 | self.online = dict() # {uid: {'student':{}}} |
| 26 | - self.allowed = set([]) # '0' is hardcoded to allowed elsewhere FIXME | |
| 28 | + self.allowed = set([]) # '0' is hardcoded to allowed elsewhere | |
| 27 | 29 | self.testfactory = test.TestFactory(filename, conf=conf) |
| 28 | 30 | self.db = database.Database(self.testfactory['database']) # FIXME |
| 29 | 31 | try: |
| ... | ... | @@ -186,12 +188,3 @@ class App(object): |
| 186 | 188 | |
| 187 | 189 | def set_user_ip(self, uid, ipaddress=''): |
| 188 | 190 | self.online[uid]['student']['ip_address'] = ipaddress |
| 189 | - | |
| 190 | -# ============================================================================ | |
| 191 | -ch = logging.StreamHandler() | |
| 192 | -ch.setLevel(logging.INFO) | |
| 193 | -ch.setFormatter(logging.Formatter('%(asctime)s | %(name)-10s | %(levelname)-8s | %(message)s')) | |
| 194 | - | |
| 195 | -logger = logging.getLogger(__name__) | |
| 196 | -logger.setLevel(logging.INFO) | |
| 197 | -logger.addHandler(ch) | ... | ... |
config/server.conf
| ... | ... | @@ -19,11 +19,3 @@ server.socket_port = 8080 |
| 19 | 19 | |
| 20 | 20 | ; not required for snakeoil: |
| 21 | 21 | ; server.ssl_certificate_chain = 'ca_certs.crt' |
| 22 | - | |
| 23 | -# add path to the log files here. empty strings disable logging | |
| 24 | -; log.error_file = 'logs/errors.log' | |
| 25 | -; log.access_file = 'logs/access.log' | |
| 26 | -log.error_file = '' | |
| 27 | -log.access_file = '' | |
| 28 | -log.screen = False | |
| 29 | - | ... | ... |
questions.py
| ... | ... | @@ -35,18 +35,6 @@ import sys |
| 35 | 35 | |
| 36 | 36 | # setup logger for this module |
| 37 | 37 | logger = logging.getLogger(__name__) |
| 38 | -logger.setLevel(logging.INFO) | |
| 39 | - | |
| 40 | -# fh = logging.FileHandler('question.log') | |
| 41 | -ch = logging.StreamHandler() | |
| 42 | -ch.setLevel(logging.INFO) | |
| 43 | - | |
| 44 | -formatter = logging.Formatter('%(asctime)s | %(name)-10s | %(levelname)-8s | %(message)s') | |
| 45 | -# fh.setFormatter(formatter) | |
| 46 | -ch.setFormatter(formatter) | |
| 47 | - | |
| 48 | -# logger.addHandler(fh) | |
| 49 | -logger.addHandler(ch) | |
| 50 | 38 | |
| 51 | 39 | try: |
| 52 | 40 | import yaml |
| ... | ... | @@ -168,8 +156,10 @@ class QuestionFactory(dict): |
| 168 | 156 | 'text' : QuestionText, |
| 169 | 157 | 'text_regex': QuestionTextRegex, |
| 170 | 158 | 'textarea' : QuestionTextArea, |
| 159 | + # informative panels | |
| 171 | 160 | 'information': QuestionInformation, |
| 172 | 161 | 'warning' : QuestionInformation, |
| 162 | + 'alert' : QuestionInformation, | |
| 173 | 163 | } |
| 174 | 164 | |
| 175 | 165 | # Shallow copy so that script generated questions will not replace | ... | ... |
serve.py
| ... | ... | @@ -5,9 +5,10 @@ |
| 5 | 5 | from os import path |
| 6 | 6 | import sys |
| 7 | 7 | import argparse |
| 8 | -# import logging | |
| 8 | +import logging.config | |
| 9 | 9 | import html |
| 10 | 10 | import json |
| 11 | +import yaml | |
| 11 | 12 | |
| 12 | 13 | try: |
| 13 | 14 | import cherrypy |
| ... | ... | @@ -17,8 +18,6 @@ except ImportError: |
| 17 | 18 | print('Some python packages are missing. See README.md for instructions.') |
| 18 | 19 | sys.exit(1) |
| 19 | 20 | |
| 20 | -# my code | |
| 21 | -from app import App | |
| 22 | 21 | |
| 23 | 22 | # ============================================================================ |
| 24 | 23 | # Authentication |
| ... | ... | @@ -219,33 +218,32 @@ def parse_arguments(): |
| 219 | 218 | argparser = argparse.ArgumentParser(description='Server for online tests. Enrolled students and tests have to be previously configured. Please read the documentation included with this software before running the server.') |
| 220 | 219 | serverconf_file = path.normpath(path.join(SERVER_PATH, 'config', 'server.conf')) |
| 221 | 220 | argparser.add_argument('--conf', default=serverconf_file, type=str, help='server configuration file') |
| 222 | - # argparser.add_argument('--debug', action='store_true', | |
| 223 | - # help='Show datastructures when rendering questions') | |
| 224 | - # argparser.add_argument('--show_ref', action='store_true', | |
| 225 | - # help='Show filename and ref field for each question') | |
| 226 | - # argparser.add_argument('--show_points', action='store_true', | |
| 227 | - # help='Show normalized points for each question') | |
| 228 | - # argparser.add_argument('--show_hints', action='store_true', | |
| 229 | - # help='Show hints in questions, if available') | |
| 230 | - # argparser.add_argument('--save_answers', action='store_true', | |
| 231 | - # help='Saves answers in JSON format') | |
| 232 | - # argparser.add_argument('--practice', action='store_true', | |
| 233 | - # help='Show correction results and allow repetitive resubmission of the test') | |
| 221 | + argparser.add_argument('--debug', action='store_true', | |
| 222 | + help='Show datastructures when rendering questions') | |
| 234 | 223 | argparser.add_argument('testfile', type=str, nargs='+', help='test/exam in YAML format.') # FIXME only one exam supported at the moment |
| 235 | 224 | return argparser.parse_args() |
| 236 | 225 | |
| 237 | 226 | # ============================================================================ |
| 238 | 227 | if __name__ == '__main__': |
| 239 | 228 | |
| 240 | - | |
| 241 | - # --- path where this file is located | |
| 242 | 229 | SERVER_PATH = path.dirname(path.realpath(__file__)) |
| 243 | 230 | TEMPLATES_DIR = path.join(SERVER_PATH, 'templates') |
| 231 | + LOGGER_CONF = path.join(SERVER_PATH, 'config/logger.yaml') | |
| 244 | 232 | SESSION_KEY = 'userid' |
| 245 | 233 | |
| 246 | 234 | # --- parse command line arguments and build base test |
| 247 | 235 | arg = parse_arguments() |
| 248 | 236 | |
| 237 | + if arg.debug: | |
| 238 | + LOGGER_CONF = path.join(SERVER_PATH, 'config/logger-debug.yaml') | |
| 239 | + | |
| 240 | + # --- Setup logging | |
| 241 | + with open(LOGGER_CONF,'r') as f: | |
| 242 | + logging.config.dictConfig(yaml.load(f)) | |
| 243 | + | |
| 244 | + # --- start application | |
| 245 | + from app import App | |
| 246 | + | |
| 249 | 247 | # FIXME do not send args that were not defined in the commandline |
| 250 | 248 | # this means options should be like --show-ref=true|false |
| 251 | 249 | # and have no default value |
| ... | ... | @@ -292,14 +290,11 @@ if __name__ == '__main__': |
| 292 | 290 | }, |
| 293 | 291 | } |
| 294 | 292 | |
| 295 | - # --- app specific configuration | |
| 293 | + cherrypy.engine.unsubscribe('graceful', cherrypy.log.reopen_files) # FIXME what's this? | |
| 296 | 294 | |
| 295 | + # --- Start server | |
| 297 | 296 | cherrypy.tree.mount(webapp, script_name='/', config=conf) |
| 298 | 297 | |
| 299 | - # logger.info('Webserver listening at {}:{}'.format( | |
| 300 | - # cherrypy.config['server.socket_host'], | |
| 301 | - # cherrypy.config['server.socket_port'])) | |
| 302 | - | |
| 303 | 298 | if hasattr(cherrypy.engine, "signal_handler"): |
| 304 | 299 | cherrypy.engine.signal_handler.subscribe() |
| 305 | 300 | if hasattr(cherrypy.engine, "console_control_handler"): |
| ... | ... | @@ -309,4 +304,3 @@ if __name__ == '__main__': |
| 309 | 304 | cherrypy.engine.block() |
| 310 | 305 | # ...App running... |
| 311 | 306 | app.exit() |
| 312 | - cherrypy.log('Terminated OK ------------------------', 'APPLICATION') | ... | ... |
templates/test.html
| ... | ... | @@ -124,19 +124,27 @@ |
| 124 | 124 | % for i,q in enumerate(t['questions']): |
| 125 | 125 | <div class="ui-corner-all custom-corners"> |
| 126 | 126 | % if q['type'] == 'information': |
| 127 | - <div class="alert alert-warning drop-shadow" role="alert"> | |
| 127 | + <div class="alert alert-info drop-shadow" role="alert"> | |
| 128 | 128 | <h4> |
| 129 | - <span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> | |
| 130 | - ${q['title']} | |
| 129 | + <span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> ${q['title']} | |
| 131 | 130 | </h4> |
| 132 | 131 | <p> |
| 133 | 132 | ${pretty(q['text'])} |
| 134 | 133 | </p> |
| 135 | 134 | </div> |
| 136 | 135 | % elif q['type'] == 'warning': |
| 136 | + <div class="alert alert-warning drop-shadow" role="alert"> | |
| 137 | + <h4> | |
| 138 | + <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> ${q['title']} | |
| 139 | + </h4> | |
| 140 | + <p> | |
| 141 | + ${pretty(q['text'])} | |
| 142 | + </p> | |
| 143 | + </div> | |
| 144 | + % elif q['type'] == 'alert': | |
| 137 | 145 | <div class="alert alert-danger drop-shadow" role="alert"> |
| 138 | 146 | <h4> |
| 139 | - <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> Atenção | |
| 147 | + <span class="glyphicon glyphicon-alert" aria-hidden="true"></span> ${q['title']} | |
| 140 | 148 | </h4> |
| 141 | 149 | <p> |
| 142 | 150 | ${pretty(q['text'])} |
| ... | ... | @@ -255,9 +263,9 @@ |
| 255 | 263 | <div class="panel-footer"> |
| 256 | 264 | |
| 257 | 265 | % if t['debug']: |
| 258 | - <pre> | |
| 266 | + <pre><code> | |
| 259 | 267 | ${yaml.dump(q)} |
| 260 | - </pre> | |
| 268 | + </code></pre> | |
| 261 | 269 | % endif |
| 262 | 270 | |
| 263 | 271 | % if t['show_ref']: | ... | ... |
test.py
| ... | ... | @@ -8,12 +8,6 @@ import logging |
| 8 | 8 | |
| 9 | 9 | # Logger configuration |
| 10 | 10 | logger = logging.getLogger(__name__) |
| 11 | -logger.setLevel(logging.INFO) | |
| 12 | - | |
| 13 | -ch = logging.StreamHandler() | |
| 14 | -ch.setLevel(logging.INFO) | |
| 15 | -ch.setFormatter(logging.Formatter('%(asctime)s | %(name)-10s | %(levelname)-8s | %(message)s')) | |
| 16 | -logger.addHandler(ch) | |
| 17 | 11 | |
| 18 | 12 | try: |
| 19 | 13 | import yaml |
| ... | ... | @@ -188,7 +182,7 @@ class TestFactory(dict): |
| 188 | 182 | q = self.question_factory.generate(random.choice(qq['ref'])) |
| 189 | 183 | |
| 190 | 184 | # some defaults |
| 191 | - if q['type'] in ('information', 'warning'): | |
| 185 | + if q['type'] in ('information', 'warning', 'alert'): | |
| 192 | 186 | q['points'] = qq.get('points', 0.0) |
| 193 | 187 | else: |
| 194 | 188 | q['title'] = '{}. '.format(n) + q['title'] | ... | ... |