Commit 1245246ac1b7668cb075807e6957a17443734437

Authored by Miguel Barão
1 parent 27e15117
Exists in master and in 1 other branch dev

- correction of tests is now asynchronous. Each question is sent to the default executor.

@@ -9,14 +9,14 @@ ou usar push (websockets?) @@ -9,14 +9,14 @@ ou usar push (websockets?)
9 - pymips: nao pode executar syscalls do spim. 9 - pymips: nao pode executar syscalls do spim.
10 - perguntas checkbox [right,wrong] com pelo menos uma opção correcta. 10 - perguntas checkbox [right,wrong] com pelo menos uma opção correcta.
11 - questions.py textarea has a abspath which does not make sense! why is it there? not working for perguntations, but seems to work for aprendizations 11 - questions.py textarea has a abspath which does not make sense! why is it there? not working for perguntations, but seems to work for aprendizations
12 -- o eventloop está a bloquear. correção do teste é blocking. usar threadpoolexecutor?  
13 - submissao faz um post ajax. 12 - submissao faz um post ajax.
14 - eventos unfocus? 13 - eventos unfocus?
15 - servidor nao esta a lidar com eventos scroll/resize. ignorar? 14 - servidor nao esta a lidar com eventos scroll/resize. ignorar?
16 15
17 # TODO 16 # TODO
18 17
19 -- gerar teste qd o prof autoriza. 18 +- decorador para user 0, evita o "if uid==0" em muitas funcoes.
  19 +- gerar teste qd o prof autoriza? melhor nao, pode apagar o teste em curso.
20 - enviar resposta de cada pergunta individualmente. 20 - enviar resposta de cada pergunta individualmente.
21 - experimentar gerador de svg que inclua no markdown da pergunta e ver se funciona. 21 - experimentar gerador de svg que inclua no markdown da pergunta e ver se funciona.
22 - suportar cotacao to teste diferente de 20 (e.g. para juntar perguntas em papel). opcao "points: 18" que normaliza total para 18 em vez de 20. 22 - suportar cotacao to teste diferente de 20 (e.g. para juntar perguntas em papel). opcao "points: 18" que normaliza total para 18 em vez de 20.
@@ -37,7 +37,6 @@ ou usar push (websockets?) @@ -37,7 +37,6 @@ ou usar push (websockets?)
37 - fazer renderer para linguagem assembly mips? 37 - fazer renderer para linguagem assembly mips?
38 - permitir eliminar teste a decorrer 38 - permitir eliminar teste a decorrer
39 - cancelar teste no menu admin. Dado o numero de aluno remove teste e faz logout do aluno. 39 - cancelar teste no menu admin. Dado o numero de aluno remove teste e faz logout do aluno.
40 -- decorador para user 0, evita o "if uid==0" em muitas funcoes.  
41 - mathjax-node: 40 - mathjax-node:
42 sudo pkg install node npm 41 sudo pkg install node npm
43 npm install mathjax-node mathjax-node-cli # pacotes em ~/node_modules 42 npm install mathjax-node mathjax-node-cli # pacotes em ~/node_modules
@@ -61,6 +60,8 @@ ou usar push (websockets?) @@ -61,6 +60,8 @@ ou usar push (websockets?)
61 60
62 # FIXED 61 # FIXED
63 62
  63 +- o eventloop está a bloquear. correção do teste é blocking. usar threadpoolexecutor?
  64 +- substituir get_event_loop por get_runnint_loop (ver https://docs.python.org/3/library/asyncio-eventloop.html)
64 - review nao esta a funcionar 65 - review nao esta a funcionar
65 - servir imagens das perguntas não funciona. Necessario passar a ref da pergunta no link para poder ajustar o path no FileHandler. 66 - servir imagens das perguntas não funciona. Necessario passar a ref da pergunta no link para poder ajustar o path no FileHandler.
66 - a primeira coluna da tabela admin deveria estar sempre ordenada. 67 - a primeira coluna da tabela admin deveria estar sempre ordenada.
@@ -26,7 +26,7 @@ class AppException(Exception): @@ -26,7 +26,7 @@ class AppException(Exception):
26 # ============================================================================ 26 # ============================================================================
27 async def check_password(try_pw, password): 27 async def check_password(try_pw, password):
28 try_pw = try_pw.encode('utf-8') 28 try_pw = try_pw.encode('utf-8')
29 - loop = asyncio.get_event_loop() 29 + loop = asyncio.get_running_loop()
30 hashed_pw = await loop.run_in_executor(None, bcrypt.hashpw, try_pw, password) 30 hashed_pw = await loop.run_in_executor(None, bcrypt.hashpw, try_pw, password)
31 return password == hashed_pw 31 return password == hashed_pw
32 32
@@ -150,10 +150,10 @@ class App(object): @@ -150,10 +150,10 @@ class App(object):
150 # ----------------------------------------------------------------------- 150 # -----------------------------------------------------------------------
151 # ans is a dictionary {question_index: answer, ...} 151 # ans is a dictionary {question_index: answer, ...}
152 # for example: {0:'hello', 1:[1,2]} 152 # for example: {0:'hello', 1:[1,2]}
153 - def correct_test(self, uid, ans): 153 + async def correct_test(self, uid, ans):
154 t = self.online[uid]['test'] 154 t = self.online[uid]['test']
155 t.update_answers(ans) 155 t.update_answers(ans)
156 - grade = t.correct() 156 + grade = await t.correct()
157 157
158 # save test in JSON format 158 # save test in JSON format
159 fname = ' -- '.join((t['student']['number'], t['ref'], str(t['finish_time']))) + '.json' 159 fname = ' -- '.join((t['student']['number'], t['ref'], str(t['finish_time']))) + '.json'
@@ -4,6 +4,7 @@ import random @@ -4,6 +4,7 @@ import random
4 import re 4 import re
5 from os import path 5 from os import path
6 import logging 6 import logging
  7 +import asyncio
7 8
8 # user installed libraries 9 # user installed libraries
9 import yaml 10 import yaml
@@ -49,6 +50,11 @@ class Question(dict): @@ -49,6 +50,11 @@ class Question(dict):
49 self['grade'] = 0.0 50 self['grade'] = 0.0
50 return 0.0 51 return 0.0
51 52
  53 + async def correct_async(self):
  54 + loop = asyncio.get_running_loop()
  55 + grade = await loop.run_in_executor(None, self.correct)
  56 + return grade
  57 +
52 def set_defaults(self, d): 58 def set_defaults(self, d):
53 'Add k:v pairs from default dict d for nonexistent keys' 59 'Add k:v pairs from default dict d for nonexistent keys'
54 for k,v in d.items(): 60 for k,v in d.items():
@@ -75,7 +75,6 @@ class LoginHandler(BaseHandler): @@ -75,7 +75,6 @@ class LoginHandler(BaseHandler):
75 def get(self): 75 def get(self):
76 self.render('login.html', error='') 76 self.render('login.html', error='')
77 77
78 - # async  
79 async def post(self): 78 async def post(self):
80 uid = self.get_body_argument('uid') 79 uid = self.get_body_argument('uid')
81 pw = self.get_body_argument('pw') 80 pw = self.get_body_argument('pw')
@@ -181,8 +180,7 @@ class TestHandler(BaseHandler): @@ -181,8 +180,7 @@ class TestHandler(BaseHandler):
181 180
182 # POST 181 # POST
183 @tornado.web.authenticated 182 @tornado.web.authenticated
184 - # async  
185 - def post(self): 183 + async def post(self):
186 uid = self.current_user 184 uid = self.current_user
187 185
188 # self.request.arguments = {'answered-0': [b'on'], '0': [b'13.45']} 186 # self.request.arguments = {'answered-0': [b'on'], '0': [b'13.45']}
@@ -205,9 +203,7 @@ class TestHandler(BaseHandler): @@ -205,9 +203,7 @@ class TestHandler(BaseHandler):
205 'numeric-interval'): 203 'numeric-interval'):
206 ans[i] = ans[i][0] 204 ans[i] = ans[i][0]
207 205
208 - # loop = asyncio.get_event_loop()  
209 - # await loop.run_in_executor(None, self.testapp.correct_test, uid, ans)  
210 - self.testapp.correct_test(uid, ans) 206 + await self.testapp.correct_test(uid, ans)
211 207
212 self.testapp.logout(uid) 208 self.testapp.logout(uid)
213 self.clear_cookie('user') 209 self.clear_cookie('user')
@@ -231,12 +231,15 @@ class Test(dict): @@ -231,12 +231,15 @@ class Test(dict):
231 231
232 # ----------------------------------------------------------------------- 232 # -----------------------------------------------------------------------
233 # Corrects all the answers and computes the final grade 233 # Corrects all the answers and computes the final grade
234 - def correct(self): 234 + async def correct(self):
235 self['finish_time'] = datetime.now() 235 self['finish_time'] = datetime.now()
236 self['state'] = 'FINISHED' 236 self['state'] = 'FINISHED'
237 237
238 - grade = sum(q.correct()*q['points'] for q in self['questions'])  
239 - self['grade'] = max(0, round(grade, 1)) # truncate FIXME scale? 238 + grade = 0.0
  239 + for q in self['questions']:
  240 + grade += await q.correct_async() * q['points']
  241 +
  242 + self['grade'] = max(0, round(grade, 1)) # avoid negative final grades
240 243
241 logger.info(f'Student {self["student"]["number"]}: correction gave {self["grade"]} points.') 244 logger.info(f'Student {self["student"]["number"]}: correction gave {self["grade"]} points.')
242 return self['grade'] 245 return self['grade']