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".
1 1
2 # BUGS 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 - usar thread.Lock para aceder a variaveis de estado. 5 - usar thread.Lock para aceder a variaveis de estado.
8 - permitir adicionar imagens nas perguntas. 6 - permitir adicionar imagens nas perguntas.
9 -- argumentos da linha de comando a funcionar.  
10 7
11 # TODO 8 # TODO
12 9
13 - implementar practice mode. 10 - implementar practice mode.
14 -- botões allow all/deny all.  
15 - enviar logs para web? 11 - enviar logs para web?
16 - SQLAlchemy em vez da classe database. 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 - aviso na pagina principal para quem usa browser da treta 14 - aviso na pagina principal para quem usa browser da treta
18 - permitir varios testes, aluno escolhe qual o teste que quer fazer. 15 - permitir varios testes, aluno escolhe qual o teste que quer fazer.
19 - criar perguntas de outros tipos, e.g. associação, ordenação, varios textinput 16 - criar perguntas de outros tipos, e.g. associação, ordenação, varios textinput
20 - perguntas para professor corrigir mais tarde. 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 - visualizar um teste ja realizado na página de administração 18 - visualizar um teste ja realizado na página de administração
23 -- Menu para professor com link para /results e /students  
24 - fazer uma calculadora javascript e por no menu. surge como modal 19 - fazer uma calculadora javascript e por no menu. surge como modal
25 - GeoIP? 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 # FIXED 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 - 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. 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 - pagina de login nao esta a apresentar bem. parece que precisa de autorizacao para aceder a /static... 30 - pagina de login nao esta a apresentar bem. parece que precisa de autorizacao para aceder a /static...
32 - Não mostrar Professor nos activos em /admin 31 - Não mostrar Professor nos activos em /admin
@@ -3,12 +3,14 @@ @@ -3,12 +3,14 @@
3 import logging 3 import logging
4 from os import path 4 from os import path
5 import sqlite3 5 import sqlite3
6 -  
7 import bcrypt 6 import bcrypt
8 7
9 import test 8 import test
10 import database 9 import database
11 10
  11 +logger = logging.getLogger(__name__)
  12 +
  13 +
12 # ============================================================================ 14 # ============================================================================
13 # Application 15 # Application
14 # ============================================================================ 16 # ============================================================================
@@ -23,7 +25,7 @@ class App(object): @@ -23,7 +25,7 @@ class App(object):
23 # } 25 # }
24 logger.info('============= Running perguntations =============') 26 logger.info('============= Running perguntations =============')
25 self.online = dict() # {uid: {'student':{}}} 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 self.testfactory = test.TestFactory(filename, conf=conf) 29 self.testfactory = test.TestFactory(filename, conf=conf)
28 self.db = database.Database(self.testfactory['database']) # FIXME 30 self.db = database.Database(self.testfactory['database']) # FIXME
29 try: 31 try:
@@ -186,12 +188,3 @@ class App(object): @@ -186,12 +188,3 @@ class App(object):
186 188
187 def set_user_ip(self, uid, ipaddress=''): 189 def set_user_ip(self, uid, ipaddress=''):
188 self.online[uid]['student']['ip_address'] = ipaddress 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,11 +19,3 @@ server.socket_port = 8080
19 19
20 ; not required for snakeoil: 20 ; not required for snakeoil:
21 ; server.ssl_certificate_chain = 'ca_certs.crt' 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 -  
@@ -35,18 +35,6 @@ import sys @@ -35,18 +35,6 @@ import sys
35 35
36 # setup logger for this module 36 # setup logger for this module
37 logger = logging.getLogger(__name__) 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 try: 39 try:
52 import yaml 40 import yaml
@@ -168,8 +156,10 @@ class QuestionFactory(dict): @@ -168,8 +156,10 @@ class QuestionFactory(dict):
168 'text' : QuestionText, 156 'text' : QuestionText,
169 'text_regex': QuestionTextRegex, 157 'text_regex': QuestionTextRegex,
170 'textarea' : QuestionTextArea, 158 'textarea' : QuestionTextArea,
  159 + # informative panels
171 'information': QuestionInformation, 160 'information': QuestionInformation,
172 'warning' : QuestionInformation, 161 'warning' : QuestionInformation,
  162 + 'alert' : QuestionInformation,
173 } 163 }
174 164
175 # Shallow copy so that script generated questions will not replace 165 # Shallow copy so that script generated questions will not replace
@@ -5,9 +5,10 @@ @@ -5,9 +5,10 @@
5 from os import path 5 from os import path
6 import sys 6 import sys
7 import argparse 7 import argparse
8 -# import logging 8 +import logging.config
9 import html 9 import html
10 import json 10 import json
  11 +import yaml
11 12
12 try: 13 try:
13 import cherrypy 14 import cherrypy
@@ -17,8 +18,6 @@ except ImportError: @@ -17,8 +18,6 @@ except ImportError:
17 print('Some python packages are missing. See README.md for instructions.') 18 print('Some python packages are missing. See README.md for instructions.')
18 sys.exit(1) 19 sys.exit(1)
19 20
20 -# my code  
21 -from app import App  
22 21
23 # ============================================================================ 22 # ============================================================================
24 # Authentication 23 # Authentication
@@ -219,33 +218,32 @@ def parse_arguments(): @@ -219,33 +218,32 @@ def parse_arguments():
219 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.') 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 serverconf_file = path.normpath(path.join(SERVER_PATH, 'config', 'server.conf')) 219 serverconf_file = path.normpath(path.join(SERVER_PATH, 'config', 'server.conf'))
221 argparser.add_argument('--conf', default=serverconf_file, type=str, help='server configuration file') 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 argparser.add_argument('testfile', type=str, nargs='+', help='test/exam in YAML format.') # FIXME only one exam supported at the moment 223 argparser.add_argument('testfile', type=str, nargs='+', help='test/exam in YAML format.') # FIXME only one exam supported at the moment
235 return argparser.parse_args() 224 return argparser.parse_args()
236 225
237 # ============================================================================ 226 # ============================================================================
238 if __name__ == '__main__': 227 if __name__ == '__main__':
239 228
240 -  
241 - # --- path where this file is located  
242 SERVER_PATH = path.dirname(path.realpath(__file__)) 229 SERVER_PATH = path.dirname(path.realpath(__file__))
243 TEMPLATES_DIR = path.join(SERVER_PATH, 'templates') 230 TEMPLATES_DIR = path.join(SERVER_PATH, 'templates')
  231 + LOGGER_CONF = path.join(SERVER_PATH, 'config/logger.yaml')
244 SESSION_KEY = 'userid' 232 SESSION_KEY = 'userid'
245 233
246 # --- parse command line arguments and build base test 234 # --- parse command line arguments and build base test
247 arg = parse_arguments() 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 # FIXME do not send args that were not defined in the commandline 247 # FIXME do not send args that were not defined in the commandline
250 # this means options should be like --show-ref=true|false 248 # this means options should be like --show-ref=true|false
251 # and have no default value 249 # and have no default value
@@ -292,14 +290,11 @@ if __name__ == '__main__': @@ -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 cherrypy.tree.mount(webapp, script_name='/', config=conf) 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 if hasattr(cherrypy.engine, "signal_handler"): 298 if hasattr(cherrypy.engine, "signal_handler"):
304 cherrypy.engine.signal_handler.subscribe() 299 cherrypy.engine.signal_handler.subscribe()
305 if hasattr(cherrypy.engine, "console_control_handler"): 300 if hasattr(cherrypy.engine, "console_control_handler"):
@@ -309,4 +304,3 @@ if __name__ == '__main__': @@ -309,4 +304,3 @@ if __name__ == '__main__':
309 cherrypy.engine.block() 304 cherrypy.engine.block()
310 # ...App running... 305 # ...App running...
311 app.exit() 306 app.exit()
312 - cherrypy.log('Terminated OK ------------------------', 'APPLICATION')  
templates/test.html
@@ -124,19 +124,27 @@ @@ -124,19 +124,27 @@
124 % for i,q in enumerate(t['questions']): 124 % for i,q in enumerate(t['questions']):
125 <div class="ui-corner-all custom-corners"> 125 <div class="ui-corner-all custom-corners">
126 % if q['type'] == 'information': 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 <h4> 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 </h4> 130 </h4>
132 <p> 131 <p>
133 ${pretty(q['text'])} 132 ${pretty(q['text'])}
134 </p> 133 </p>
135 </div> 134 </div>
136 % elif q['type'] == 'warning': 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 <div class="alert alert-danger drop-shadow" role="alert"> 145 <div class="alert alert-danger drop-shadow" role="alert">
138 <h4> 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 </h4> 148 </h4>
141 <p> 149 <p>
142 ${pretty(q['text'])} 150 ${pretty(q['text'])}
@@ -255,9 +263,9 @@ @@ -255,9 +263,9 @@
255 <div class="panel-footer"> 263 <div class="panel-footer">
256 264
257 % if t['debug']: 265 % if t['debug']:
258 - <pre> 266 + <pre><code>
259 ${yaml.dump(q)} 267 ${yaml.dump(q)}
260 - </pre> 268 + </code></pre>
261 % endif 269 % endif
262 270
263 % if t['show_ref']: 271 % if t['show_ref']:
@@ -8,12 +8,6 @@ import logging @@ -8,12 +8,6 @@ import logging
8 8
9 # Logger configuration 9 # Logger configuration
10 logger = logging.getLogger(__name__) 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 try: 12 try:
19 import yaml 13 import yaml
@@ -188,7 +182,7 @@ class TestFactory(dict): @@ -188,7 +182,7 @@ class TestFactory(dict):
188 q = self.question_factory.generate(random.choice(qq['ref'])) 182 q = self.question_factory.generate(random.choice(qq['ref']))
189 183
190 # some defaults 184 # some defaults
191 - if q['type'] in ('information', 'warning'): 185 + if q['type'] in ('information', 'warning', 'alert'):
192 q['points'] = qq.get('points', 0.0) 186 q['points'] = qq.get('points', 0.0)
193 else: 187 else:
194 q['title'] = '{}. '.format(n) + q['title'] 188 q['title'] = '{}. '.format(n) + q['title']