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'] | ... | ... |