Commit 6bd5c0063bc65b6b402407251f3c60fb80836ede
1 parent
d378e701
Exists in
master
and in
1 other branch
converted test generation to be done asynchronously.
Showing
4 changed files
with
38 additions
and
32 deletions
Show diff stats
BUGS.md
1 | 1 | ||
2 | # BUGS | 2 | # BUGS |
3 | 3 | ||
4 | +- numeric interval deve converter respostas que usam virgulas para pontos decimais | ||
4 | - a revisao do teste não mostra as imagens. | 5 | - a revisao do teste não mostra as imagens. |
5 | - se aluno tem teste activo e é allowed uma segunda vez, deve manter o mesmo teste. adicionar opcao para eliminar um teste em curso. | 6 | - se aluno tem teste activo e é allowed uma segunda vez, deve manter o mesmo teste. adicionar opcao para eliminar um teste em curso. |
6 | - melhorar o botao de autorizar (desliga-se), usar antes um botao? | 7 | - melhorar o botao de autorizar (desliga-se), usar antes um botao? |
app.py
@@ -136,16 +136,16 @@ class App(object): | @@ -136,16 +136,16 @@ class App(object): | ||
136 | logger.info(f'Student {uid}: logged out.') | 136 | logger.info(f'Student {uid}: logged out.') |
137 | 137 | ||
138 | # ----------------------------------------------------------------------- | 138 | # ----------------------------------------------------------------------- |
139 | - def generate_test(self, uid): | 139 | + async def generate_test(self, uid): |
140 | if uid in self.online: | 140 | if uid in self.online: |
141 | logger.info(f'Student {uid}: generating new test.') | 141 | logger.info(f'Student {uid}: generating new test.') |
142 | student_id = self.online[uid]['student'] | 142 | student_id = self.online[uid]['student'] |
143 | - self.online[uid]['test'] = self.testfactory.generate(student_id) | 143 | + self.online[uid]['test'] = await self.testfactory.generate(student_id) |
144 | + logger.debug(f'Student {uid}: test ok.') | ||
144 | return self.online[uid]['test'] | 145 | return self.online[uid]['test'] |
145 | else: | 146 | else: |
146 | # this implies an error in the code. should never be here! | 147 | # this implies an error in the code. should never be here! |
147 | logger.critical(f'Student {uid}: offline, can\'t generate test') | 148 | logger.critical(f'Student {uid}: offline, can\'t generate test') |
148 | - return None | ||
149 | 149 | ||
150 | # ----------------------------------------------------------------------- | 150 | # ----------------------------------------------------------------------- |
151 | # ans is a dictionary {question_index: answer, ...} | 151 | # ans is a dictionary {question_index: answer, ...} |
serve.py
@@ -24,7 +24,7 @@ from app import App, AppException | @@ -24,7 +24,7 @@ from app import App, AppException | ||
24 | from tools import load_yaml, md_to_html | 24 | from tools import load_yaml, md_to_html |
25 | 25 | ||
26 | # ---------------------------------------------------------------------------- | 26 | # ---------------------------------------------------------------------------- |
27 | -# Decorator used to restrict access only to the administrator | 27 | +# Decorator used to restrict access to the administrator |
28 | # ---------------------------------------------------------------------------- | 28 | # ---------------------------------------------------------------------------- |
29 | def admin_only(func): | 29 | def admin_only(func): |
30 | @functools.wraps(func) | 30 | @functools.wraps(func) |
@@ -96,7 +96,7 @@ class LoginHandler(BaseHandler): | @@ -96,7 +96,7 @@ class LoginHandler(BaseHandler): | ||
96 | self.set_secure_cookie("user", str(uid), expires_days=30) | 96 | self.set_secure_cookie("user", str(uid), expires_days=30) |
97 | self.redirect(self.get_argument("next", "/")) | 97 | self.redirect(self.get_argument("next", "/")) |
98 | else: | 98 | else: |
99 | - self.render("login.html", error='Não autorizado ou número/senha inválido') | 99 | + self.render("login.html", error='Não autorizado ou senha inválida') |
100 | 100 | ||
101 | 101 | ||
102 | # ---------------------------------------------------------------------------- | 102 | # ---------------------------------------------------------------------------- |
@@ -199,10 +199,11 @@ class TestHandler(BaseHandler): | @@ -199,10 +199,11 @@ class TestHandler(BaseHandler): | ||
199 | 199 | ||
200 | # --- GET | 200 | # --- GET |
201 | @tornado.web.authenticated | 201 | @tornado.web.authenticated |
202 | - def get(self): | 202 | + async def get(self): |
203 | uid = self.current_user | 203 | uid = self.current_user |
204 | - # FIXME make generate async? | ||
205 | - t = self.testapp.get_student_test(uid) or self.testapp.generate_test(uid) | 204 | + t = self.testapp.get_student_test(uid) # reload page returns same test |
205 | + if t is None: | ||
206 | + t = await self.testapp.generate_test(uid) | ||
206 | self.render('test.html', t=t, md=md_to_html, templ=self._templates) | 207 | self.render('test.html', t=t, md=md_to_html, templ=self._templates) |
207 | 208 | ||
208 | # --- POST | 209 | # --- POST |
test.py
@@ -6,6 +6,7 @@ import random | @@ -6,6 +6,7 @@ import random | ||
6 | from datetime import datetime | 6 | from datetime import datetime |
7 | import json | 7 | import json |
8 | import logging | 8 | import logging |
9 | +import asyncio | ||
9 | 10 | ||
10 | # this project | 11 | # this project |
11 | import questionfactory as questions | 12 | import questionfactory as questions |
@@ -36,29 +37,30 @@ class TestFactory(dict): | @@ -36,29 +37,30 @@ class TestFactory(dict): | ||
36 | 37 | ||
37 | if conf['review']: | 38 | if conf['review']: |
38 | logger.info('Review mode. No questions loaded.') | 39 | logger.info('Review mode. No questions loaded.') |
39 | - else: | ||
40 | - # loads yaml files to question_factory | ||
41 | - self.question_factory = questions.QuestionFactory() | ||
42 | - self.question_factory.load_files(files=self['files'], questions_dir=self['questions_dir']) | ||
43 | - | ||
44 | - # check if all questions exist ('ref' keys are correct?) | ||
45 | - errors_found = False | ||
46 | - for q in self['questions']: | ||
47 | - for r in q['ref']: | ||
48 | - logger.info(f'Checking question "{r}".') | ||
49 | - try: | ||
50 | - self.question_factory.generate(r) | ||
51 | - # except questions.QuestionFactoryException: | ||
52 | - # logger.critical(f'Can\'t generate question "{r}".') | ||
53 | - except: | ||
54 | - logger.critical(f'Can\'t generate question "{r}".') | ||
55 | - errors_found = True | ||
56 | - | ||
57 | - if errors_found: | ||
58 | - logger.critical('Errors found while generating questions.') | ||
59 | - raise TestFactoryException() | 40 | + return |
60 | 41 | ||
61 | - logger.info(f'Test factory ready for "{self["ref"]}".') | 42 | + # loads yaml files to question_factory |
43 | + self.question_factory = questions.QuestionFactory() | ||
44 | + self.question_factory.load_files(files=self['files'], questions_dir=self['questions_dir']) | ||
45 | + | ||
46 | + # check if all questions exist ('ref' keys are correct?) | ||
47 | + errors_found = False | ||
48 | + for q in self['questions']: | ||
49 | + for r in q['ref']: | ||
50 | + logger.info(f'Checking question "{r}".') | ||
51 | + try: | ||
52 | + self.question_factory.generate(r) | ||
53 | + # except questions.QuestionFactoryException: | ||
54 | + # logger.critical(f'Can\'t generate question "{r}".') | ||
55 | + except: | ||
56 | + logger.critical(f'Can\'t generate question "{r}".') | ||
57 | + errors_found = True | ||
58 | + | ||
59 | + if errors_found: | ||
60 | + logger.critical('Errors found while generating questions.') | ||
61 | + raise TestFactoryException() | ||
62 | + | ||
63 | + logger.info(f'Test factory ready for "{self["ref"]}".') | ||
62 | 64 | ||
63 | 65 | ||
64 | # ----------------------------------------------------------------------- | 66 | # ----------------------------------------------------------------------- |
@@ -144,17 +146,19 @@ class TestFactory(dict): | @@ -144,17 +146,19 @@ class TestFactory(dict): | ||
144 | # Given a dictionary with a student id {'name':'john', 'number': 123} | 146 | # Given a dictionary with a student id {'name':'john', 'number': 123} |
145 | # returns instance of Test() for that particular student | 147 | # returns instance of Test() for that particular student |
146 | # ----------------------------------------------------------------------- | 148 | # ----------------------------------------------------------------------- |
147 | - def generate(self, student): | 149 | + async def generate(self, student): |
148 | test = [] | 150 | test = [] |
149 | total_points = 0.0 | 151 | total_points = 0.0 |
150 | 152 | ||
151 | n = 1 | 153 | n = 1 |
154 | + loop = asyncio.get_running_loop() | ||
155 | + | ||
152 | for qq in self['questions']: | 156 | for qq in self['questions']: |
153 | # generate Question() selected randomly from list of references | 157 | # generate Question() selected randomly from list of references |
154 | qref = random.choice(qq['ref']) | 158 | qref = random.choice(qq['ref']) |
155 | 159 | ||
156 | try: | 160 | try: |
157 | - q = self.question_factory.generate(qref) | 161 | + q = await loop.run_in_executor(None, self.question_factory.generate, qref) |
158 | except: | 162 | except: |
159 | logger.error(f'Can\'t generate question "{qref}". Skipping.') | 163 | logger.error(f'Can\'t generate question "{qref}". Skipping.') |
160 | continue | 164 | continue |