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 |