Commit 870abe3cb5d32d58c0e6d1f1a46be2e748999075

Authored by Miguel Barão
1 parent 3c36fb56
Exists in master and in 1 other branch dev

- code to give up test when exiting. Also saves test.

1 1
2 # BUGS 2 # BUGS
3 3
4 -- abrir o teste numa janela maximizada e que nao permite que o aluno a redimensione/mova.  
5 -- detectar scroll e enviar posição para servidor (analise de scroll para detectar copianço? ou simplesmente para analisar como os alunos percorrem o teste)  
6 -- detectar se janela perde focus e alertar o prof (http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active)  
7 -  
8 - usar thread.Lock para aceder a variaveis de estado? 4 - usar thread.Lock para aceder a variaveis de estado?
9 - permitir adicionar imagens nas perguntas. 5 - permitir adicionar imagens nas perguntas.
10 - debug mode: log levels not working 6 - debug mode: log levels not working
11 -- Se aluno fizer logout, o teste não é gravado e ficamos sem registo do teste que o aluno viu.  
12 7
13 # TODO 8 # TODO
14 9
15 - implementar practice mode. 10 - implementar practice mode.
  11 +- abrir o teste numa janela maximizada e que nao permite que o aluno a redimensione/mova.
  12 +- detectar scroll e enviar posição para servidor (analise de scroll para detectar copianço? ou simplesmente para analisar como os alunos percorrem o teste)
  13 +- detectar se janela perde focus e alertar o prof (http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active)
16 - single page web no teste/correcçao. Página construída em javascript, obter perguntas com ajax (para practice?). 14 - 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 15 - aviso na pagina principal para quem usa browser da treta
18 - permitir varios testes, aluno escolhe qual o teste que quer fazer. 16 - permitir varios testes, aluno escolhe qual o teste que quer fazer.
@@ -26,6 +24,7 @@ @@ -26,6 +24,7 @@
26 24
27 # FIXED 25 # FIXED
28 26
  27 +- Se aluno fizer logout, o teste não é gravado e ficamos sem registo do teste que o aluno viu.
29 - criar sqlalchemy sessions dentro de app de modo a estarem associadas a requests. ver se é facil usar with db:(...) para criar e fechar sessão. 28 - criar sqlalchemy sessions dentro de app de modo a estarem associadas a requests. ver se é facil usar with db:(...) para criar e fechar sessão.
30 - sqlalchemy queixa-se de threads. 29 - sqlalchemy queixa-se de threads.
31 - SQLAlchemy em vez da classe database. 30 - SQLAlchemy em vez da classe database.
@@ -27,6 +27,7 @@ class App(object): @@ -27,6 +27,7 @@ class App(object):
27 # uid2: {...} 27 # uid2: {...}
28 # } 28 # }
29 logger.info('============= Running perguntations =============') 29 logger.info('============= Running perguntations =============')
  30 + self.lock = threading.Lock()
30 self.online = dict() # {uid: {'student':{}}} 31 self.online = dict() # {uid: {'student':{}}}
31 self.allowed = set([]) # '0' is hardcoded to allowed elsewhere 32 self.allowed = set([]) # '0' is hardcoded to allowed elsewhere
32 33
@@ -115,7 +116,9 @@ class App(object): @@ -115,7 +116,9 @@ class App(object):
115 if uid in self.online: 116 if uid in self.online:
116 logger.info('Student {}: generating new test.'.format(uid)) 117 logger.info('Student {}: generating new test.'.format(uid))
117 student_id = self.online[uid]['student'] 118 student_id = self.online[uid]['student']
  119 + self.lock.acquire() # FIXME is it needed?
118 self.online[uid]['test'] = self.testfactory.generate(student_id) 120 self.online[uid]['test'] = self.testfactory.generate(student_id)
  121 + self.lock.release()
119 return self.online[uid]['test'] 122 return self.online[uid]['test']
120 else: 123 else:
121 logger.error('Student {}: offline, can''t generate test'.format(uid)) 124 logger.error('Student {}: offline, can''t generate test'.format(uid))
@@ -152,6 +155,16 @@ class App(object): @@ -152,6 +155,16 @@ class App(object):
152 return grade 155 return grade
153 156
154 # ----------------------------------------------------------------------- 157 # -----------------------------------------------------------------------
  158 + def giveup_test(self, uid):
  159 + logger.info('Student {0}: gave up.'.format(uid))
  160 + t = self.online[uid]['test']
  161 + t.giveup()
  162 + if t['save_answers']:
  163 + fname = ' -- '.join((t['student']['number'], t['ref'], str(t['finish_time']))) + '.json'
  164 + fpath = path.abspath(path.join(t['answers_dir'], fname))
  165 + t.save_json(fpath)
  166 +
  167 + # -----------------------------------------------------------------------
155 168
156 # --- helpers (getters) 169 # --- helpers (getters)
157 def get_student_name(self, uid): 170 def get_student_name(self, uid):
@@ -152,7 +152,6 @@ class Root(object): @@ -152,7 +152,6 @@ class Root(object):
152 cherrypy.lib.sessions.expire() # session coockie expires client side 152 cherrypy.lib.sessions.expire() # session coockie expires client side
153 cherrypy.session[SESSION_KEY] = cherrypy.request.login = None 153 cherrypy.session[SESSION_KEY] = cherrypy.request.login = None
154 cherrypy.log.error('Student {0} logged out.'.format(uid), 'APPLICATION') 154 cherrypy.log.error('Student {0} logged out.'.format(uid), 'APPLICATION')
155 -  
156 self.app.logout(uid) 155 self.app.logout(uid)
157 raise cherrypy.HTTPRedirect('/') 156 raise cherrypy.HTTPRedirect('/')
158 157
@@ -164,10 +163,7 @@ class Root(object): @@ -164,10 +163,7 @@ class Root(object):
164 @require() 163 @require()
165 def test(self): 164 def test(self):
166 uid = cherrypy.session.get(SESSION_KEY) 165 uid = cherrypy.session.get(SESSION_KEY)
167 - test = self.app.get_test(uid)  
168 - if test is None:  
169 - test = self.app.generate_test(uid) # try to generate a new test  
170 - 166 + test = self.app.get_test(uid) or self.app.generate_test(uid)
171 return self.template['test'].render(t=test) 167 return self.template['test'].render(t=test)
172 168
173 # --- CORRECT ------------------------------------------------------------ 169 # --- CORRECT ------------------------------------------------------------
@@ -213,6 +209,26 @@ class Root(object): @@ -213,6 +209,26 @@ class Root(object):
213 allgrades=self.app.get_student_grades_from_all_tests(uid) 209 allgrades=self.app.get_student_grades_from_all_tests(uid)
214 ) 210 )
215 211
  212 + # --- GIVEUP -------------------------------------------------------------
  213 + @cherrypy.expose
  214 + @require()
  215 + def giveup(self):
  216 + uid = cherrypy.session.get(SESSION_KEY)
  217 + grade = self.app.giveup_test(uid)
  218 +
  219 + # --- Expire session
  220 + cherrypy.lib.sessions.expire() # session coockie expires client side
  221 + cherrypy.session[SESSION_KEY] = cherrypy.request.login = None
  222 +
  223 + # --- Show result to student
  224 + return self.template['grade'].render(
  225 + title='title',
  226 + student_id=uid,
  227 + grade=grade,
  228 + allgrades=self.app.get_student_grades_from_all_tests(uid)
  229 + )
  230 +
  231 +
216 # --- ADMIN -------------------------------------------------------------- 232 # --- ADMIN --------------------------------------------------------------
217 @cherrypy.expose 233 @cherrypy.expose
218 @require(name_is('0')) 234 @require(name_is('0'))
templates/test.html
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 <meta charset="utf-8"> 4 <meta charset="utf-8">
5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge">
6 <meta name="viewport" content="width=device-width, initial-scale=1"> 6 <meta name="viewport" content="width=device-width, initial-scale=1">
7 - <title> ${t['title']} </title> 7 + <title> Teste </title>
8 <link rel="icon" href="/static/favicon.ico"> 8 <link rel="icon" href="/static/favicon.ico">
9 9
10 <!-- MathJax --> 10 <!-- MathJax -->
@@ -327,7 +327,7 @@ @@ -327,7 +327,7 @@
327 </div> 327 </div>
328 <div class="modal-footer"> 328 <div class="modal-footer">
329 <button type="button" class="btn btn-success btn-lg" data-dismiss="modal">Não!</button> 329 <button type="button" class="btn btn-success btn-lg" data-dismiss="modal">Não!</button>
330 - <a href="/logout" class="btn btn-danger btn-lg" role="button">Sim, desistir do teste</a> 330 + <a href="/giveup" class="btn btn-danger btn-lg" role="button">Sim, desistir do teste</a>
331 </div> 331 </div>
332 </div> 332 </div>
333 </div> 333 </div>
@@ -11,6 +11,7 @@ logger = logging.getLogger(__name__) @@ -11,6 +11,7 @@ logger = logging.getLogger(__name__)
11 try: 11 try:
12 # import yaml 12 # import yaml
13 import json 13 import json
  14 + import markdown
14 except ImportError: 15 except ImportError:
15 logger.critical('Python package missing. See README.md for instructions.') 16 logger.critical('Python package missing. See README.md for instructions.')
16 sys.exit(1) 17 sys.exit(1)
@@ -236,8 +237,21 @@ class Test(dict): @@ -236,8 +237,21 @@ class Test(dict):
236 return self['grade'] 237 return self['grade']
237 238
238 # ----------------------------------------------------------------------- 239 # -----------------------------------------------------------------------
  240 + def giveup(self):
  241 + self['comments'] = 'DESISTIU'
  242 + self['grade'] = 0.0
  243 + logger.info('Student {}: gave up.'.format(self['student']['number']))
  244 + return self['grade']
  245 +
  246 + # -----------------------------------------------------------------------
239 def save_json(self, filepath): 247 def save_json(self, filepath):
240 with open(filepath, 'w') as f: 248 with open(filepath, 'w') as f:
241 json.dump(self, f, indent=2, default=str) 249 json.dump(self, f, indent=2, default=str)
242 # HACK default=str is required for datetime objects 250 # HACK default=str is required for datetime objects
243 logger.info('Student {}: saved JSON file.'.format(self['student']['number'])) 251 logger.info('Student {}: saved JSON file.'.format(self['student']['number']))
  252 +
  253 + # -----------------------------------------------------------------------
  254 + # def generate_html(self):
  255 + # for q in self['questions']:
  256 + # q['text_html'] = markdown.markdown(q['text'])
  257 +