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 | 9 | - pymips: nao pode executar syscalls do spim. |
| 10 | 10 | - perguntas checkbox [right,wrong] com pelo menos uma opção correcta. |
| 11 | 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 | 12 | - submissao faz um post ajax. |
| 14 | 13 | - eventos unfocus? |
| 15 | 14 | - servidor nao esta a lidar com eventos scroll/resize. ignorar? |
| 16 | 15 | |
| 17 | 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 | 20 | - enviar resposta de cada pergunta individualmente. |
| 21 | 21 | - experimentar gerador de svg que inclua no markdown da pergunta e ver se funciona. |
| 22 | 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 | 37 | - fazer renderer para linguagem assembly mips? |
| 38 | 38 | - permitir eliminar teste a decorrer |
| 39 | 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 | 40 | - mathjax-node: |
| 42 | 41 | sudo pkg install node npm |
| 43 | 42 | npm install mathjax-node mathjax-node-cli # pacotes em ~/node_modules |
| ... | ... | @@ -61,6 +60,8 @@ ou usar push (websockets?) |
| 61 | 60 | |
| 62 | 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 | 65 | - review nao esta a funcionar |
| 65 | 66 | - servir imagens das perguntas não funciona. Necessario passar a ref da pergunta no link para poder ajustar o path no FileHandler. |
| 66 | 67 | - a primeira coluna da tabela admin deveria estar sempre ordenada. | ... | ... |
app.py
| ... | ... | @@ -26,7 +26,7 @@ class AppException(Exception): |
| 26 | 26 | # ============================================================================ |
| 27 | 27 | async def check_password(try_pw, password): |
| 28 | 28 | try_pw = try_pw.encode('utf-8') |
| 29 | - loop = asyncio.get_event_loop() | |
| 29 | + loop = asyncio.get_running_loop() | |
| 30 | 30 | hashed_pw = await loop.run_in_executor(None, bcrypt.hashpw, try_pw, password) |
| 31 | 31 | return password == hashed_pw |
| 32 | 32 | |
| ... | ... | @@ -150,10 +150,10 @@ class App(object): |
| 150 | 150 | # ----------------------------------------------------------------------- |
| 151 | 151 | # ans is a dictionary {question_index: answer, ...} |
| 152 | 152 | # for example: {0:'hello', 1:[1,2]} |
| 153 | - def correct_test(self, uid, ans): | |
| 153 | + async def correct_test(self, uid, ans): | |
| 154 | 154 | t = self.online[uid]['test'] |
| 155 | 155 | t.update_answers(ans) |
| 156 | - grade = t.correct() | |
| 156 | + grade = await t.correct() | |
| 157 | 157 | |
| 158 | 158 | # save test in JSON format |
| 159 | 159 | fname = ' -- '.join((t['student']['number'], t['ref'], str(t['finish_time']))) + '.json' | ... | ... |
questions.py
| ... | ... | @@ -4,6 +4,7 @@ import random |
| 4 | 4 | import re |
| 5 | 5 | from os import path |
| 6 | 6 | import logging |
| 7 | +import asyncio | |
| 7 | 8 | |
| 8 | 9 | # user installed libraries |
| 9 | 10 | import yaml |
| ... | ... | @@ -49,6 +50,11 @@ class Question(dict): |
| 49 | 50 | self['grade'] = 0.0 |
| 50 | 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 | 58 | def set_defaults(self, d): |
| 53 | 59 | 'Add k:v pairs from default dict d for nonexistent keys' |
| 54 | 60 | for k,v in d.items(): | ... | ... |
serve.py
| ... | ... | @@ -75,7 +75,6 @@ class LoginHandler(BaseHandler): |
| 75 | 75 | def get(self): |
| 76 | 76 | self.render('login.html', error='') |
| 77 | 77 | |
| 78 | - # async | |
| 79 | 78 | async def post(self): |
| 80 | 79 | uid = self.get_body_argument('uid') |
| 81 | 80 | pw = self.get_body_argument('pw') |
| ... | ... | @@ -181,8 +180,7 @@ class TestHandler(BaseHandler): |
| 181 | 180 | |
| 182 | 181 | # POST |
| 183 | 182 | @tornado.web.authenticated |
| 184 | - # async | |
| 185 | - def post(self): | |
| 183 | + async def post(self): | |
| 186 | 184 | uid = self.current_user |
| 187 | 185 | |
| 188 | 186 | # self.request.arguments = {'answered-0': [b'on'], '0': [b'13.45']} |
| ... | ... | @@ -205,9 +203,7 @@ class TestHandler(BaseHandler): |
| 205 | 203 | 'numeric-interval'): |
| 206 | 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 | 208 | self.testapp.logout(uid) |
| 213 | 209 | self.clear_cookie('user') | ... | ... |
test.py
| ... | ... | @@ -231,12 +231,15 @@ class Test(dict): |
| 231 | 231 | |
| 232 | 232 | # ----------------------------------------------------------------------- |
| 233 | 233 | # Corrects all the answers and computes the final grade |
| 234 | - def correct(self): | |
| 234 | + async def correct(self): | |
| 235 | 235 | self['finish_time'] = datetime.now() |
| 236 | 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 | 244 | logger.info(f'Student {self["student"]["number"]}: correction gave {self["grade"]} points.') |
| 242 | 245 | return self['grade'] | ... | ... |