Commit 704d1d69b38b507dab68a6db6f9b28b02491a77c

Authored by Miguel Barão
1 parent 4feaaba5
Exists in master and in 1 other branch dev

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