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.

BUGS.md
1 1  
2 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 4 - usar thread.Lock para aceder a variaveis de estado?
9 5 - permitir adicionar imagens nas perguntas.
10 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 8 # TODO
14 9  
15 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 14 - single page web no teste/correcçao. Página construída em javascript, obter perguntas com ajax (para practice?).
17 15 - aviso na pagina principal para quem usa browser da treta
18 16 - permitir varios testes, aluno escolhe qual o teste que quer fazer.
... ... @@ -26,6 +24,7 @@
26 24  
27 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 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 29 - sqlalchemy queixa-se de threads.
31 30 - SQLAlchemy em vez da classe database.
... ...
app.py
... ... @@ -27,6 +27,7 @@ class App(object):
27 27 # uid2: {...}
28 28 # }
29 29 logger.info('============= Running perguntations =============')
  30 + self.lock = threading.Lock()
30 31 self.online = dict() # {uid: {'student':{}}}
31 32 self.allowed = set([]) # '0' is hardcoded to allowed elsewhere
32 33  
... ... @@ -115,7 +116,9 @@ class App(object):
115 116 if uid in self.online:
116 117 logger.info('Student {}: generating new test.'.format(uid))
117 118 student_id = self.online[uid]['student']
  119 + self.lock.acquire() # FIXME is it needed?
118 120 self.online[uid]['test'] = self.testfactory.generate(student_id)
  121 + self.lock.release()
119 122 return self.online[uid]['test']
120 123 else:
121 124 logger.error('Student {}: offline, can''t generate test'.format(uid))
... ... @@ -152,6 +155,16 @@ class App(object):
152 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 169 # --- helpers (getters)
157 170 def get_student_name(self, uid):
... ...
serve.py
... ... @@ -152,7 +152,6 @@ class Root(object):
152 152 cherrypy.lib.sessions.expire() # session coockie expires client side
153 153 cherrypy.session[SESSION_KEY] = cherrypy.request.login = None
154 154 cherrypy.log.error('Student {0} logged out.'.format(uid), 'APPLICATION')
155   -
156 155 self.app.logout(uid)
157 156 raise cherrypy.HTTPRedirect('/')
158 157  
... ... @@ -164,10 +163,7 @@ class Root(object):
164 163 @require()
165 164 def test(self):
166 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 167 return self.template['test'].render(t=test)
172 168  
173 169 # --- CORRECT ------------------------------------------------------------
... ... @@ -213,6 +209,26 @@ class Root(object):
213 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 232 # --- ADMIN --------------------------------------------------------------
217 233 @cherrypy.expose
218 234 @require(name_is('0'))
... ...
templates/test.html
... ... @@ -4,7 +4,7 @@
4 4 <meta charset="utf-8">
5 5 <meta http-equiv="X-UA-Compatible" content="IE=edge">
6 6 <meta name="viewport" content="width=device-width, initial-scale=1">
7   - <title> ${t['title']} </title>
  7 + <title> Teste </title>
8 8 <link rel="icon" href="/static/favicon.ico">
9 9  
10 10 <!-- MathJax -->
... ... @@ -327,7 +327,7 @@
327 327 </div>
328 328 <div class="modal-footer">
329 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 331 </div>
332 332 </div>
333 333 </div>
... ...
test.py
... ... @@ -11,6 +11,7 @@ logger = logging.getLogger(__name__)
11 11 try:
12 12 # import yaml
13 13 import json
  14 + import markdown
14 15 except ImportError:
15 16 logger.critical('Python package missing. See README.md for instructions.')
16 17 sys.exit(1)
... ... @@ -236,8 +237,21 @@ class Test(dict):
236 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 247 def save_json(self, filepath):
240 248 with open(filepath, 'w') as f:
241 249 json.dump(self, f, indent=2, default=str)
242 250 # HACK default=str is required for datetime objects
243 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 +
... ...