Commit 1245246ac1b7668cb075807e6957a17443734437
1 parent
27e15117
Exists in
master
and in
1 other branch
- correction of tests is now asynchronous. Each question is sent to the default executor.
Showing
5 changed files
with
21 additions
and
15 deletions
Show diff stats
BUGS.md
| @@ -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. |
app.py
| @@ -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' |
questions.py
| @@ -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(): |
serve.py
| @@ -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') |
test.py
| @@ -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'] |