Commit 870abe3cb5d32d58c0e6d1f1a46be2e748999075
1 parent
3c36fb56
Exists in
master
and in
1 other branch
- code to give up test when exiting. Also saves test.
Showing
5 changed files
with
54 additions
and
12 deletions
Show diff stats
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 | + | ... | ... |